При разработке запросов в системе 1С, мы часто сталкиваемся с ситуацией, когда некоторые параметры для отбора данных являются необязательными. Например, пользователь может указать начальную и конечную дату периода, но если он этого не сделает, мы должны получить все данные без учета этих дат. Эта задача, на первый взгляд, может показаться простой, но имеет свои нюансы, касающиеся производительности и корректности выполнения запроса. Давайте подробно рассмотрим, как реализовать такую логику в запросах 1С, избегая распространенных ошибок и применяя лучшие практики.
Наш основной вопрос заключается в том, как построить условие в секции ГДЕ запроса таким образом, чтобы реквизит (например, дата) проверялся только в том случае, если соответствующий параметр запроса заполнен. Если параметр не указан (например, имеет значение пустой даты), условие должно быть проигнорировано.
Прежде чем перейти к решениям, давайте определимся, что такое "пустая" дата в контексте 1С. В системе 1С пустая дата соответствует значению 01.01.0001 00:00:00. В запросах мы можем обозначить ее с помощью литерала ДАТАВРЕМЯ(1,1,1). Этот способ общепринятый и широко используется. Однако, более гибким и читаемым подходом является передача пустой даты через параметр запроса, например, &ПустаяДата, куда мы можем передать значение ДАТАВРЕМЯ(1,1,1) или ЗНАЧЕНИЕ(ДатаВремя.ПустаяДата). Передача через параметр позволяет СУБД лучше оптимизировать план выполнения запроса, так как значение формируется один раз, а не при каждом сравнении в запросе.
Самый распространенный и часто рекомендуемый способ обработки необязательных параметров в запросах 1С — это использование логического оператора ИЛИ. Мы можем построить условие таким образом, чтобы оно проверяло либо соответствие поля заданному значению, либо "отключалось", если параметр равен пустой дате.
Рассмотрим пример из нашего обсуждения. Изначально стояла задача отфильтровать записи справочника АдрПомещения_ФизЛица по полям ДатаНачала и ДатаОкончание, которые могут быть не заполнены. Если они не заполнены, проверять их не нужно.
Вот как мы можем адаптировать запрос, чтобы обрабатывать необязательные даты:
ВЫБРАТЬ
АдрПомещения_ФизЛица.ФизическоеЛицо КАК ФизическоеЛицо,
АдрПомещения_ФизЛица.ДатаНачала,
АдрПомещения_ФизЛица.ДатаОкончание
ИЗ
Справочник.АдрПомещения_ФизЛица КАК АдрПомещения_ФизЛица
ГДЕ
(АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца ИЛИ &ДатаЗапросаНачалоМесяца = ДАТАВРЕМЯ(1,1,1))
И (АдрПомещения_ФизЛица.ДатаОкончание >= &ДатаЗапросаКонецМесяца ИЛИ &ДатаЗапросаКонецМесяца = ДАТАВРЕМЯ(1,1,1))
Давайте проанализируем это условие. Например, для ДатаНачала:
(АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца ИЛИ &ДатаЗапросаНачалоМесяца = ДАТАВРЕМЯ(1,1,1))
&ДатаЗапросаНачалоМесяца заполнен (т.е. не равен ДАТАВРЕМЯ(1,1,1)), то вторая часть условия &ДатаЗапросаНачалоМесяца = ДАТАВРЕМЯ(1,1,1) будет ложной. Вся проверка сведется к АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца. Таким образом, отбор будет выполнен по заданной дате.&ДатаЗапросаНачалоМесяца не заполнен (т.е. равен ДАТАВРЕМЯ(1,1,1)), то вторая часть условия &ДатаЗапросаНачалоМесяца = ДАТАВРЕМЯ(1,1,1) будет истинной. Поскольку мы используем ИЛИ, все условие (АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца ИЛИ ИСТИНА) будет истинным, независимо от значения АдрПомещения_ФизЛица.ДатаНачала. Это фактически "отключает" фильтрацию по данному полю.Это очень эффективный и понятный способ реализации необязательных условий. Важно обернуть каждый такой блок условий в скобки, чтобы избежать проблем с приоритетом операторов И и ИЛИ.
Как мы уже упоминали, вместо прямого использования ДАТАВРЕМЯ(1,1,1) в условии запроса, мы можем передать пустую дату через параметр. Это повышает читаемость кода и может немного улучшить производительность, так как система избегает повторного формирования даты при каждом выполнении запроса. Например:
Перем ПустаяДатаДляЗапроса;
ПустаяДатаДляЗапроса = ДАТАВРЕМЯ(1,1,1); // Или ЗНАЧЕНИЕ(ДатаВремя.ПустаяДата)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| АдрПомещения_ФизЛица.ФизическоеЛицо КАК ФизическоеЛицо
|ИЗ
| Справочник.АдрПомещения_ФизЛица КАК АдрПомещения_ФизЛица
|ГДЕ
| (АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца ИЛИ &ДатаЗапросаНачалоМесяца = &ПустаяДата)";
Запрос.УстановитьПараметр("ДатаЗапросаНачалоМесяца", НачальнаяДата); // Передаем фактическое значение или ДАТАВРЕМЯ(1,1,1)
Запрос.УстановитьПараметр("ПустаяДата", ПустаяДатаДляЗапроса);
В этом случае, в параметре &ДатаЗапросаНачалоМесяца мы будем передавать либо заполненную дату, либо ДАТАВРЕМЯ(1,1,1), если поле не заполнено.
Если мы работаем с отчетами или формами, построенными на СКД, то Система Компоновки Данных предоставляет гораздо более элегантный и мощный механизм для обработки необязательных параметров. Мы можем поместить условие в фигурные скобки {} в тексте запроса, что является расширением языка запросов для СКД.
В СКД, если параметр, используемый в условии, заключенном в фигурные скобки, не заполнен (т.е. для него не задано значение или не установлен флаг "Использование" в настройках СКД), то СКД автоматически "выкусывает" соответствующий кусок условия из окончательного текста запроса, который будет выполнен СУБД. Это не только упрощает написание запросов, но и положительно сказывается на производительности, так как СУБД получает максимально оптимизированный текст запроса без лишних проверок.
Предположим, у нас есть поле ДатаНачала и параметр &ПараметрДатаНачала:
ВЫБРАТЬ
АдрПомещения_ФизЛица.ФизическоеЛицо КАК ФизическоеЛицо,
АдрПомещения_ФизЛица.ДатаНачала,
АдрПомещения_ФизЛица.ДатаОкончание
ИЗ
Справочник.АдрПомещения_ФизЛица КАК АдрПомещения_ФизЛица
ГДЕ
{((АдрПомещения_ФизЛица.ДатаНачала >= &ПараметрДатаНачала))}
И {((АдрПомещения_ФизЛица.ДатаОкончание <= &ПараметрДатаОкончания))}
В этом примере:
&ПараметрДатаНачала, то условие АдрПомещения_ФизЛица.ДатаНачала >= &ПараметрДатаНачала будет включено в запрос.&ПараметрДатаНачала (или снимет флаг "Использование"), то весь фрагмент ((АдрПомещения_ФизЛица.ДатаНачала >= &ПараметрДатаНачала)) будет удален из запроса, и отбора по начальной дате не будет.Этот подход очень гибкий и позволяет создавать мощные, динамические фильтры без необходимости изменять текст запроса программно. При настройке параметров в СКД, мы определяем, является ли параметр "жестким" (обязательным) или необязательным. Если для обязательного параметра не задано значение, СКД выдаст ошибку.
Теоретически, мы могли бы формировать текст запроса программно, добавляя условия ГДЕ только в том случае, если соответствующие параметры заполнены. Например:
ТекстЗапроса =
"ВЫБРАТЬ
| АдрПомещения_ФизЛица.ФизическоеЛицо КАК ФизическоеЛицо
|ИЗ
| Справочник.АдрПомещения_ФизЛица КАК АдрПомещения_ФизЛица";
Условия = Новый Массив;
Если НачальнаяДата <> ДАТАВРЕМЯ(1,1,1) Тогда
Условия.Добавить("АдрПомещения_ФизЛица.ДатаНачала >= &НачальнаяДата");
КонецЕсли;
Если КонечнаяДата <> ДАТАВРЕМЯ(1,1,1) Тогда
Условия.Добавить("АдрПомещения_ФизЛица.ДатаОкончание <= &КонечнаяДата");
КонецЕсли;
Если Условия.Количество() > 0 Тогда
ТекстЗапроса = ТекстЗапроса + " ГДЕ " + СтрСоединить(Условия, " И ");
КонецЕсли;
Запрос = Новый Запрос(ТекстЗапроса);
Если НачальнаяДата <> ДАТАВРЕМЯ(1,1,1) Тогда
Запрос.УстановитьПараметр("НачальнаяДата", НачальнаяДата);
КонецЕсли;
// ... и так далее для других параметров
Мы настоятельно рекомендуем избегать динамической сборки текста запроса, когда это возможно. Это существенно затрудняет анализ кода, отладку и поддержку. При изменении логики или структуры данных приходится переписывать сложную цепочку формирования строки запроса. Рекомендации 1С также часто указывают на нежелательность такого подхода.
В процессе обсуждения часто возникает вопрос об использовании конструкции ВЫБОР КОГДА в секции ГДЕ для обработки необязательных параметров. Некоторые разработчики считают это более наглядным или даже более производительным подходом:
ГДЕ ВЫБОР
КОГДА &ДатаЗапросаНачалоМесяца = ДАТАВРЕМЯ(1,1,1) ТОГДА ИСТИНА
ИНАЧЕ АдрПомещения_ФизЛица.ДатаНачала = &ДатаЗапросаНачалоМесяца
КОНЕЦ
Однако, по общему мнению опытных разработчиков и согласно некоторым рекомендациям, использование ВЫБОР КОГДА в условии ГДЕ может быть менее эффективным, чем "прямое" сравнение с ИЛИ, особенно на больших таблицах. Почему?
ВЫБОР КОГДА может помешать оптимизатору СУБД эффективно использовать индексы. Оптимизатор может решить, что условие слишком сложное, и вместо использования индекса для быстрого поиска данных, будет выполнять полное сканирование таблицы.ВЫБОР КОГДА, он может выбрать неоптимальный план выполнения запроса, что приведет к увеличению времени выполнения.ВЫБОР КОГДА может вычисляться для каждой строки таблицы, тогда как в случае с (Поле = &Параметр ИЛИ &Параметр = ДАТАВРЕМЯ(1,1,1)), вторая часть условия &Параметр = ДАТАВРЕМЯ(1,1,1) вычисляется всего один раз.Тем не менее, важно отметить, что современные СУБД и оптимизаторы запросов становятся все более интеллектуальными. В некоторых случаях, особенно если условие ВЫБОР КОГДА зависит от внешнего параметра и вычисляется один раз (как в нашем примере), а не для каждой строки, разницы в производительности может и не быть. Однако, чтобы обеспечить максимальную производительность и избежать потенциальных "ловушек" оптимизатора, рекомендуется предпочитать конструкцию с ИЛИ.
Если все же необходимо использовать ВЫБОР КОГДА в ГДЕ, старайтесь помещать его после основного условия, которое максимально ограничивает объем выборки и использует индексы. Основное условие должно быть объединено по И.
Помимо рассмотренных способов обработки необязательных параметров, мы предлагаем обратить внимание на общие принципы оптимизации запросов, которые помогут обеспечить высокую производительность в любых условиях:
ГДЕ, ПО и виртуальных таблицах, имеются подходящие индексы. Индекс должен содержать все поля, перечисленные в условии, находиться в самом начале индекса и идти подряд. Отсутствие или неправильное использование индексов является одной из самых частых причин медленной работы запросов.ПО, так как это может значительно замедлить выполнение запроса. Вместо этого, мы рекомендуем использовать временные таблицы, предварительно проиндексировав их по полям соединения.ГОД(Дата) = &Год) в условиях ГДЕ могут препятствовать использованию индексов, так как для каждой строки придется вычислять функцию перед сравнением. Мы стараемся преобразовывать значение параметра, а не поле таблицы (например, Дата МЕЖДУ НАЧАЛОПЕРИОДА(&Год, ГОД) И КОНЕЦПЕРИОДА(&Год, ГОД)).ВЫРАЗИТЬ для ускорения выполнения запроса, особенно при сравнениях или соединениях. Например: ВЫРАЗИТЬ(Ссылка КАК Справочник.Номенклатура).Мы рассмотрели различные подходы к обработке необязательных реквизитов в запросах 1С. Наиболее универсальным и производительным способом для программного формирования запросов является использование логического оператора ИЛИ в условии ГДЕ, а также передача пустых значений через параметры. Для отчетов и форм на базе СКД, мы однозначно рекомендуем применять механизм фигурных скобок {}, так как он обеспечивает максимальную гибкость и оптимальность без лишних усилий. Динамическую сборку текста запроса мы стараемся избегать из-за сложности поддержки и анализа. Помните, что постоянный анализ планов запросов и мониторинг производительности — ключ к созданию быстрых и надежных решений в 1С.