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