К статье
Практический материал

Разработка и Конфигурация · 11 мин

Левое соединение в 1С работает как внутреннее: причины и как исправить

Почему левое соединение в запросе 1С ведёт себя как внутреннее: условия в ГДЕ, фильтры во временных таблицах, параметры виртуальных таблиц. Разбор причин и решений.

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

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

Ниже разобраны основные сценарии, при которых левое соединение ведёт себя как внутреннее, способы диагностики и конкретные приёмы исправления запроса без потери данных.

Как устроено левое соединение и почему появляются NULL

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

Ключевой момент: соединение выполняется раньше, чем применяется секция ГДЕ. Сначала формируется полный результат соединения с NULL-строками, а затем к нему применяется фильтр. Если в фильтре есть условие вида «Право.Поле = &Значение», то строки, где «Право.Поле» равно NULL, это условие не пройдут — сравнение NULL с любым значением даёт не ИСТИНА. В итоге результат совпадает с внутренним соединением.

Причина 1: условие по правой таблице в секции ГДЕ

Самая распространённая ситуация. Запрос соединяет справочник с регистром, а в секции ГДЕ стоит условие на поле регистра — например, отбор по складу, организации или периоду. Все строки справочника, для которых в регистре нет записей, получают NULL в этих полях и отсекаются фильтром.

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

Второй способ — дополнить условие проверкой на NULL: «ГДЕ (Право.Склад = &Склад ИЛИ Право.Склад ЕСТЬ NULL)». Этот вариант сохраняет строки без пары, но делает запрос менее читаемым и может мешать оптимизатору. Предпочтительнее всё же перенос условия в ПО.

// Было — работает как внутреннее:

// ГДЕ Остатки.Склад = &Склад

// Стало — условие в соединении:

ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки

ПО Номенклатура.Ссылка = Остатки.Номенклатура

И Остатки.Склад = &Склад

Причина 2: параметры и отборы виртуальных таблиц

Виртуальные таблицы регистров (Остатки, Обороты, СрезПоследних) принимают параметры, и здесь есть тонкость. Условие, переданное в параметры виртуальной таблицы, применяется до соединения — ещё на этапе формирования самой виртуальной таблицы. Это правильное поведение, и оно не ломает левое соединение: отбор по складу в параметрах таблицы Остатки безопасен.

Проблема возникает, когда разработчик путает эти два механизма: часть условий указывает в параметрах виртуальной таблицы, а часть дублирует в секции ГДЕ основного запроса. Условие в ГДЕ по тем же полям отсекает NULL-строки. При рефакторинге запроса проверяйте, не осталось ли «хвостов» фильтрации после переноса условий в параметры.

Причина 3: соединение с ранее отфильтрованной временной таблицей

В пакетных запросах левая таблица соединения может быть не справочником, а временной таблицей, которая уже прошла фильтрацию. Тогда «выпадают» не NULL-строки, а сами исходные данные: если во временную таблицу попали только документы конкретного контрагента, левое соединение дальше работает честно, но состав левой части уже усечён.

Диагностический признак: запрос по отдельности даёт ожидаемый результат, а в составе пакета — нет. В этом случае нужно проверять не соединение, а запрос, формирующий временную таблицу. Частая ошибка — поместить в левую таблицу результат другого соединения, где уже стояло проблемное условие в ГДЕ.

Порядок поиска причины
  1. 1
    Выполните запрос без секции ГДЕ и сравните количество строк — если строк стало больше, причина в фильтре.
  2. 2
    Проверьте каждое условие в ГДЕ: есть ли среди них поля правой таблицы соединения.
  3. 3
    Проверьте параметры виртуальных таблиц и отборы во временных таблицах, участвующих в соединении.
  4. 4
    Перенесите условия по правой таблице в секцию ПО или добавьте обработку NULL.
  5. 5
    Выполните запрос повторно и убедитесь, что строки без пары присутствуют, а их поля равны NULL.

Причина 4: отборы СКД и настройки динамического списка

Если запрос используется в схеме компоновки данных, отбор может добавляться не в тексте запроса, а в настройках отчёта. Пользователь или разработчик ставит отбор по полю, которое физически принадлежит правой таблице соединения, — и СКД добавляет условие в итоговый запрос, отсекая NULL-строки. В консоли запроса текст выглядит правильно, а отчёт показывает меньше строк.

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

Причина 5: обращение к полям через точку от NULL-ссылки

Менее очевидный сценарий: в выборке или условии используется разыменование — например, «Остатки.Номенклатура.Артикул». Когда левая часть соединения имеет пару, всё работает, но для NULL-строк разыменование даёт NULL, и если такое поле участвует в ГДЕ или в группировке с дополнительными условиями, строки снова теряются. Дополнительно разыменование по соединённым таблицам ухудшает производительность: платформа неявно строит дополнительные соединения.

Практическая рекомендация — выбирать реквизиты из той таблицы, где они физически хранятся, а проверки на отсутствие пары выполнять через ЕСТЬ NULL по ключевому полю правой таблицы, например по ссылке или измерению регистра.

Как проверить, что запрос исправлен

