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

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

При работе с запросами в платформе «1С:Предприятие» разработчики регулярно сталкиваются с необходимостью объединения данных из нескольких таблиц. Одним из самых часто используемых механизмов является левое соединение (LEFT JOIN). Его главная задача — вернуть абсолютно все записи из левой (первичной) таблицы и дополнить их подходящими данными из правой таблицы. Если же в правой таблице соответствий не нашлось, на их месте должны остаться пустые значения (NULL).

Однако на практике часто возникает ситуация, когда после добавления условий фильтрации левое соединение неожиданно начинает вести себя как внутреннее (INNER JOIN), безжалостно отсекая из итогового результата те строки левой таблицы, для которых не нашлось соответствия в правой. Давайте подробно разберем, почему так происходит, изучим физику процесса работы СУБД и рассмотрим два надежных способа решения этой проблемы.

Анализ проблемы: почему левое соединение «ломается»?

Для начала проанализируем исходный запрос, который вызывает затруднения у автора темы:


ВЫБРАТЬ
    ПорядокТоваровДляМаршрутов.Номенклатура,
    РеализацияТоваровУслугТовары.Количество
ИЗ
    Справочник.ПорядокТоваровДляМаршрутов КАК ПорядокТоваровДляМаршрутов
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
        ПО ПорядокТоваровДляМаршрутов.Номенклатура = РеализацияТоваровУслугТовары.Номенклатура
ГДЕ
    РеализацияТоваровУслугТовары.Ссылка = &Ссылка

На первый взгляд запрос выглядит логично: мы хотим получить весь список номенклатуры из справочника ПорядокТоваровДляМаршрутов, а также информацию о количестве этой номенклатуры в конкретном документе реализации. Но на выходе мы получаем только те позиции номенклатуры, которые физически присутствуют в документе. Вся остальная номенклатура из справочника просто исчезает из результата.

Выясним причину такого поведения СУБД. Процесс обработки этого запроса сервером баз данных происходит пошагово:

  1. Этап соединения (секция ПО): СУБД берет всю номенклатуру из справочника ПорядокТоваровДляМаршрутов и пытается сопоставить её со строками табличной части документа РеализацияТоваровУслуг.Товары по равенству полей Номенклатура. На этом шаге левое соединение отрабатывает корректно. Для тех товаров, которых нет в документе, СУБД формирует виртуальные строки, где все поля правой таблицы (включая Ссылка и Количество) принимают значение NULL.
  2. Этап фильтрации (секция ГДЕ): После того как временный результирующий набор строк сформирован, СУБД применяет к нему фильтр, указанный в секции ГДЕ. В нашем случае это условие РеализацияТоваровУслугТовары.Ссылка = &Ссылка.
  3. Логическая проверка: СУБД начинает проверять каждую строку. Для позиций, которых не было в документе, значение поля Ссылка равно NULL. Система вычисляет выражение: NULL = &Ссылка. Согласно законам трехзначной логики SQL, любое сравнение с NULL (кроме специальных операторов проверки на пустоту) возвращает значение Ложь (или Unknown).
  4. Исключение строк: Поскольку условие фильтрации вернуло Ложь, все строки, содержащие NULL в правой части, нещадно отбрасываются из финального результата. Таким образом, наше левое соединение фактически превратилось во внутреннее.

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

Решение 1: Перенос условий фильтрации в секцию соединения (секция ПО)

Это самый простой, элегантный и наиболее производительный способ решения проблемы при работе с физическими таблицами базы данных (такими как справочники, документы и их табличные части). Нам достаточно просто перенести условие отбора по конкретному документу из секции ГДЕ непосредственно в секцию описания связи ПО.

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


ВЫБРАТЬ
    ПорядокТоваровДляМаршрутов.Номенклатура,
    РеализацияТоваровУслугТовары.Количество
ИЗ
    Справочник.ПорядокТоваровДляМаршрутов КАК ПорядокТоваровДляМаршрутов
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
        ПО ПорядокТоваровДляМаршрутов.Номенклатура = РеализацияТоваровУслугТовары.Номенклатура
            И РеализацияТоваровУслугТовары.Ссылка = &Ссылка

Проанализируем, как теперь СУБД обрабатывает этот запрос. Условие РеализацияТоваровУслугТовары.Ссылка = &Ссылка теперь применяется не к готовому результату соединения, а непосредственно в момент осуществления связи строк.

СУБД сначала отфильтрует строки правой таблицы РеализацияТоваровУслуг.Товары по значению &Ссылка, а уже затем выполнит левое соединение справочника с полученным отфильтрованным набором. В итоге все записи из справочника гарантированно попадут в результат запроса, а для отсутствующих в документе товаров в поле Количество выведется пустое значение (NULL). Цель достигнута!

Решение 2: Использование временных таблиц (ВТ)

Второй способ заключается в предварительной подготовке данных правой таблицы во временной таблице. Этот подход также был предложен в процессе обсуждения на форуме. Разберем его по шагам.

Сначала мы создаем временную таблицу, в которую помещаем данные табличной части конкретного документа, предварительно отфильтровав их в секции ГДЕ:


ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Номенклатура КАК Номенклатура,
    РеализацияТоваровУслугТовары.Количество КАК Количество
ПОМЕСТИТЬ ВТ_Реализации
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    РеализацияТоваровУслугТовары.Ссылка = &Ссылка
;

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


ВЫБРАТЬ
    ПорядокТоваровДляМаршрутов.Номенклатура,
    ВТ_Реализации.Количество
ИЗ
    Справочник.ПорядокТоваровДляМаршрутов КАК ПорядокТоваровДляМаршрутов
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Реализации КАК ВТ_Реализации
        ПО ПорядокТоваровДляМаршрутов.Номенклатура = ВТ_Реализации.Номенклатура

Этот способ абсолютно рабочий. Мы заранее избавились от лишних записей в правой таблице, поэтому при левом соединении СУБД не встретит никаких мешающих условий фильтрации, и левое соединение отработает корректно.

Сравнение методов: какой подход выбрать?

Оба представленных решения приводят к одинаковому результату, но с точки зрения производительности и стандартов разработки «1С:Предприятия» между ними есть существенная разница. Давайте проведем сравнительный анализ.

Перенос условий в секцию ПО (Решение 1) является наиболее предпочтительным при работе с простыми физическими таблицами. Его преимущества:

Использование временных таблиц (Решение 2) становится незаменимым инструментом в следующих сценариях:

В нашем конкретном примере мы имеем дело с физической таблицей справочника и табличной частью документа. Поэтому наиболее правильным, красивым и быстрым решением будет Решение №1 — простой перенос условия РеализацияТоваровУслугТовары.Ссылка = &Ссылка в секцию соединения ПО.

← На главную