При написании сложных запросов в системе 1С:Предприятие разработчики часто пытаются перенести сложную бизнес-логику непосредственно в условие соединения таблиц. Одним из таких приемов является использование конструкции ВЫБОР КОГДА ... ТОГДА ... КОНЕЦ внутри секции ПО при выполнении ЛЕВОГО СОЕДИНЕНИЯ. Однако на практике это часто приводит к тому, что часть условий (особенно связанных через ИЛИ) перестает отрабатывать или возвращает неверные результаты. Рассмотрим, почему это происходит и как правильно проектировать такие запросы.
Представим ситуацию, когда нам необходимо присоединить таблицу спецификаций к заказам на производство. Условие присоединения зависит от того, заполнено ли поле оформления в заказе. Разберем пример кода, который вызывает затруднения:
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
И (ВЫБОР
КОГДА ЗаказыНаПроизводство.Оформление <> &Строк
ТОГДА СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление
ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сырье)
ИНАЧЕ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сборочная)
КОНЕЦ)
В данном случае разработчик ожидает, что если условие в КОГДА выполняется, то система проверит сложное условие СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление ИЛИ СпецификацииНоменклатуры.ВидСпецификации = Сырье. Однако на практике «вторая часть» условия после ИЛИ может игнорироваться. Это связано с тем, как механизм запросов 1С транслирует конструкцию ВЫБОР в SQL-код.
Проанализируем технические причины, по которым данный подход работает нестабильно:
CASE (аналог ВЫБОР) должен быть скалярным значением (числом, строкой, датой), а не логическим выражением. Когда мы пишем логику внутри ТОГДА, 1С пытается преобразовать это в некую форму булевого сравнения, которая может интерпретироваться СУБД неоднозначно.ИЛИ уже определен как Истина, вторая часть может даже не вычисляться. В сложных вложенных конструкциях внутри JOIN это приводит к тому, что записи, которые должны были попасть в выборку по второму условию, отсекаются.ПО, содержащие функции или конструкции ВЫБОР, называются Non-SARGable (Search Argumentable). Это означает, что СУБД не может эффективно использовать индексы для выполнения этого соединения. Вместо быстрого поиска (Index Seek) система будет вынуждена сканировать всю таблицу (Table Scan), что критически замедлит работу базы на больших объемах данных.ЛЕВОМ СОЕДИНЕНИИ, если запись не найдена, поля присоединяемой таблицы принимают значение NULL. Любое сравнение с NULL (кроме специального ЕСТЬ NULL) дает результат Ложь, что в сложных цепочках ВЫБОР...ИЛИ может привести к непредсказуемому выпадению строк из результата.Самый надежный и правильный способ решить проблему — отказаться от ВЫБОР в пользу классических операторов И, ИЛИ и скобок. Это делает запрос прозрачным для оптимизатора СУБД и гарантирует корректную работу всех условий.
Рассмотрим, как преобразовать наше условие. Общая схема выглядит так: (Условие_1 И Логика_1) ИЛИ (НЕ Условие_1 И Логика_2). Перепишем наш запрос:
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
И (
(ЗаказыНаПроизводство.Оформление <> &Строк
И (СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление
ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сырье)))
ИЛИ
(ЗаказыНаПроизводство.Оформление = &Строк
И СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сборочная))
)
Преимущества этого подхода:
Номенклатура и Оформление.Если логика соединения становится слишком тяжелой и первый вариант продолжает работать медленно, проанализируем возможность разделения запроса на части. Вместо одного сложного соединения мы можем сделать два простых и объединить их результаты.
Посмотрим на алгоритм:
ЗаказыНаПроизводство.Оформление <> &Строк. Здесь мы используем простое условие соединения для этого конкретного случая.ЗаказыНаПроизводство.Оформление = &Строк.ОБЪЕДИНИТЬ ВСЕ.
ВЫБРАТЬ ...
ИЗ ЗаказыНаПроизводство КАК ЗаказыНаПроизводство
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
ГДЕ ЗаказыНаПроизводство.Оформление = &Строк
И СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сборочная)
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ...
ИЗ ЗаказыНаПроизводство КАК ЗаказыНаПроизводство
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
ГДЕ ЗаказыНаПроизводство.Оформление <> &Строк
И (СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление
ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпезификаций.Сырье))
Такой подход часто оказывается быстрее, так как СУБД строит два элементарных плана выполнения вместо одного перегруженного. Это особенно актуально для высоконагруженных систем (ERP, КА, УТ 11).
Если вы столкнулись с тем, что соединение не отрабатывает, выполните следующие шаги:
Перечисление с Строкой или NULL всегда будет ложным.ЛЕВОЕ СОЕДИНЕНИЕ на ВНУТРЕННЕЕ СОЕДИНЕНИЕ временно — это поможет понять, вообще ли удовлетворяют данные условию ПО.И выше, чем у ИЛИ. Всегда используйте скобки для группировки условий, чтобы избежать логических ошибок.Подведем итог: использование ВЫБОР внутри ПО — это антипаттерн, которого следует избегать. Переход к чистой булевой логике или разделение запроса через ОБЪЕДИНИТЬ ВСЕ сделает ваш код надежным, быстрым и легко читаемым.