Почему не работает условие ИЛИ внутри конструкции ВЫБОР при левом соединении в запросе 1С?

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

При написании сложных запросов в системе 1С:Предприятие разработчики часто пытаются перенести сложную бизнес-логику непосредственно в условие соединения таблиц. Одним из таких приемов является использование конструкции ВЫБОР КОГДА ... ТОГДА ... КОНЕЦ внутри секции ПО при выполнении ЛЕВОГО СОЕДИНЕНИЯ. Однако на практике это часто приводит к тому, что часть условий (особенно связанных через ИЛИ) перестает отрабатывать или возвращает неверные результаты. Рассмотрим, почему это происходит и как правильно проектировать такие запросы.

Суть проблемы и анализ исходного кода

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


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

В данном случае разработчик ожидает, что если условие в КОГДА выполняется, то система проверит сложное условие СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление ИЛИ СпецификацииНоменклатуры.ВидСпецификации = Сырье. Однако на практике «вторая часть» условия после ИЛИ может игнорироваться. Это связано с тем, как механизм запросов 1С транслирует конструкцию ВЫБОР в SQL-код.

Почему ВЫБОР в условии соединения — это плохая практика?

Проанализируем технические причины, по которым данный подход работает нестабильно:

  1. Особенности трансляции в SQL: В языке SQL результат конструкции CASE (аналог ВЫБОР) должен быть скалярным значением (числом, строкой, датой), а не логическим выражением. Когда мы пишем логику внутри ТОГДА, 1С пытается преобразовать это в некую форму булевого сравнения, которая может интерпретироваться СУБД неоднозначно.
  2. Ленивые вычисления (Short-circuit evaluation): В логических операциях, если результат первой части выражения ИЛИ уже определен как Истина, вторая часть может даже не вычисляться. В сложных вложенных конструкциях внутри JOIN это приводит к тому, что записи, которые должны были попасть в выборку по второму условию, отсекаются.
  3. Проблема SARGability: Условия в секции ПО, содержащие функции или конструкции ВЫБОР, называются Non-SARGable (Search Argumentable). Это означает, что СУБД не может эффективно использовать индексы для выполнения этого соединения. Вместо быстрого поиска (Index Seek) система будет вынуждена сканировать всю таблицу (Table Scan), что критически замедлит работу базы на больших объемах данных.
  4. Обработка NULL: При ЛЕВОМ СОЕДИНЕНИИ, если запись не найдена, поля присоединяемой таблицы принимают значение NULL. Любое сравнение с NULL (кроме специального ЕСТЬ NULL) дает результат Ложь, что в сложных цепочках ВЫБОР...ИЛИ может привести к непредсказуемому выпадению строк из результата.

Решение №1: Преобразование в чистую булеву логику

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

Рассмотрим, как преобразовать наше условие. Общая схема выглядит так: (Условие_1 И Логика_1) ИЛИ (НЕ Условие_1 И Логика_2). Перепишем наш запрос:


ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
И (
    (ЗаказыНаПроизводство.Оформление <> &Строк 
     И (СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление 
        ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сырье)))
    ИЛИ 
    (ЗаказыНаПроизводство.Оформление = &Строк 
     И СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сборочная))
)

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

Решение №2: Использование ОБЪЕДИНИТЬ ВСЕ (UNION ALL)

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

Посмотрим на алгоритм:

  1. В первой части запроса выбираем данные для ЗаказыНаПроизводство.Оформление <> &Строк. Здесь мы используем простое условие соединения для этого конкретного случая.
  2. Во второй части выбираем данные для ЗаказыНаПроизводство.Оформление = &Строк.
  3. Объединяем результаты через ОБЪЕДИНИТЬ ВСЕ.

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

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ ...
ИЗ ЗаказыНаПроизводство КАК ЗаказыНаПроизводство
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
ГДЕ ЗаказыНаПроизводство.Оформление <> &Строк
И (СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление 
   ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпезификаций.Сырье))

Такой подход часто оказывается быстрее, так как СУБД строит два элементарных плана выполнения вместо одного перегруженного. Это особенно актуально для высоконагруженных систем (ERP, КА, УТ 11).

Рекомендации по отладке

Если вы столкнулись с тем, что соединение не отрабатывает, выполните следующие шаги:

Подведем итог: использование ВЫБОР внутри ПО — это антипаттерн, которого следует избегать. Переход к чистой булевой логике или разделение запроса через ОБЪЕДИНИТЬ ВСЕ сделает ваш код надежным, быстрым и легко читаемым.

← На главную