Работа с запросами в 1С:Предприятие часто сталкивает разработчиков с неожиданным поведением: левое соединение (LEFT JOIN), которое по логике должно возвращать все записи из левой таблицы, внезапно ведет себя как внутреннее (INNER JOIN) — исключает строки без совпадений. Эта проблема особенно актуальна в конфигурациях 1С 8.3 и 8.2, где ошибки в запросах могут приводить к потере данных в отчетах, обработках или обменах.

В этой статье мы разберем 7 ключевых причин, почему LEFT JOIN трансформируется в INNER JOIN, — от синтаксических ошибок до особенностей платформы, — а также дадим пошаговые инструкции по диагностике и исправлению. Материал будет полезен как начинающим программистам , так и опытным разработчикам, сталкивающимся с неочевидными багами в сложных запросах.

1. Синтаксические ошибки в условии WHERE

Самая распространенная причина — неправильное размещение условий фильтрации в запросе. Если в секции WHERE указать поле из правой таблицы (той, что подключается через LEFT JOIN), платформа автоматически преобразует соединение во внутреннее. Это связано с тем, что условие WHERE применяется после соединения таблиц.

Пример ошибочного запроса:

ВЫБРАТЬ

Товары.Наименование,

Остатки.Количество

ИЗ

Справочник.Товары КАК Товары

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

ПО Товары.Ссылка = Остатки.Товар

ГДЕ

Остатки.Склад = &ТекущийСклад // <-- Условие на правую таблицу!

Чтобы сохранить левое соединение, условия для правой таблицы нужно переносить в секцию ON:

ВЫБРАТЬ

Товары.Наименование,

Остатки.Количество

ИЗ

Справочник.Товары КАК Товары

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

ПО Товары.Ссылка = Остатки.Товар

И Остатки.Склад = &ТекущийСклад // <-- Теперь корректно

💡

Всегда проверяйте запрос в режиме "Конструктор запросов" (F12) — там визуально отображается тип соединения (стрелочка для LEFT JOIN).

  • 🔍 Как проверить: Удалите все условия из WHERE, связанные с правой таблицей, и посмотрите, вернутся ли "пустые" строки.
  • Быстрое исправление: Перенесите условия в ON или используйте ВЫРАЗИТЬ для подзапросов.
  • 📌 Исключение: Если правая таблица гарантированно имеет данные (например, справочник с предопределенными элементами), условие в WHERE не ломает логику.

2. Неявные преобразования типов данных

1С:Предприятие автоматически преобразует типы данных при сравнении полей в соединении. Если типы не совпадают (например, Ссылка и УникальныйИдентификатор), платформа может молча пропустить строки, которые не удалось сопоставить. В результате LEFT JOIN фактически становится INNER JOIN.

Типичные пары проблемных типов:

  • 🔹 СправочникСсылка.Товары vs ДокументСсылка.ПоступлениеТоваров
  • 🔹 Число(10,2) vs Число(15,3)
  • 🔹 Дата vs Строка (например, при соединении с внешним источником)

Решение — явное приведение типов с помощью ВЫРАЗИТЬ:

ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваров КАК Поступления

ПО ВЫРАЗИТЬ(Товары.Ссылка КАК СправочникСсылка.Товары) = Поступления.Товар

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

Откройте конфигуратор, найдите объект в дереве метаданных, и в палитре свойств посмотрите тип реквизита. Для регистров и табличных частей тип отображается в колонке "Тип" при просмотре структуры хранения (правый клик → "Структура хранения").

3. Пустые значения NULL в условиях соединения

В поле со значением NULL (неопределено) ведет себя иначе, чем в классическом SQL. Если в условии соединения участвует поле, которое может быть NULL, и вы используете операторы = или <>, платформа пропустит такие строки. Например:

ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказыПокупателей КАК Заказы

ПО Товары.Ссылка = Заказы.Товар // Если Заказы.Товар = NULL, строка исключается

