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

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов и обработок на платформе 1С:Предприятие 8 программисты часто сталкиваются с задачей фильтрации данных по необязательным реквизитам. Например, пользователь может выбрать конкретного контрагента, а может оставить поле пустым, чтобы увидеть данные по всем клиентам сразу. Если просто передать пустую ссылку в параметр, запрос вернет только те записи, где поле действительно не заполнено, что не соответствует логике "выбрать всё". В этой статье мы подробно разберем, как реализовать гибкие фильтры, рассмотрим влияние различных подходов на производительность и выясним, почему "красивый" код не всегда является "правильным".

Проблема пустых параметров в языке запросов

Рассмотрим стандартную ситуацию: у нас есть запрос к табличной части документа ЗаказПокупателя. Мы хотим отобрать строки по Контрагенту и Номенклатуре. Если мы напишем условие напрямую:


ГДЕ
    ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент
    И ЗаказПокупателяТовары.Номенклатура = &Товар

То при передаче в &Клиент значения Справочники.Контрагенты.ПустаяСсылка(), система будет искать именно пустые значения в документах. Наша же задача — проигнорировать фильтр, если параметр не задан. Разберем основные способы решения этой задачи.

Способ 1: Использование логических условий внутри запроса

Самый простой путь — добавить проверку параметра прямо в текст запроса. Это позволяет сохранить статичность текста и не прибегать к программной склейке строк. Рассмотрим вариант с использованием оператора ВЫБОР:


ГДЕ
    ВЫБОР 
        КОГДА &Клиент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) ТОГДА ИСТИНА 
        ИНАЧЕ ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент 
    КОНЕЦ
    И ВЫБОР 
        КОГДА &Товар = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) ТОГДА ИСТИНА 
        ИНАЧЕ ЗаказПокупателяТовары.Номенклатура = &Товар 
    КОНЕЦ

Существует и более компактная запись через оператор В, которую часто применяют для краткости:


ГДЕ
    &Клиент В (ЗаказПокупателяТовары.Ссылка.Контрагент, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка))
    И &Товар В (ЗаказПокупателяТовары.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка))

Важное замечание: Несмотря на удобство, этот метод имеет серьезный недостаток — производительность. Проанализируем ситуацию с точки зрения СУБД. Использование конструкций ВЫБОР или сложных логических условий в секции ГДЕ мешает оптимизатору SQL-сервера эффективно использовать индексы. Это приводит к так называемому Index Scan (полному сканированию индекса) вместо Index Seek (точечного поиска). На таблицах с миллионами записей такой запрос может выполняться крайне медленно.

Способ 2: Программное формирование текста запроса

Этот метод считается наиболее профессиональным и эффективным. Мы проверяем заполненность параметров в коде на встроенном языке и добавляем условия в текст запроса только тогда, когда это необходимо. Рассмотрим пример реализации:


ТекстУсловия = "";
Если ЗначениеЗаполнено(Клиент) Тогда
    ТекстУсловия = ТекстУсловия + " И ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент";
КонецЕсли;

Если ЗначениеЗаполнено(Товар) Тогда
    ТекстУсловия = ТекстУсловия + " И ЗаказПокупателяТовары.Номенклатура = &Товар";
КонецЕсли;

Запрос.Текст = 
    "ВЫБРАТЬ
    |   ЗаказПокупателяТовары.Ссылка КАК Заказ,
    |   ЗаказПокупателяТовары.Номенклатура
    |ИЗ
    |   Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
    |ГДЕ
    |   ЗаказПокупателяТовары.Ссылка.Проведен = ИСТИНА" + ТекстУсловия;

Если ЗначениеЗаполнено(Клиент) Тогда
    Запрос.УстановитьПараметр("Клиент", Клиент);
КонецЕсли;
// Аналогично для товара...

Преимущества этого подхода:

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

Для еще более удобной работы можно использовать конструкцию ГДЕ ИСТИНА, чтобы не следить за тем, является ли добавляемое условие первым (нужно ли писать И):


Запрос.Текст = "ВЫБРАТЬ ... ИЗ ... ГДЕ ИСТИНА " 
    + ?(ЗначениеЗаполнено(Клиент), " И Контрагент = &Клиент", "");

Особенности работы с составными типами и оператором ССЫЛКА

Иногда задача усложняется: нужно сделать отбор в поле составного типа (например, ДокументОснование), ограничив выборку только определенными типами документов. Проанализируем типичную ошибку при использовании оператора ССЫЛКА:


// ОШИБКА: Условие никогда не выполнится, если типы взаимоисключающие через И
ГДЕ 
    ДокументОснование ССЫЛКА Документ.ЗаказПоставщику
    И ДокументОснование ССЫЛКА Документ.ВозвратТоваровПоставщику

Если вам нужно выбрать данные по нескольким типам, следует использовать логическое ИЛИ, а еще лучше — ОБЪЕДИНИТЬ ВСЕ. При использовании ОБЪЕДИНИТЬ запросы по каждому типу документа будут выполняться независимо, что позволяет СУБД использовать индексы каждой конкретной таблицы.

Также обратим внимание на избыточность конструкции Поле.Ссылка ССЫЛКА Тип. Поскольку Поле уже содержит ссылку, обращение к .Ссылка порождает лишнее соединение с таблицей. Правильно писать просто: ДокументОснование ССЫЛКА Документ.ЗаказПоставщику.

Советы по оптимизации и культуре кода

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

Рассмотренные нами подходы позволяют найти баланс между удобством разработки и скоростью работы системы. Для небольших баз данных и простых инструментов вполне допустимы условия ВЫБОР КОГДА, но для высоконагруженных систем динамическая сборка текста запроса или использование СКД являются обязательным стандартом качества.

← На главную