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

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

При написании запросов в 1С разработчики нередко сталкиваются с ситуацией, когда конструкция ЛЕВОЕ СОЕДИНЕНИЕ неожиданно отсекает строки левой таблицы и возвращает результат, аналогичный внутреннему соединению. Разберем механизм возникновения этой ошибки и рассмотрим правильные варианты построения запроса.

Проанализируем исходную проблемную ситуацию. Допустим, нам необходимо сопоставить номенклатурные позиции из табличной части документа ЗаказКлиента (15 строк) с позициями документа ПеремещениеТоваров (7 строк). Разработчик пишет следующий запрос:


ВЫБРАТЬ
    ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
    ПеремещениеТоваровТовары.Номенклатура КАК Номенклатура1
ИЗ
    Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
        ПО ЗаказКлиентаТовары.Номенклатура = ПеремещениеТоваровТовары.Номенклатура
ГДЕ
    ЗаказКлиентаТовары.Ссылка = &Ссылка
    И ПеремещениеТоваровТовары.Ссылка = &СсылкаП

В результате выполнения такого запроса вместо ожидаемых 15 строк возвращаются только те 7, которые присутствуют в обоих документах. Выясним, почему так происходит.

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

Способ 1. Перенос условия фильтрации правой таблицы в секцию ПО

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


ВЫБРАТЬ
    ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
    ПеремещениеТоваровТовары.Номенклатура КАК Номенклатура1
ИЗ
    Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
        ПО ЗаказКлиентаТовары.Номенклатура = ПеремещениеТоваровТовары.Номенклатура
        И ПеремещениеТоваровТовары.Ссылка = &СсылкаП
ГДЕ
    ЗаказКлиентаТовары.Ссылка = &Ссылка

В этом случае СУБД сначала отберет строки правой таблицы, относящиеся только к документу &СсылкаП, а затем соединит их со всеми строками левой таблицы, ограниченной документом &Ссылка в секции ГДЕ.

Способ 2. Использование временных таблиц (рекомендуемый подход)

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

  1. Помещаем во временную таблицу товары из первого документа с группировкой по номенклатуре.
  2. Помещаем во вторую временную таблицу товары из второго документа, также сгруппировав их.
  3. Индексируем поля соединения для ускорения работы СУБД.
  4. Выполняем левое соединение между подготовленными временными таблицами.

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

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Заказ.Номенклатура КАК Номенклатура,
    ВТ_Заказ.Количество КАК КоличествоЗаказ,
    ЕСТЬNULL(ВТ_Перемещение.Количество, 0) КАК КоличествоПеремещение
ИЗ
    ВТ_Заказ КАК ВТ_Заказ
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Перемещение КАК ВТ_Перемещение
        ПО ВТ_Заказ.Номенклатура = ВТ_Перемещение.Номенклатура

Способ 3. Обработка NULL в условии секции ГДЕ

Посмотрим еще на один возможный вариант обхода проблемы, если перестроить запрос невозможно. В секции ГДЕ можно явно разрешить значения NULL для правой таблицы с помощью функции ЕСТЬNULL:


ГДЕ
    ЗаказКлиентаТовары.Ссылка = &Ссылка
    И ЕСТЬNULL(ПеремещениеТоваровТовары.Ссылка, &СсылкаП) = &СсылкаП

Обратите внимание: данный способ не рекомендуется использовать на больших объемах данных, так как применение функций к полям в секции ГДЕ препятствует использованию индексов оптимизатором СУБД.

Важные правила и нюансы при соединении таблиц в 1С

  1. Риск размножения строк (декартово произведение): если в табличной части документа одна и та же номенклатура указана несколько раз (например, с разными характеристиками или сериями), соединение исключительно по полю Номенклатура приведет к дублированию строк. Всегда используйте СГРУППИРОВАТЬ ПО перед соединением или связывайте таблицы по всем ключевым аналитикам.
  2. Индексация временных таблиц: при использовании промежуточных таблиц обязательно добавляйте конструкцию ИНДЕКСИРОВАТЬ ПО для полей, участвующих в условиях ПО.
  3. Обработка значений NULL: при выполнении арифметических операций с полями правой таблицы (например, вычисление остатка или разницы) всегда оборачивайте их в функцию ЕСТЬNULL(Поле, 0), иначе результат любой арифметической операции со значением NULL вернет NULL.
  4. Запросы к регистрам вместо документов: в реальных задачах учета сопоставление движений рекомендуется выполнять по виртуальным таблицам регистров накопления, а не путем прямого соединения табличных частей документов.
← На главную