При разработке отчетов, обработок и печатных форм часто возникает задача формировать запрос с динамическим набором условий или полей. Если склеивать текст запроса через конкатенацию строк с переменными, Конструктор запросов платформы 1С перестает открывать такой текст без предварительного ручного редактирования. Разберем по шагам, какие способы позволяют динамически управлять текстом запроса, сохраняя удобство отладки через Конструктор запросов, и какие ограничения производительности при этом следует учитывать.
Рассмотрим популярный прием, при котором в статичном тексте запроса заранее объявляются условия-заглушки. Это позволяет без проблем открывать и редактировать текст запроса в Конструкторе, а перед выполнением запроса заменять заглушки на реальные условия на встроенном языке 1С.
Посмотрим на пример с использованием истинных предикатов:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Заказы.Номенклатура КАК Номенклатура,
| Заказы.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ЗаказыКлиентов.Остатки КАК Заказы
|ГДЕ
| 1 = 1
| И Заказы.КоличествоОстаток > 0";
Если ЗначениеЗаполнено(Контрагент) Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст, "1 = 1", "Заказы.ЗаказКлиента.Контрагент В ИЕРАРХИИ(&Контрагент)");
Запрос.УстановитьПараметр("Контрагент", Контрагент);
КонецЕсли;
Проанализируем аналогичный прием для динамического выбора полей, который встречается в типовых конфигурациях (например, при переключении между кодом, артикулом или наименованием товара):
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВложенныйЗапрос.Номенклатура КАК Номенклатура,
| ВЫРАЗИТЬ(ВложенныйЗапрос.Номенклатура.НаименованиеПолное КАК СТРОКА(1000)) КАК ТоварНаименование,
| 0 КАК ТоварКод
|ИЗ
| Справочник.Номенклатура КАК ВложенныйЗапрос";
Если ИспользоватьАртикул Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст, "0 КАК ТоварКод", "ВложенныйЗапрос.Номенклатура.Артикул КАК ТоварКод");
Иначе
Запрос.Текст = СтрЗаменить(Запрос.Текст, "0 КАК ТоварКод", "ВложенныйЗапрос.Номенклатура.Код КАК ТоварКод");
КонецЕсли;
Важное предостережение: Использование простых чисел и конструкций вроде 1 = 1 или 0 в качестве маркеров замены несет риски ложных срабатываний, если эти цифры встретятся в строковых литералах, числовых константах или других частях запроса. Для повышения надежности рекомендуется использовать уникальные семантические маркеры, например &_УсловиеКонтрагент или комментарии-метки, обернутые в валидный синтаксис.
Платформа 1С содержит встроенный механизм инструкций построителя в тексте запроса. Конструктор запросов штатно поддерживает этот синтаксис на вкладке «Построитель» и не ломается при сохранении запроса.
Разберем пример с использованием объекта ПостроительЗапроса:
Построитель = Новый ПостроительЗапроса;
Построитель.Текст =
"ВЫБРАТЬ
| Заказы.Номенклатура КАК Номенклатура,
| Заказы.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ЗаказыКлиентов.Остатки КАК Заказы
|{ГДЕ
| Заказы.ЗаказКлиента.Контрагент.* КАК Контрагент,
| Заказы.Номенклатура.* КАК Номенклатура}";
// Добавляем отбор только при необходимости
Если ЗначениеЗаполнено(Контрагент) Тогда
ЭлементОтбора = Построитель.Отбор.Добавить("Контрагент");
ЭлементОтбора.ВидСравнения = ВидСравнения.ВИерархии;
ЭлементОтбора.Значение = Контрагент;
ЭлементОтбора.Использование = Истина;
КонецЕсли;
Результат = Построитель.Результат;
При таком подходе платформа сама сгенерирует результирующий SQL-запрос, исключив неиспользуемые секции и не перегружая код строковыми манипуляциями.
Когда требуется сформировать сложную логику без жестких маркеров в тексте, наиболее надежным и читаемым подходом является сборка условий в массив на встроенном языке с последующей вставкой в шаблон запроса.
Посмотрим на реализацию:
МассивУсловий = Новый Массив;
// Обязательные условия
МассивУсловий.Добавить("Заказы.КоличествоОстаток > 0");
// Динамические условия
Если ЗначениеЗаполнено(Контрагент) Тогда
МассивУсловий.Добавить("Заказы.ЗаказКлиента.Контрагент В ИЕРАРХИИ(&Контрагент)");
КонецЕсли;
Если ЗначениеЗаполнено(Склад) Тогда
МассивУсловий.Добавить("Заказы.Склад = &Склад");
КонецЕсли;
ТекстУсловий = СтрСоединить(МассивУсловий, Символы.ПС + " И ");
ШаблонЗапроса =
"ВЫБРАТЬ
| Заказы.Номенклатура КАК Номенклатура,
| Заказы.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ЗаказыКлиентов.Остатки КАК Заказы
|ГДЕ
| %1";
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон(ШаблонЗапроса, ТекстУсловий);
Если ЗначениеЗаполнено(Контрагент) Тогда
Запрос.УстановитьПараметр("Контрагент", Контрагент);
КонецЕсли;
Если ЗначениеЗаполнено(Склад) Тогда
Запрос.УстановитьПараметр("Склад", Склад);
КонецЕсли;
Для открытия такого запроса в Конструкторе достаточно временно подставить в секцию ГДЕ любое базовое условие (например, ИСТИНА), настроить поля и таблицы, после чего вернуть вызов функции СтрШаблон.
Для сложных аналитических отчетов и выборок с большим числом необязательных фильтров наиболее производительным решением является программная работа с СКД через объекты СхемаКомпоновкиДанных и КомпоновщикМакетаКомпоновкиДанных.
СКД обладает ключевыми преимуществами:
Выясним причину, по которой к динамическим условиям нужно подходить с осторожностью с точки зрения оптимизации СУБД:
1 = 1: Хотя современные СУБД (PostgreSQL, MS SQL Server) легко оптимизируют тривиальные выражения, большое количество лишних условий в сложных запросах может искажать статистику селективности и оценку кардинальности, приводя к неоптимальному выбору индексов (например, замене Index Seek на полное сканирование Table Scan).ИЛИ в параметрах: Попытка избежать строковых замен конструкцией ГДЕ (&НеОтбирать ИЛИ Таблица.Поле = &Поле) часто блокирует использование индексов по полю отбора. СУБД вынуждена сканировать всю таблицу. Динамическая генерация текста запроса (когда условие либо есть, либо отсутствует вовсе) работает в разы быстрее.