При разработке в среде 1С:Предприятие программисты часто сталкиваются с задачей поиска элементов в справочниках по сложным критериям. Типичный пример — необходимость найти элемент не по всей базе данных, а исключительно внутри определенной папки (группы) справочника. Это позволяет сузить область поиска, избежать ложных срабатываний (когда элементы с одинаковыми наименованиями лежат в разных папках) и существенно оптимизировать скорость работы программы.
В этой статье мы подробно разберем, как правильно реализовать поиск по нескольким условиям, рассмотрим различные способы фильтрации по иерархии, проанализируем типичные ошибки разработчиков и разберем вопросы производительности различных подходов.
Использование механизма запросов в 1С — это самый гибкий, универсальный и рекомендуемый способ получения данных. Если нам нужно выбрать элементы, родителем которых является конкретная группа, мы можем составить простой запрос с условием в секции ГДЕ.
Давайте рассмотрим классический пример реализации такого запроса. Предположим, у нас есть иерархический справочник Номенклатура, и мы хотим выбрать все элементы, находящиеся непосредственно в группе "Любимые".
Сначала разберем правильный шаблон кода:
// Шаг 1. Получаем ссылку на интересующую нас группу-родителя.
// В реальных задачах ссылка может быть получена из реквизита формы, документа или найдена программно.
СсылкаНаРодителя = Справочники.Номенклатура.НайтиПоНаименованию("Любимые");
Если Не СсылкаНаРодителя.Пустая() Тогда
// Шаг 2. Инициализируем запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель = &Родитель";
// Шаг 3. Передаем ссылку на группу в параметры запроса
Запрос.УстановитьПараметр("Родитель", СсылкаНаРодителя);
// Шаг 4. Выполняем запрос и обходим результаты
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
КонецЕсли;
Важное замечание по отладке: Начинающие разработчики часто сталкиваются с непониманием работы отладчика на шаге обхода выборки. Если поставить точку останова на строке Пока Выборка.Следующий() Цикл и попытаться вычислить выражение Выборка.Наименование, конфигуратор выдаст сообщение "Ошибка чтения запроса" или "Ошибка чтения свойства".
Выясним причину этого поведения. Дело в том, что объект ВыборкаИзРезультатаЗапроса до первого вызова метода Следующий() находится в "нулевой" позиции (перед первой записью) и еще не содержит считанных данных. Только после того, как управление зайдет внутрь цикла (то есть метод Следующий() выполнится и вернет значение Истина), поля текущей строки станут доступны для анализа в табло отладчика.
Очень распространенная ошибка при написании подобных алгоритмов — попытка передать в параметр запроса текстовое представление группы вместо ссылочного типа данных. Посмотрим на некорректный фрагмент кода:
// ОШИБКА: Строка.Группа содержит текст (например, "Продукты питания")
Запрос.УстановитьПараметр("Родитель", Строка.Группа);
Если выполнить такой запрос, система не выдаст синтаксическую ошибку, но результат выборки будет пустым. Почему так происходит? Поле Родитель в таблице справочника базы данных имеет тип СправочникСсылка. СУБД не может корректно сравнить уникальный идентификатор ссылки с текстовой строкой, поэтому условие фильтрации Номенклатура.Родитель = &Родитель никогда не выполнится.
Чтобы исправить эту ситуацию, нам необходимо предварительно преобразовать строку в ссылку. Сделать это можно, выполнив поиск по наименованию менеджера соответствующего справочника:
// Находим ссылку по строковому названию группы
СсылкаНаРодителя = Справочники.Номенклатура.НайтиПоНаименованию(Строка.Группа);
// Теперь передаем в параметр именно Ссылку, а не Строку
Запрос.УстановитьПараметр("Родитель", СсылкаНаРодителя);
Только после этого сравнение в базе данных отработает корректно, и мы получим ожидаемый список элементов.
Рассмотренный выше вариант с условием Номенклатура.Родитель = &Родитель имеет существенное ограничение: он находит элементы только на первом уровне вложенности выбранной группы.
Представим структуру каталога:
Если мы передадим в параметр &Родитель ссылку на группу "Бытовая техника" с использованием оператора равенства (=), то элемент "Стиральная машина LG" найден не будет, так как его непосредственным родителем является папка "Стиральные машины", а вовсе не "Бытовая техника".
Чтобы произвести поиск по всей ветке структуры каталога, включая любые уровни вложенности подчиненных папок, мы должны использовать специальный оператор языка запросов 1С — В ИЕРАРХИИ.
Проанализируем пример запроса с использованием иерархического поиска:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В ИЕРАРХИИ(&Родитель)";
Запрос.УстановитьПараметр("Родитель", ГруппаБытоваяТехника);
РезультатЗапроса = Запрос.Выполнить();
Использование конструкции Ссылка В ИЕРАРХИИ(&Родитель) заставит платформу автоматически определить все подгруппы, входящие в указанную группу, и выбрать элементы из каждой подгруппы на любой глубине вложенности. Это чрезвычайно мощный инструмент для работы с каталогами товаров.
В некоторых случаях создавать объект Запрос ради поиска одного конкретного элемента нецелесообразно с точки зрения лаконичности кода. Если перед нами стоит задача быстро найти один элемент по коду, наименованию или определенному реквизиту строго внутри конкретной группы, мы можем воспользоваться встроенными методами менеджера справочника.
У стандартных методов поиска платформы 1С есть необязательные параметры, о которых разработчики часто забывают. Обратимся к синтаксису методов:
НайтиПоНаименованию(Наименование, ТочноеСоответствие, Родитель, Владелец)НайтиПоКоду(Код, Родитель, Владелец)НайтиПоРеквизиту(ИмяРеквизита, ЗначениеРеквизита, Родитель, Владелец)Обратите внимание на параметр Родитель. Передав туда ссылку на нужную группу, мы ограничим область поиска этой группой. Посмотрим на практический пример:
// Быстрый поиск элемента "Клубника" строго внутри группы "Любимые"
ГруппаЛюбимые = Справочники.Вкусы.НайтиПоНаименованию("Любимые");
Если Не ГруппаЛюбимые.Пустая() Тогда
// Третьим параметром передаем ссылку на группу-родителя
НайденныйЭлемент = Справочники.Вкусы.НайтиПоНаименованию("Клубника", Истина, ГруппаЛюбимые);
Если ЗначениеЗаполнено(НайденныйЭлемент) Тогда
Сообщить("Элемент найден: " + НайденныйЭлемент);
Иначе
Сообщить("В группе 'Любимые' нет элемента с наименованием 'Клубника'!");
EndIf;
КонецЕсли;
Этот метод работает быстрее с точки зрения написания кода и отлично подходит для простых точечных проверок "на лету". Однако помните: данный поиск ищет элементы только на первом уровне указанной группы (аналогично оператору = в запросе). Поиск во вложенных подгруппах этот метод не поддерживает.
Название темы форума звучит как "Выбор по нескольким условиям". На практике поиск по группе — это лишь одно из условий. В реальных бизнес-сценариях нам часто приходится комбинировать его с другими фильтрами: проверять статус пометки на удаление, анализировать виды номенклатуры, цены или остатки.
Давайте составим сложный комплексный запрос, который находит элементы внутри группы по нескольким критериям одновременно:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Артикул КАК Артикул
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В ИЕРАРХИИ(&Родитель)
| И Номенклатура.ПометкаУдаления = ЛОЖЬ
| И Номенклатура.ВидНоменклатуры = &ВидНоменклатуры
| И Номенклатура.Производитель = &Производитель";
Запрос.УстановитьПараметр("Родитель", ГруппаБытоваяТехника);
Запрос.УстановитьПараметр("ВидНоменклатуры", Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Товар"));
Запрос.УстановитьПараметр("Производитель", Справочники.Контрагенты.НайтиПоНаименованию("Завод Bosch"));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка результатов поиска
КонецЦикла;
В данном примере мы использовали логический оператор И для объединения четырех независимых условий. Запрос вернет только те элементы, которые удовлетворяют всем критериям одновременно.
При работе с крупными базами данных вопросы производительности выходят на первый план. Неправильно написанный поисковый запрос может намертво заблокировать работу пользователей или выполняться непозволительно долго. Рассмотрим основные правила оптимизации:
Родитель = &Родитель всегда работает в разы быстрее, чем условие Ссылка В ИЕРАРХИИ(&Родитель). При прямом сравнении СУБД делает простую и быструю фильтрацию по индексу. При использовании оператора В ИЕРАРХИИ СУБД вынуждена выполнять рекурсивный обход дерева групп и строить временную таблицу всех подгрупп. По возможности старайтесь проектировать структуру данных так, чтобы избегать глубокой вложенности и обходиться прямым сравнением по родителю.
Номенклатура.Производитель = &Производитель), убедитесь, что у реквизита "Производитель" в конфигураторе включено свойство "Индексировать" или "Индексировать с дополнительным упорядочиванием". Без индекса СУБД будет выполнять полное сканирование (Table Scan) всей таблицы справочника, что приведет к сильному падению производительности по мере роста базы данных.
ВЫБРАТЬ ПЕРВЫЕ 1. Это позволит СУБД прекратить поиск сразу же после нахождения первого подходящего элемента, не тратя ресурсы на обход оставшейся части таблицы.
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Родитель = &Родитель";
Применяя на практике данные методы фильтрации и оптимизации, мы сможем создавать быстрые, надежные и легко читаемые алгоритмы поиска данных в любых конфигурациях на платформе 1С:Предприятие.