При разработке внешних обработок для загрузки данных из внешних источников (например, из файлов Excel) часто возникает задача идентификации объектов в базе данных. Типичный сценарий: в файле содержатся Наименование, Артикул и Код номенклатуры. Пользователю необходимо предоставить выбор, по каким именно полям искать соответствие, причем поиск должен работать по принципу "полного совпадения" всех выбранных критериев.
В этой статье мы подробно разберем, как реализовать такой поиск эффективно, избегая нагромождения условий Если...Тогда и учитывая особенности работы с запросами в 1С.
Начинающие разработчики часто пытаются решить задачу "в лоб", перебирая все возможные комбинации установленных флажков поиска. Проанализируем, почему это плохой путь. Если у нас есть три критерия поиска (Код, Артикул, Наименование), то количество комбинаций будет равно семи. Для каждого варианта придется писать отдельный блок кода или отдельный текст запроса.
Рассмотрим пример того, как делать не рекомендуется:
Для Каждого ТекСтрока из ТабЧасть Цикл
Если Флаг_Наименование И Флаг_Код И Флаг_Артикул Тогда
// Один текст запроса с тремя ГДЕ
ИначеЕсли Флаг_Наименование И Флаг_Код Тогда
// Второй текст запроса с двумя ГДЕ
// ... и так далее
КонецЕсли;
КонецЦикла;
Такой код сложно поддерживать, в нем легко допустить ошибку, а при добавлении четвертого критерия (например, производителя) количество веток условий вырастет в геометрической прогрессии.
Самый элегантный и лаконичный способ — передать состояние флажков поиска напрямую в параметры запроса. Это позволяет использовать один и тот же текст запроса для любых комбинаций фильтров. Разберем логику условия:
(Номенклатура.Наименование = &Наименование ИЛИ НЕ &Флаг_Наименование)
Как это работает? Рассмотрим два состояния:
НЕ &Флаг_Наименование превращается в Ложь. Чтобы все выражение в скобках стало истинным, СУБД обязана проверить первую часть — Номенклатура.Наименование = &Наименование. Поиск будет выполнен.НЕ &Флаг_Наименование превращается в Истину. Поскольку используется оператор ИЛИ, все выражение в скобках становится истинным независимо от того, что написано в первой части. Поле просто игнорируется при фильтрации.Проанализируем итоговый программный код:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| (Номенклатура.Наименование = &Наименование ИЛИ НЕ &Флаг_Наименование)
| И (Номенклатура.Код = &Код ИЛИ НЕ &Флаг_Код)
| И (Номенклатура.Артикул = &Артикул ИЛИ НЕ &Флаг_Артикул)";
Для Каждого ТекСтрока из ТабЧасть Цикл
Запрос.УстановитьПараметр("Наименование", ТекСтрока.Т_Наименование);
Запрос.УстановитьПараметр("Код", ТекСтрока.Т_Код);
Запрос.УстановитьПараметр("Артикул", ТекСтрока.Т_Артикул);
// Передаем значения самих флажков с формы
Запрос.УстановитьПараметр("Флаг_Наименование", Флаг_Наименование);
Запрос.УстановитьПараметр("Флаг_Код", Флаг_Код);
Запрос.УстановитьПараметр("Флаг_Артикул", Флаг_Артикул);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Выборка = Результат.Выбрать();
Выборка.Следующий();
ТекСтрока.Т_Номенклатура = Выборка.Ссылка;
КонецЕсли;
КонецЦикла;
Хотя первый метод удобен, он может работать не оптимально на огромных базах данных, так как SQL-серверу сложнее построить план запроса с условиями ИЛИ. В таких случаях лучше формировать текст запроса динамически, добавляя только нужные поля ГДЕ.
Разберем этот подход по шагам:
ТекстЗапроса = "ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура ГДЕ ИСТИНА";
Если Флаг_Наименование Тогда
ТекстЗапроса = ТекстЗапроса + " И Номенклатура.Наименование = &Наименование";
КонецЕсли;
Если Флаг_Код Тогда
ТекстЗапроса = ТекстЗапроса + " И Номенклатура.Код = &Код";
КонецЕсли;
Если Флаг_Артикул Тогда
ТекстЗапроса = ТекстЗапроса + " И Номенклатура.Артикул = &Артикул";
КонецЕсли;
Запрос = Новый Запрос(ТекстЗапроса);
// Далее установка параметров и выполнение в цикле...
Важный момент: Мы используем ГДЕ ИСТИНА в начале, чтобы потом просто добавлять условия через И, не проверяя, является ли это условие первым.
Для конфигураций на обычных формах (как УТ 10.3) очень удобно использовать объект ПостроительЗапроса. Он позволяет управлять отборами программно, и система сама сформирует оптимальный текст SQL.
Построитель = Новый ПостроительЗапроса;
Построитель.Текст = "ВЫБРАТЬ Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура {ГДЕ Номенклатура.Ссылка.*}";
// Добавляем отборы динамически
Если Флаг_Код Тогда
ОтборКод = Построитель.Отбор.Добавить("Код");
ОтборКод.Использование = Истина;
ОтборКод.Значение = НужныйКод;
КонецЕсли;
Построитель.Выполнить();
Результат = Построитель.Результат.Выгрузить();
При реализации поиска по полному соответствию необходимо учитывать специфику данных 1С и Excel, иначе поиск может не дать результатов даже при видимом совпадении.
1. Лидирующие нули в кодах.
В справочнике 1С код часто имеет тип "Строка" и содержит лидирующие нули (например, "000000123"). При загрузке из Excel программа может прочитать это как число 123. При сравнении строки "000000123" и числа 123 (или строки "123") 1С выдаст "Ложь". Необходимо дополнять код нулями до длины, установленной в метаданных справочника, используя функцию ОбщегоНазначения.ДополнитьСтрокуЛП() или аналогичную.
2. Пробелы и невидимые символы.
Данные из Excel часто содержат лишние пробелы в конце или в начале строки. Всегда используйте функцию СокрЛП() для входящих параметров поиска.
3. Производительность (Запрос в цикле).
Если в вашей таблице более 100-200 строк, выполнять запрос на каждой итерации цикла — плохая практика. Каждое выполнение запроса — это отдельное обращение к серверу БД. Рассмотрим более производительный алгоритм:
МенеджерВременныхТаблиц).Номенклатура по условиям, которые выбрал пользователь.4. Индексация полей.
Для того чтобы поиск по Артикулу работал быстро, убедитесь, что в конфигураторе у этого реквизита установлено свойство Индексировать. Поля Код и Наименование индексируются платформой по умолчанию.
Выяснив эти особенности и выбрав подходящий метод реализации запроса, вы сможете создать надежный и быстрый инструмент для поиска данных, который будет корректно отрабатывать любые сочетания пользовательских фильтров.