Решения:

  1. Используйте ЕСТЬ NULL для явной проверки:
    ПО Товары.Ссылка = Заказы.Товар ИЛИ Заказы.Товар ЕСТЬ NULL
  2. Замените NULL на значение по умолчанию с помощью ВЫБОР КОГДА.
📊 Как часто вы сталкиваетесь с проблемами LEFT JOIN в 1С?
Редко (раз в год)
Иногда (раз в квартал)
Часто (раз в месяц)
Постоянно (еженедельно)

4. Особенности работы с виртуальными таблицами

Виртуальные таблицы (например, РегистрНакопления.ОстаткиТоваров.Остатки) имеют скрытые условия отбора, которые могут конфликтовать с LEFT JOIN. Если в виртуальной таблице задан отбор по периоду или измерению, а в основной таблице нет соответствующих данных, платформа может проигнорировать "пустые" строки.

Пример проблемы:

ВЫБРАТЬ

Товары.Наименование,

Остатки.Количество

ИЗ

Справочник.Товары КАК Товары

ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(&ТекущаяДата,) КАК Остатки

ПО Товары.Ссылка = Остатки.Товар

// Если у товара нет движений на &ТекущаяДата, строка может пропасть

Решения:

  • 📅 Расширьте период: Используйте Остатки(НАЧАЛОПЕРИОДА(&ТекущаяДата, МЕСЯЦ), &ТекущаяДата).
  • 🔄 Замените виртуальную таблицу: Используйте обычный регистр с явным указанием отбора.
  • ⚠️ Проверьте настройки регистра: Убедитесь, что в метаданных не установлен флаг "Только непустые значения".

Убедиться, что период покрывает все нужные даты|Проверить отборы в параметрах виртуальной таблицы|Заменить виртуальную таблицу на обычный запрос к регистру|Добавить явное условие для NULL-значений-->

5. Ошибки в подзапросах и временных таблицах

Если LEFT JOIN используется с подзапросом или временной таблицей, где сам подзапрос содержит WHERE с условиями на правую таблицу, соединение может "сломаться". Типичный случай — когда подзапрос возвращает только непустые строки.

Пример:

ВЫБРАТЬ

Товары.Наименование,

Продажи.Сумма

ИЗ

Справочник.Товары КАК Товары

ЛЕВОЕ СОЕДИНЕНИЕ (

ВЫБРАТЬ

Товар,

СУММА(Сумма) КАК Сумма

ИЗ

Документ.РеализацияТоваровУслуг.Товары

ГДЕ

Сумма > 0 // <-- Это условие ломает LEFT JOIN!

СГРУППИРОВАТЬ ПО Товар

) КАК Продажи ПО Товары.Ссылка = Продажи.Товар

Исправление:

  • Перенесите условия из WHERE подзапроса в ON основного запроса.
  • Используйте ВЫРАЗИТЬ для создания временной таблицы с явным указанием всех строк.
💡

Подзапросы в LEFT JOIN должны возвращать ВСЕ возможные строки левой таблицы, даже с NULL-значениями. Любой фильтр в подзапросе рискует преобразовать соединение во внутреннее.

6. Влияние индексов и планов выполнения

Платформа оптимизирует запросы на основе индексов и статистики данных. В некоторых случаях оптимизатор может решить, что LEFT JOIN эффективнее выполнить как INNER JOIN, если:

  • 📊 В правой таблице есть индекс по полю соединения, и он сильно избирателен.
  • 🔍 Статистика показывает, что почти все строки левой таблицы имеют соответствия справа.
  • 💾 Таблицы хранятся в разных файлах базы данных (например, при разделенных регистрах).

Как диагностировать:

  1. Включите ОбъяснитьЗапрос() и посмотрите план выполнения:
    Объяснение = Новый ОбъяснениеЗапроса(Запрос.Текст);
    

    Сообщить(Объяснение.ПолучитьТекст());

  2. Проверьте, не используется ли в плане оператор INNER JOIN вместо LEFT JOIN.

Решения:

  • 🔧 Подсказки оптимизатору: Используйте /+ INDEX(Таблица Индекс) / (в некоторых версиях поддерживается).
  • 🔄 Перепишите запрос: Разбейте его на несколько простых запросов с промежуточными временными таблицами.

