Как в запросе 1С выбрать все записи или отфильтровать по значению реквизита

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

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

Метод 1. Использование логического условия «ИЛИ» непосредственно в запросе

Рассмотрим самый простой и очевидный вариант, который часто выбирают начинающие разработчики. Суть заключается в добавлении в секцию ГДЕ условия, которое проверяет либо равенство реквизита параметру, либо пустоту самого параметра. Проанализируем пример кода:


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

Преимущества: Этот метод очень прост в реализации. Текст запроса остается статичным, его легко читать и редактировать в конструкторе запросов.

Недостатки: Главный минус — катастрофическое падение производительности на больших объемах данных. Использование конструкции ИЛИ в условии ГДЕ делает запрос «не-SARGable» (от англ. Search Argumentable). Это означает, что оптимизатор SQL-сервера, скорее всего, не сможет эффективно использовать индексы. Вместо быстрого поиска (Index Seek) система будет вынуждена сканировать всю таблицу (Table Scan или Index Scan). На регистрах с миллионами записей время выполнения может вырасти с нескольких секунд до десятков минут.

Метод 2. Использование дополнительного флага-параметра

Чтобы сделать логику более явной, можно передать в запрос отдельный булев параметр, который будет сигнализировать о необходимости фильтрации. Рассмотрим реализацию:


// В коде 1С:
Запрос.УстановитьПараметр("ОтбиратьТовар", ЗначениеЗаполнено(ВыбТовар));
Запрос.УстановитьПараметр("ВыбТовар", ВыбТовар);

// В тексте запроса:
ГДЕ 
    НЕ &ОтбиратьТовар 
    ИЛИ Продажи.Номенклатура = &ВыбТовар

Этот способ немногим лучше первого с точки зрения производительности, так как проблема с использованием индексов из-за ИЛИ сохраняется. Однако он делает код более читаемым для других разработчиков, явно показывая намерение автора.

Метод 3. Динамическое формирование текста запроса

Проанализируем наиболее эффективный подход с точки зрения быстродействия. Если мы хотим, чтобы SQL-сервер работал максимально быстро, мы должны передавать ему максимально конкретный запрос. Если фильтр не нужен — условия ГДЕ вообще не должно быть в итоговом SQL-запросе.

Разберем, как это реализовать с помощью программной сборки текста:


ТекстУсловия = "";
Если ЗначениеЗаполнено(ВыбТовар) Тогда
    ТекстУсловия = "ГДЕ Продажи.Номенклатура = &ВыбТовар";
    Запрос.УстановитьПараметр("ВыбТовар", ВыбТовар);
КонецЕсли;

Запрос.Текст = 
    "ВЫБРАТЬ
    |    Продажи.Номенклатура КАК Номенклатура,
    |    СУММА(Продажи.Сумма) КАК Сумма
    |ИЗ
    |    РегистрНакопления.Продажи КАК Продажи
    |" + ТекстУсловия + "
    |СГРУППИРОВАТЬ ПО
    |    Продажи.Номенклатура";

Почему это важно: При таком подходе SQL-сервер строит оптимальный план выполнения. Если ВыбТовар заполнен, будет использован индекс по номенклатуре. Если не заполнен — сервер просто прочитает данные без лишних проверок. Как отмечают опытные разработчики, разница в скорости на больших регистрах может достигать 15-20 раз.

Метод 4. Использование шаблонов и функции СтрЗаменить

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


ТекстЗапроса = 
    "ВЫБРАТЬ
    |    Продажи.Номенклатура КАК Номенклатура,
    |    СУММА(Продажи.Сумма) КАК Сумма
    |ИЗ
    |    РегистрНакопления.Продажи КАК Продажи
    |ГДЕ
    |    &СтрокаУсловия
    |СГРУППИРОВАТЬ ПО
    |    Продажи.Номенклатура";

Если ЗначениеЗаполнено(ВыбТовар) Тогда
    ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&СтрокаУсловия", "Продажи.Номенклатура = &ВыбТовар");
    Запрос.УстановитьПараметр("ВыбТовар", ВыбТовар);
Иначе
    ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&СтрокаУсловия", "ИСТИНА");
КонецЕсли;

В данном случае, если товар не выбран, условие превращается в ГДЕ ИСТИНА, что практически не влияет на производительность, так как оптимизатор SQL легко «проглатывает» такие константы.

Метод 5. Параметры виртуальных таблиц (Обороты и Остатки)

Выясним важную деталь: если вы работаете с регистрами накопления (как в примере автора темы), то использование физической таблицы — это зачастую ошибка. Правильнее использовать виртуальные таблицы, такие как Продажи.Обороты.

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


// Правильный подход с виртуальной таблицей:
ТекстУсловия = ?(ЗначениеЗаполнено(ВыбТовар), "Номенклатура = &ВыбТовар", "");

Запрос.Текст = 
    "ВЫБРАТЬ
    |    ПродажиОбороты.Номенклатура,
    |    ПродажиОбороты.СуммаОборот
    |ИЗ
    |    РегистрНакопления.Продажи.Обороты(,,," + ТекстУсловия + ") КАК ПродажиОбороты";

Метод 6. Использование Системы Компоновки Данных (СКД)

Проанализируем самый современный способ решения задачи. В СКД механизмы опциональных фильтров реализованы «из коробки». Вам достаточно написать стандартный запрос с условием:


ВЫБРАТЬ
    Продажи.Номенклатура,
    Продажи.Сумма
ИЗ
    РегистрНакопления.Продажи КАК Продажи
ГДЕ
    Продажи.Номенклатура = &ВыбТовар

В настройках параметра СКД достаточно снять галочку «Использовать всегда». Если пользователь не заполнит этот параметр в отчете, СКД автоматически и интеллектуально исключит это условие из финального текста запроса, который уйдет на сервер базы данных. Это избавляет программиста от необходимости вручную писать СтрЗаменить или конкатенировать строки.

Метод 7. Фильтрация через массив (оператор «В»)

Рассмотрим еще один удобный прием — использование оператора В. Вместо передачи одного значения можно всегда передавать массив СписокТоваров.


ГДЕ
    Номенклатура В (&СписокТоваров)

Если нужно выбрать все товары, вы просто не добавляете это условие в текст запроса (динамически), либо передаете в массив все доступные значения (что менее эффективно). Однако чаще всего программно проверяют: Если СписокТоваров.Количество() > 0 Тогда... и далее по тексту добавляют условие.

Подведем итоги по производительности

Рассмотрим, как выбор метода влияет на поведение SQL-сервера:

  1. Вариант с «ИЛИ»: Приводит к сканированию всей таблицы. Огромные затраты ресурсов процессора и дисковой подсистемы (I/O).
  2. Вариант с динамическим текстом: Позволяет серверу БД построить «чистый» план. Происходит Index Seek (поиск по индексу), что выполняется практически мгновенно даже на миллионах строк.
  3. Влияние на кэш планов: Некоторые опасаются, что динамическое изменение текста «замусорит» кэш планов SQL. Однако в данном случае у нас всего два варианта запроса: с фильтром и без. Это абсолютно безопасно и гораздо лучше, чем один универсальный, но медленный план.

Рекомендация: Если вы пишете простой отчет, используйте СКД. Если же вам необходимо выполнить запрос в коде (в модуле объекта или менеджера), всегда отдавайте предпочтение динамическому формированию текста запроса или использованию параметров виртуальных таблиц. Это обеспечит масштабируемость вашего решения и отсутствие «тормозов» при росте базы данных.

← На главную