Надёжный способ верификации — сравнить три выборки: количество строк в левой таблице отдельно, количество строк результата левого соединения без фильтров и количество строк итогового запроса. Если третье число меньше второго, часть строк по-прежнему отсекается, и нужно искать оставшееся условие.

Дополнительно полезно вывести в результат ключевое поле правой таблицы и проверить, есть ли в выборке строки, где оно равно NULL. Их наличие — прямое подтверждение, что левое соединение работает как задумано. Если NULL-строк нет, хотя в правой таблице заведомо отсутствуют пары для части данных, — соединение фактически внутреннее.

Контрольные точки

Как проверить результат

Строки без пары в правой таблице
Норма

Присутствуют в результате, поля правой таблицы равны NULL

Тревожный признак

Отсутствуют — соединение работает как внутреннее

Количество строк результата
Норма

Не меньше количества строк левой таблицы (без дублей соединения)

Тревожный признак

Меньше, чем строк в левой таблице

Условие по полю правой таблицы
Норма

Находится в секции ПО соединения

Тревожный признак

Находится в секции ГДЕ без проверки ЕСТЬ NULL

Типичные ошибки и альтернативные подходы

Иногда левое соединение вообще не нужно. Если по логике задачи строки без пары должны быть исключены, честнее написать ВНУТРЕННЕЕ СОЕДИНЕНИЕ — оно понятнее читается и даёт оптимизатору больше свободы. Обратная ситуация: когда нужно объединить полный список с данными регистра, но фильтр сложный, удобнее сначала поместить отфильтрованные данные регистра во временную таблицу, а затем соединять с ней — так структура запроса становится прозрачнее.

Типичные ошибки
01
Условие по полю правой таблицы в ГДЕ — отсекает NULL-строки и превращает соединение во внутреннее.
02
Сравнение поля с NULL через равенство вместо оператора ЕСТЬ NULL — условие никогда не срабатывает.
03
Отбор в настройках СКД по полю правой таблицы — отчёт показывает меньше строк, чем консоль запроса.
04
Разыменование через точку от полей правой таблицы — лишние неявные соединения и потеря строк при дополнительных условиях.
05
Фильтрация левой временной таблицы раньше соединения — исчезают не NULL-строки, а сами исходные данные.
Маршрут решения

Как выбрать действие

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

Подводя итог: платформа 1С исполняет левое соединение корректно, а эффект «внутреннего соединения» всегда объясняется пост-фильтрацией NULL-строк. Найдите условие, которое отсекает строки без пары, перенесите его в соединение или в параметры виртуальной таблицы — и запрос вернёт полный набор данных левой таблицы.

Итог

Что важно запомнить

Левое соединение в 1С превращается во внутреннее из-за условий по полям правой таблицы в секции ГДЕ, отборов СКД или фильтрации временных таблиц. Переносите условия в секцию ПО или параметры виртуальных таблиц, проверяйте результат наличием NULL-строк, а перед изменением рабочего запроса тестируйте его на копии базы.

Частые вопросы

Почему условие в секции ПО не отсекает строки, а в ГДЕ отсекает?

Потому что условие в ПО применяется в момент соединения: если пара не найдена, строка левой таблицы всё равно попадает в результат с NULL в полях правой. Секция ГДЕ выполняется уже после соединения над готовым набором, и сравнение поля правой таблицы со значением отбрасывает все NULL-строки.

Как проверить поле на NULL в языке запросов 1С?

Только оператором ЕСТЬ NULL: «ГДЕ Поле ЕСТЬ NULL» или «ГДЕ НЕ Поле ЕСТЬ NULL». Сравнение «Поле = NULL» не работает, потому что NULL не равен ничему, включая сам себя. Для замены NULL на конкретное значение в выборке используйте функцию ЕСТЬNULL(Поле, Значение).

Влияет ли отбор в параметрах виртуальной таблицы Остатки на левое соединение?

Нет, это безопасный способ фильтрации. Параметры виртуальной таблицы применяются до соединения, при формировании самой таблицы остатков. Левая таблица при этом сохраняет все свои строки, а для отсутствующих пар поля будут равны NULL — это и есть ожидаемое поведение левого соединения.

Отчёт на СКД показывает меньше строк, чем тот же запрос в консоли. Почему?

Почти наверняка в настройках отчёта установлен отбор по полю правой таблицы соединения. СКД добавляет это условие в итоговый запрос, и NULL-строки отсекаются. Проверьте отборы на вкладке настроек и в пользовательских настройках отчёта, включая отборы в группировках.

Когда вместо левого соединения лучше использовать внутреннее?

Когда строки без пары по смыслу задачи не нужны: например, вы выбираете только номенклатуру с ненулевыми остатками. Внутреннее соединение в этом случае честнее отражает логику, проще читается и позволяет оптимизатору СУБД строить более эффективный план выполнения.

Может ли соединение по нескольким условиям давать дубли строк?

Да, если в правой таблице несколько строк удовлетворяют условию соединения, каждая строка левой таблицы продублируется. Это не превращает соединение во внутреннее, но искажает итоги при группировке. Решение — предварительно свернуть правую таблицу группировкой до уровня ключа соединения.