7. Ошибки в конфигурации и метаданных

Реже, но встречаются случаи, когда проблема кроется в некорректных настройках метаданных:

Причина Признаки Решение
У реквизита соединения установлен флаг "Индексировать" = Ложь LEFT JOIN работает медленно или некорректно только для конкретного поля Включите индексирование в конфигураторе (свойства реквизита)
Поле соединения имеет тип "Строка неограниченной длины" Соединение работает нестабильно для длинных значений Измените тип на "Строка(255)" или используйте хэш-поле
В регистре установлен флаг "Только непустые записи" LEFT JOIN к регистру всегда возвращает INNER JOIN Снимите флаг в свойствах регистра или используйте обход через объектный запрос

Для диагностики:

  1. Откройте конфигуратор и проверьте свойства полей, участвующих в соединении.
  2. Сравните поведение запроса в разных базах (например, в пустой тестовой и рабочей).
  3. Проверьте журнал регистрации (Администрирование → Журнал регистрации) на ошибки выполнения запросов.
💡

Если проблема проявляется только в конкретной базе, экспортируйте запрос в DT-файл и протестируйте его в другой информационной базе с аналогичной структурой.

FAQ: Частые вопросы по LEFT JOIN в 1С

Почему в отчете не показываются товары с нулевыми остатками, хотя используется LEFT JOIN?

Скорее всего, в отчете дополнительно применен отбор по полю из правой таблицы (например, Остатки.Количество > 0). Перенесите это условие в секцию ПО соединения или удалите его. Также проверьте, не используются ли в отчете автоматические отборы по виртуальным таблицам.

Как проверить, что запрос действительно выполняется как LEFT JOIN?

Используйте следующие методы:

  1. Добавьте в выборку поле из правой таблицы и проверьте, есть ли строки с NULL.
  2. Включите ОбъяснитьЗапрос() и найдите в плане оператор LeftOuterJoin.
  3. Выполните запрос в SQL Server Profiler (для баз на MS SQL) и посмотрите сгенерированный SQL-код.
Можно ли заставить 1С всегда использовать LEFT JOIN без оптимизации?

Прямого способа отключить оптимизацию нет, но можно:

  • Использовать временные таблицы для принудительного сохранения промежуточных результатов.
  • Разбивать сложные запросы на несколько простых с явным управлением соединениями.
  • Добавлять в запрос "фиктивные" условия, которые не влияют на результат, но мешают оптимизатору (например, ПО 1=1).

⚠️ Внимание: Такие приемы могут ухудшить производительность!

Почему LEFT JOIN работает корректно в конструкторе запросов, но не в коде?

Вероятные причины:

  • В коде используются параметры запроса (&Параметр), которые в конструкторе подставлены константами.
  • В конструкторе автоматически добавляются скобки или приоритеты, которые отсутствуют в ручном коде.
  • В коде есть опечатки в именах полей или таблиц (конструктор подсказывает правильные имена).

Решение: Скопируйте запрос из конструктора в код через буфер обмена (кнопка "Текст запроса").

Как эмулировать LEFT JOIN, если он не работает?

Альтернативные подходы:

  1. Использовать UNION:
    ВЫБРАТЬ
    

    Товары.Наименование,

    Остатки.Количество

    ИЗ

    Справочник.Товары КАК Товары

    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Остатки КАК Остатки ПО Товары.Ссылка = Остатки.Товар

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

    ВЫБРАТЬ

    Товары.Наименование,

    NULL КАК Количество

    ИЗ

    Справочник.Товары КАК Товары

    ГДЕ

    НЕ Товары.Ссылка В (

    ВЫБРАТЬ Остатки.Товар ИЗ Остатки КАК Остатки

    )

  2. Применять программный обход: Выгрузить данные в массив и обработать в цикле.
  3. Использовать объекты: Для небольших выборок заменить запрос на методы объектов (например, Товар.ПолучитьОстатки()).