Как в запросе 1С не учитывать незаполненные параметры?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в 1С часто возникает задача сделать фильтры необязательными: если пользователь указал значение отбора на форме, запрос должен отфильтровать данные, а если значение не заполнено — условие должно игнорироваться. Рассмотрим детально все подходы к решению этой задачи, разберем их плюсы, минусы и влияние на производительность базы данных.

Способ 1. Динамическое формирование текста запроса через массив условий (рекомендуемый)

Наиболее надежный и производительный подход в коде 1С — это динамическое добавление строк отбора в текст запроса только для тех параметров, которые были фактически заполнены. Чтобы не усложнять код проверками расстановки логического союза И, воспользуемся массивом условий и функцией СтрСоединить.

Разберем по шагам, как реализовать этот алгоритм:

  1. Создадим базовый текст запроса без блока ГДЕ.
  2. Создадим пустой массив для накопления условий.
  3. Для каждого реквизита выполним проверку через ЗначениеЗаполнено(). Если значение передано, добавим строку условия в массив и передадим параметр в запрос.
  4. Объединим элементы массива через разделитель " И " и добавим полученную секцию ГДЕ к тексту запроса.

Посмотрим на пример реализации на встроенном языке 1С:


Запрос = Новый Запрос;

МассивУсловий = Новый Массив;

Если ЗначениеЗаполнено(Машина) Тогда
    МассивУсловий.Добавить("Шапка.Машина = &Машина");
    Запрос.УстановитьПараметр("Машина", Машина);
КонецЕсли;

Если ЗначениеЗаполнено(Водитель1) Тогда
    МассивУсловий.Добавить("Шапка.Водитель1 = &Водитель1");
    Запрос.УстановитьПараметр("Водитель1", Водитель1);
КонецЕсли;

Если ЗначениеЗаполнено(Выезд) Тогда
    МассивУсловий.Добавить("Шапка.Выезд >= &Выезд");
    Запрос.УстановитьПараметр("Выезд", Выезд);
КонецЕсли;

Если ЗначениеЗаполнено(Возврат) Тогда
    МассивУсловий.Добавить("Шапка.Возврат <= &Возврат");
    Запрос.УстановитьПараметр("Возврат", Возврат);
КонецЕсли;

ТекстГДЕ = "";
Если МассивУсловий.Количество() > 0 Тогда
    ТекстГДЕ = "ГДЕ " + СтрСоединить(МассивУсловий, " И ");
КонецЕсли;

Запрос.Текст = 
"ВЫБРАТЬ
|   ПутевойЛист.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
|   Документ.ПутевойЛист КАК ПутевойЛист
|" + ТекстГДЕ + ";
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|   ВТ_Документы.Ссылка КАК Ссылка,
|   ПутевойЛистТЧ.ФактЛитры КАК ФактЛитры
|ИЗ
|   ВТ_Документы КАК ВТ_Документы
|   ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПутевойЛист.ТЧ КАК ПутевойЛистТЧ
|       ПО ВТ_Документы.Ссылка = ПутевойЛистТЧ.Ссылка";

Результат = Запрос.Выполнить();

Преимущества: СУБД получает чистый SQL-запрос, в котором присутствуют только актуальные фильтры, благодаря чему оптимизатор всегда строит максимально эффективный план выполнения с использованием индексов.

Способ 2. Использование условий с ИЛИ или ВЫБОР внутри запроса

Иногда разработчики пытаются избежать динамической сборки текста и прописывают логику прямо в теле запроса, передавая пустые значения или значение Неопределено:


ГДЕ
    (Шапка.Машина = &Машина ИЛИ &Машина = ЗНАЧЕНИЕ(Справочник.Машины.ПустаяСсылка))
    И (Шапка.Выезд >= &Выезд ИЛИ &Выезд = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0))

Либо через конструкцию ВЫБОР:


ГДЕ
    ВЫБОР 
        КОГДА &Машина = Неопределено ТОГДА ИСТИНА 
        ИНАЧЕ Шапка.Машина = &Машина 
    КОНЕЦ

Проанализируем, почему такой способ не рекомендуется использовать в высоконагруженных системах:

  1. Неиспользование индексов: Наличие условий с логическим ИЛИ между полем таблицы и параметром часто приводит к тому, что СУБД не может применить индекс по полю и выполняет полное сканирование таблицы (Index Scan или Table Scan).
  2. Проблема кэширования планов (Parameter Sniffing): План выполнения запроса компилируется СУБД под первое переданное значение. Если первый раз запрос вызвался с пустым параметром, СУБД выберет сканирование таблицы, и последующие вызовы с заполненным параметром будут выполняться неэффективно.

Способ 3. Использование Схемы компоновки данных (СКД) или Построителя запроса

Если задача решается в отчете или динамическом списке, стандартным механизмом является использование фигурных скобок {}:


ВЫБРАТЬ
    ПутевойЛист.Ссылка КАК Ссылка
ИЗ
    Документ.ПутевойЛист КАК ПутевойЛист
{ГДЕ
    ПутевойЛист.Машина.* КАК Машина,
    ПутевойЛист.Выезд >= &Выезд}

В этом случае платформа 1С самостоятельно модифицирует текст SQL-запроса перед отправкой в СУБД: условия, для которых пользователь не включил флажок отбора или не заполнил значение, полностью удаляются из итогового текста.

Оптимизация: правильная работа с табличными частями и шапкой

Обратим внимание на частую архитектурную ошибку в исходных запросах: фильтрацию по полям документа через разыменование ссылки из табличной части (например, ПутевойЛистТЧ.Ссылка.Машина = &Машина).

Разыменование ссылки внутри таблицы табличной части порождает неявное соединение (LEFT JOIN) всей таблицы строк со всей таблицей документов. Правильный подход состоит в разделении выборки:

  1. Сначала отбираются ссылки на документы из таблицы шапки Документ.ПутевойЛист с наложением всех фильтров.
  2. Отобранные ссылки помещаются во временную таблицу.
  3. Временная таблица соединяется с табличной частью документа через ВНУТРЕННЕЕ СОЕДИНЕНИЕ по полю Ссылка.

Такой подход гарантирует минимальное количество чтений с диска и максимальное быстродействие запроса.

← На главную