При разработке отчетов и обработок на платформе 1С:Предприятие 8 программисты часто сталкиваются с задачей фильтрации данных по необязательным реквизитам. Например, пользователь может выбрать конкретного контрагента, а может оставить поле пустым, чтобы увидеть данные по всем клиентам сразу. Если просто передать пустую ссылку в параметр, запрос вернет только те записи, где поле действительно не заполнено, что не соответствует логике "выбрать всё". В этой статье мы подробно разберем, как реализовать гибкие фильтры, рассмотрим влияние различных подходов на производительность и выясним, почему "красивый" код не всегда является "правильным".
Рассмотрим стандартную ситуацию: у нас есть запрос к табличной части документа ЗаказПокупателя. Мы хотим отобрать строки по Контрагенту и Номенклатуре. Если мы напишем условие напрямую:
ГДЕ
ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент
И ЗаказПокупателяТовары.Номенклатура = &Товар
То при передаче в &Клиент значения Справочники.Контрагенты.ПустаяСсылка(), система будет искать именно пустые значения в документах. Наша же задача — проигнорировать фильтр, если параметр не задан. Разберем основные способы решения этой задачи.
Самый простой путь — добавить проверку параметра прямо в текст запроса. Это позволяет сохранить статичность текста и не прибегать к программной склейке строк. Рассмотрим вариант с использованием оператора ВЫБОР:
ГДЕ
ВЫБОР
КОГДА &Клиент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) ТОГДА ИСТИНА
ИНАЧЕ ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент
КОНЕЦ
И ВЫБОР
КОГДА &Товар = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) ТОГДА ИСТИНА
ИНАЧЕ ЗаказПокупателяТовары.Номенклатура = &Товар
КОНЕЦ
Существует и более компактная запись через оператор В, которую часто применяют для краткости:
ГДЕ
&Клиент В (ЗаказПокупателяТовары.Ссылка.Контрагент, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка))
И &Товар В (ЗаказПокупателяТовары.Номенклатура, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка))
Важное замечание: Несмотря на удобство, этот метод имеет серьезный недостаток — производительность. Проанализируем ситуацию с точки зрения СУБД. Использование конструкций ВЫБОР или сложных логических условий в секции ГДЕ мешает оптимизатору SQL-сервера эффективно использовать индексы. Это приводит к так называемому Index Scan (полному сканированию индекса) вместо Index Seek (точечного поиска). На таблицах с миллионами записей такой запрос может выполняться крайне медленно.
Этот метод считается наиболее профессиональным и эффективным. Мы проверяем заполненность параметров в коде на встроенном языке и добавляем условия в текст запроса только тогда, когда это необходимо. Рассмотрим пример реализации:
ТекстУсловия = "";
Если ЗначениеЗаполнено(Клиент) Тогда
ТекстУсловия = ТекстУсловия + " И ЗаказПокупателяТовары.Ссылка.Контрагент = &Клиент";
КонецЕсли;
Если ЗначениеЗаполнено(Товар) Тогда
ТекстУсловия = ТекстУсловия + " И ЗаказПокупателяТовары.Номенклатура = &Товар";
КонецЕсли;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПокупателяТовары.Ссылка КАК Заказ,
| ЗаказПокупателяТовары.Номенклатура
|ИЗ
| Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
|ГДЕ
| ЗаказПокупателяТовары.Ссылка.Проведен = ИСТИНА" + ТекстУсловия;
Если ЗначениеЗаполнено(Клиент) Тогда
Запрос.УстановитьПараметр("Клиент", Клиент);
КонецЕсли;
// Аналогично для товара...
Преимущества этого подхода:
Для еще более удобной работы можно использовать конструкцию ГДЕ ИСТИНА, чтобы не следить за тем, является ли добавляемое условие первым (нужно ли писать И):
Запрос.Текст = "ВЫБРАТЬ ... ИЗ ... ГДЕ ИСТИНА "
+ ?(ЗначениеЗаполнено(Клиент), " И Контрагент = &Клиент", "");
Иногда задача усложняется: нужно сделать отбор в поле составного типа (например, ДокументОснование), ограничив выборку только определенными типами документов. Проанализируем типичную ошибку при использовании оператора ССЫЛКА:
// ОШИБКА: Условие никогда не выполнится, если типы взаимоисключающие через И
ГДЕ
ДокументОснование ССЫЛКА Документ.ЗаказПоставщику
И ДокументОснование ССЫЛКА Документ.ВозвратТоваровПоставщику
Если вам нужно выбрать данные по нескольким типам, следует использовать логическое ИЛИ, а еще лучше — ОБЪЕДИНИТЬ ВСЕ. При использовании ОБЪЕДИНИТЬ запросы по каждому типу документа будут выполняться независимо, что позволяет СУБД использовать индексы каждой конкретной таблицы.
Также обратим внимание на избыточность конструкции Поле.Ссылка ССЫЛКА Тип. Поскольку Поле уже содержит ссылку, обращение к .Ссылка порождает лишнее соединение с таблицей. Правильно писать просто: ДокументОснование ССЫЛКА Документ.ЗаказПоставщику.
Подводя итог обсуждению, выделим несколько правил, которые помогут вам писать надежные и быстрые запросы:
Номенклатура.Ссылка, если можно написать просто Номенклатура. Каждая точка — это потенциальное неявное соединение таблиц.ГДЕ должны быть максимально простыми. Любые функции, ВЫБОР КОГДА или арифметические операции над полями таблиц в условиях поиска убивают производительность.СхемаЗапроса. Это позволяет программно управлять структурой запроса (полями, условиями, порядком) как объектами, что гораздо надежнее простой склейки строк.Рассмотренные нами подходы позволяют найти баланс между удобством разработки и скоростью работы системы. Для небольших баз данных и простых инструментов вполне допустимы условия ВЫБОР КОГДА, но для высоконагруженных систем динамическая сборка текста запроса или использование СКД являются обязательным стандартом качества.