При разработке сложных отчетов, особенно в производственных блоках, программисты часто сталкиваются с ситуацией, когда ЛЕВОЕ СОЕДИНЕНИЕ ведет себя непредсказуемо. Казалось бы, логика прописана верно, условия ИЛИ добавлены, но часть данных «пропадает» или соединение отрабатывает только по первой части условия. Рассмотрим эту проблему на примере реального кейса, связанного с выбором спецификаций номенклатуры в зависимости от оформления заказа.
Представим задачу: нам необходимо соединить таблицу заказов с таблицей спецификаций. Логика выбора спецификации должна быть гибкой: если в заказе указано конкретное Оформление, берем спецификацию с таким же оформлением. Если же соответствия не найдено или условие иное, должны подтянуться спецификации с видом Сырье. Проанализируем исходный код, который вызвал затруднения:
ЛЕВОЕ СОЕДИНЕНИЕ СпецификацииНоменклатуры КАК СпецификацииНоменклатуры
ПО ЗаказыНаПроизводство.Номенклатура = СпецификацииНоменклатуры.Номенклатура
И (ВЫБОР
КОГДА ЗаказыНаПроизводство.Оформление <> &Строк
ТОГДА СпецификацииНоменклатуры.Оформление = ЗаказыНаПроизводство.Оформление
ИЛИ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сырье)
ИНАЧЕ СпецификацииНоменклатуры.ВидСпецификации = ЗНАЧЕНИЕ(Перечисление.ВидыСпецификаций.Сборочная)
КОНЕЦ)
Проблема заключается в том, что при добавлении оператора ИЛИ внутри конструкции ВЫБОР, которая сама находится внутри условия ПО, платформа 1С и нижележащая СУБД могут интерпретировать логику не так, как ожидает разработчик. Выясним основные причины «нестабильности» такого подхода.
Использование ИЛИ в секции ПО (JOIN ON) — это классическая «антипрактика» в SQL. Когда СУБД видит OR в условии соединения, она часто не может использовать индексы эффективно. Вместо быстрого поиска (Index Seek) планировщик может переключиться на полное сканирование таблиц или использовать неоптимальные алгоритмы соединения.
В контексте 1С это часто приводит к тому, что условие отрабатывает «частично». Если первая часть условия (по оформлению) возвращает результат, вторая часть (по виду спецификации) может игнорироваться или приводить к избыточному дублированию строк, которые затем некорректно фильтруются последующими условиями.
В более детальном тексте запроса (сообщение №29) мы видим еще более сложную конструкцию — поиск максимальной даты утверждения спецификации прямо внутри условия соединения:
И (СпецификацииНоменклатуры.ДатаУтверждения В
(ВЫБРАТЬ
МАКСИМУМ(Спецификации.ДатаУтверждения)
ИЗ
СпецификацииНоменклатуры КАК Спецификации
ГДЕ
Спецификации.Номенклатура = СпецификацииНоменклатуры.Номенклатура
И Спецификации.ДатаУтверждения <= ЗаказыНаПроизводство.ДатаЗаказа
И ВЫБОР ... КОНЕЦ))
Это критическая точка отказа. Подзапрос ВЫБРАТЬ МАКСИМУМ выполняется в контексте каждой строки соединения. Если логика ВЫБОР внутри подзапроса не совпадает идеально с логикой ВЫБОР в основном условии ПО, подзапрос вернет дату «не той» спецификации. Например, он найдет максимальную дату для сырьевой спецификации, а основное условие в этот момент пытается найти спецификацию по оформлению. В итоге условие ДатаУтверждения В (...) вернет ЛОЖЬ, и строка просто исчезнет из результата, хотя данные в базе есть.
Разберем момент со сравнением ЗаказыНаПроизводство.Оформление <> &Строк. Если в результате левого соединения поле Оформление оказывается равным NULL, любое сравнение с ним (кроме ЕСТЬ NULL) даст результат «Ложь» или «Неизвестно». Проанализируем ситуацию: если мы ищем пустые значения, лучше использовать явную проверку:
// Вместо этого:
КОГДА ЗаказыНаПроизводство.Оформление <> ""
// Используем это:
КОГДА ЕСТЬNULL(ЗаказыНаПроизводство.Оформление, "") <> ""
Чтобы сделать запрос стабильным и читаемым, мы рекомендуем отказаться от сложных условий в ПО и перенести логику во временные таблицы. Разберем по шагам, как это сделать.
ВТ_АктуальныеСпецификации. В ней заранее рассчитаем максимальные даты для каждого вида спецификации и каждого оформления отдельно для каждой номенклатуры.ВЫБОР...ИЛИ, выполним несколько простых левых соединений.Рассмотрим пример реализации структуры итогового запроса:
// Сначала получаем спецификации по оформлению
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СпецификацииПоОформлению КАК СпецПоОформлению
ПО Заказы.Номенклатура = СпецПоОформлению.Номенклатура
И Заказы.Оформление = СпецПоОформлению.Оформление
// Затем отдельно получаем сырьевые спецификации
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СпецификацииСырье КАК СпецСырье
ПО Заказы.Номенклатура = СпецСырье.Номенклатура
После того как мы получили данные из двух разных соединений, в секции ВЫБРАТЬ мы можем легко определить, какую спецификацию приоритетнее использовать, с помощью простой функции ЕСТЬNULL или ВЫБОР:
ВЫБОР
КОГДА СпецПоОформлению.Ссылка ЕСТЬ НЕ NULL
ТОГДА СпецПоОформлению.Ссылка
ИНАЧЕ ЕСТЬNULL(СпецСырье.Ссылка, ЗНАЧЕНИЕ(Справочник.СпецификацииНоменклатуры.ПустаяСсылка))
КОНЕЦ КАК ИтоговаяСпецификация
Если вы все же решили бороться с текущим запросом, проанализируем метод декомпозиции условий, предложенный в сообщении №33:
ПО только базовое равенство: Заказы.Номенклатура = Спецификации.Номенклатура.ВЫБРАТЬ, которые вычисляют ваши условия: (Заказы.Оформление = Спецификации.Оформление) КАК Условие1.Условие1 возвращает ЛОЖЬ, хотя вы ожидали ИСТИНА. Часто выясняется, что типы данных различаются (например, пустая ссылка против NULL) или в строках присутствуют невидимые символы.Важный итог: Нестабильность левого соединения в 1С — это почти всегда результат избыточной сложности условий внутри секции ПО. Вынося расчеты (особенно получение МАКСИМУМ) во временные таблицы, вы не только исправляете логические ошибки, но и значительно ускоряете работу системы, так как СУБД получает возможность построить оптимальный план выполнения.