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

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

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

Причина ошибки: приоритет логических операторов

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

  1. Оператор НЕ (наивысший приоритет)
  2. Оператор И (логическое умножение)
  3. Оператор ИЛИ (логическое сложение, наименьший приоритет)

Рассмотрим исходный фрагмент кода, в котором возникла проблема:


ГДЕ
    (Контрагент.Ссылка В ИЕРАРХИИ (&ВыбДилер)
            ИЛИ &ВыбДилер = ЗНАЧЕНИЕ(Справочник.Контрагент.ПустаяСсылка)
                И (Контрагент.Город В ИЕРАРХИИ (&ВыбГород)
                    ИЛИ &ВыбГород = ЗНАЧЕНИЕ(Справочник.Города.ПустаяСсылка))
                И (Контрагент.Ответственный В ИЕРАРХИИ (&ВыбОтветственный)
                    ИЛИ &ВыбОтветственный = ЗНАЧЕНИЕ(Справочник.Контрагент.ПустаяСсылка)))

Из-за неправильной расстановки внешних скобок и высокого приоритета оператора И СУБД воспринимает выражение как два больших блока, соединенных оператором ИЛИ:

Блок 1: Контрагент.Ссылка В ИЕРАРХИИ (&ВыбДилер)

ИЛИ

Блок 2: &ВыбДилер = ПустаяСсылка И (УсловиеПоГороду) И (УсловиеПоОтветственному)

В результате, если выбран дилер (параметр &ВыбДилер заполнен), первая часть условия возвращает ИСТИНА для всех элементов группы дилера, а вся правая часть после ИЛИ полностью игнорируется. Фильтры по городу и ответственному просто не применяются.

Решение 1: Корректная расстановка скобок

Чтобы каждое условие проверялось независимо и соединялось с остальными через логическое И, необходимо явно сгруппировать выражения внутри скобок. Каждая группа должна содержать пару: «проверка значения ИЛИ проверка на пустую ссылку».

Разберем исправленный вариант текста запроса:


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

В таком виде запрос работает корректно при любых сочетаниях заполненных и незаполненных параметров.

Решение 2: Динамическое формирование текста запроса

Использование конструкций вида &Параметр = ЗНАЧЕНИЕ(...) в секции ГДЕ может существенно снижать производительность запроса на больших объемах данных. СУБД сложно построить оптимальный план выполнения с использованием индексов, когда в условии присутствует проверка параметров через ИЛИ.

Более предпочтительный подход — подставлять условия в текст запроса программно только тогда, когда соответствующий параметр реально заполнен.

Посмотрим на пример с подстановкой подстрок-маркеров:


ТекстЗапроса = 
    "ВЫБРАТЬ
    |	Контрагент.Ссылка КАК Дилер,
    |	Контрагент.ЭлПочта КАК ЭлПочта,
    |	Контрагент.Город,
    |	Контрагент.Ответственный
    |ИЗ
    |	Справочник.Контрагенты КАК Контрагент
    |ГДЕ
    |	&УсловиеДилер
    |	И &УсловиеГород
    |	И &УсловиеОтветственный";

ТекстДилер = ?(ЗначениеЗаполнено(ВыбДилер), "Контрагент.Ссылка В ИЕРАРХИИ (&ВыбДилер)", "ИСТИНА");
ТекстГород = ?(ЗначениеЗаполнено(ВыбГород), "Контрагент.Город В ИЕРАРХИИ (&ВыбГород)", "ИСТИНА");
ТекстОтветственный = ?(ЗначениеЗаполнено(ВыбОтветственный), "Контрагент.Ответственный В ИЕРАРХИИ (&ВыбОтветственный)", "ИСТИНА");

ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&УсловиеДилер", ТекстДилер);
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&УсловиеГород", ТекстГород);
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&УсловиеОтветственный", ТекстОтветственный);

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ВыбДилер", ВыбДилер);
Запрос.УстановитьПараметр("ВыбГород", ВыбГород);
Запрос.УстановитьПараметр("ВыбОтветственный", ВыбОтветственный);

При таком подходе в итоговый запрос попадают либо реальные условия фильтрации, либо выражение ИСТИНА, которое не ломает логику и легко оптимизируется платформами 1С и СУБД.

Решение 3: Использование Системы компоновки данных (СКД)

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


ВЫБРАТЬ
	Контрагент.Ссылка КАК Дилер,
	Контрагент.ЭлПочта КАК ЭлПочта,
	Контрагент.Город,
	Контрагент.Ответственный
ИЗ
	Справочник.Контрагенты КАК Контрагент
{ГДЕ
	Контрагент.Ссылка В ИЕРАРХИИ (&ВыбДилер),
	Контрагент.Город В ИЕРАРХИИ (&ВыбГород),
	Контрагент.Ответственный В ИЕРАРХИИ (&ВыбОтветственный)}

Если пользователь или программный код не задает значение параметра, СКД автоматически исключает неиспользуемое условие из итогового SQL-запроса, что предотвращает ошибки логики и обеспечивает максимальное быстродействие.

← На главную