При разработке запросов в платформе 1С:Предприятие часто возникает задача сделать отбор опциональным — например, накладывать фильтр по реквизиту только тогда, когда пользователь заполнил соответствующее поле на форме. В связи с этим у разработчиков возникает два ключевых вопроса: можно ли передавать в метод УстановитьПараметр() переменные, которых нет в тексте запроса, и как технически правильно реализовать необязательное условие в секции ГДЕ.
Разберем детально поведение платформы 1С и рассмотрим наиболее эффективные подходы к решению этой задачи.
Проанализируем базовые правила платформы 1С при работе с объектом Запрос:
& (например, &ПарамНаименование), но перед вызовом метода Выполнить() разработчик не вызвал УстановитьПараметр(), платформа сгенерирует исключительную ситуацию: «Не задано значение параметра...». В тексте запроса все объявленные параметры являются строго обязательными.УстановитьПараметр() имя и значение, которых нет в тексте запроса, платформа 1С проигнорирует этот вызов и выполнит запрос без ошибок.Посмотрим на пример кода, демонстрирующий установку неиспользуемого параметра:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
// Параметра &ОтборНаименование в тексте нет, но код выполнится без ошибок:
Запрос.УстановитьПараметр("ОтборНаименование", Истина);
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка результата
КонецЦикла;
Иногда разработчики пытаются сделать параметр отбора условно-необязательным прямо внутри текста запроса, используя ветвление в блоке ГДЕ. Посмотрим на пример такой реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| ВЫБОР
| КОГДА &ОтборНаименование
| ТОГДА Номенклатура.Наименование = &ПарамНаименование
| ИНАЧЕ ИСТИНА
| КОНЕЦ";
Запрос.УстановитьПараметр("ОтборНаименование", ОтборНаименование);
Запрос.УстановитьПараметр("ПарамНаименование", ПарамНаименование);
Результат = Запрос.Выполнить();
Внимание: данный подход является антипаттерном. Использование конструкций ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ИСТИНА КОНЕЦ, а также условий с ИЛИ (например, ГДЕ &Параметр = ЗНАЧЕНИЕ(...) ИЛИ Поле = &Параметр) критически снижает производительность. Оптимизатор СУБД не может заранее построить эффективный план выполнения и задействовать индексы таблиц, что приводит к полному сканированию таблицы (Table Scan) на больших объемах данных.
Кроме того, сравнение параметров с Неопределено или NULL непосредственно в тексте запроса может приводить к ошибкам трансляции в SQL в зависимости от типов полей.
Разберем наиболее распространенный и производительный способ решения задачи на встроенном языке 1С — динамическое формирование секции ГДЕ в зависимости от заполненности условий.
Запрос = Новый Запрос;
ТекстЗапроса =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
МассивУсловий = Новый Массив;
Если ЗначениеЗаполнено(ПарамНаименование) Тогда
МассивУсловий.Добавить("Номенклатура.Наименование = &ПарамНаименование");
Запрос.УстановитьПараметр("ПарамНаименование", ПарамНаименование);
КонецЕсли;
Если МассивУсловий.Количество() > 0 Тогда
ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ГДЕ" + Символы.ПС + СтрСоединить(МассивУсловий, " И ");
КонецЕсли;
Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить();
Такой подход гарантирует, что в СУБД уйдет только необходимый фрагмент условия, а оптимизатор сможет применить индекс по полю Наименование.
Если требуется избежать ручной конкатенации строк, используем программный объект СхемаЗапроса. Он позволяет безопасно модифицировать структуру запроса, включая коллекцию отборов оператора выбора.
Схема = Новый СхемаЗапроса;
Схема.УстановитьТекстЗапроса(
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура"
);
Если ЗначениеЗаполнено(ПарамНаименование) Тогда
Оператор = Схема.ПакетЗапросов[0].Операторы[0];
Оператор.Отбор.Добавить("Номенклатура.Наименование = &ПарамНаименование");
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = Схема.ПолучитьТекстЗапроса();
Если ЗначениеЗаполнено(ПарамНаименование) Тогда
Запрос.УстановитьПараметр("ПарамНаименование", ПарамНаименование);
КонецЕсли;
Результат = Запрос.Выполнить();
Для отчетов и динамических списков идеальным решением является Система компоновки данных. В СКД механизмы необязательных параметров и условий встроены на уровне платформы:
Когда параметров много, удобно хранить их в объекте Структура и устанавливать в цикле:
СтруктураПараметров = Новый Структура;
Если ЗначениеЗаполнено(ПарамНаименование) Тогда
СтруктураПараметров.Вставить("ПарамНаименование", ПарамНаименование);
КонецЕсли;
Для Каждого КлючИЗначение Из СтруктураПараметров Цикл
Запрос.УстановитьПараметр(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЦикла;
Таким образом, для реализации гибких и производительных алгоритмов следует исключать конструкции ВЫБОР из секции ГДЕ и формировать текст условий динамически во встроенном языке либо средствами СКД.