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

Программист 1С v8.3 (Обычные формы)
← На главную

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

Анализ проблемной ситуации

Рассмотрим типичный пример. Нам необходимо выбрать все документы ТребованиеНакладная и для каждого из них попытаться получить значение дополнительного реквизита "Причина" из регистра сведений ЗначенияСвойствОбъектов. Если у документа нет такого реквизита, мы все равно хотим видеть сам документ в списке, но с пустым значением свойства.

Посмотрим на ошибочный вариант запроса, который часто пишут новички:


"ВЫБРАТЬ
|    ТребованиеНакладная.Номер КАК Номер,
|    ЗначенияСвойствОбъектов.Значение КАК Значение
|ИЗ
|    Документ.ТребованиеНакладная КАК ТребованиеНакладная
|        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
|        ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
|ГДЕ
|    ТребованиеНакладная.Ссылка = &ТН
|    И ЗначенияСвойствОбъектов.Объект = &ТН
|    И ЗначенияСвойствОбъектов.Свойство = &Причина";

Проанализируем, почему этот запрос не вернет документ, если в регистре нет записи по свойству "Причина". Основная причина кроется в секции ГДЕ. Когда мы накладываем условие на поля таблицы, которая находится в "правой" части левого соединения (в нашем случае это ЗначенияСвойствОбъектов), СУБД сначала выполняет соединение, а затем применяет фильтр ГДЕ ко всему результату.

Если в регистре нет подходящей записи, то после соединения в полях регистра будет значение NULL. Однако условие ЗначенияСвойствОбъектов.Свойство = &Причина не может быть истинным для NULL. В итоге строка отсекается фильтром, и мы теряем данные основной таблицы ТребованиеНакладная.

Решение №1: Перенос условий в секцию ПО

Чтобы сохранить логику левого соединения, необходимо перенести все условия, касающиеся правой таблицы, непосредственно в условие соединения ПО. Выясним, как изменится текст запроса:


"ВЫБРАТЬ
|    ТребованиеНакладная.Номер КАК Номер,
|    ЗначенияСвойствОбъектов.Значение КАК Значение
|ИЗ
|    Документ.ТребованиеНакладная КАК ТребованиеНакладная
|        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
|        ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
|            И ЗначенияСвойствОбъектов.Свойство = &Причина
|ГДЕ
|    ТребованиеНакладная.Ссылка = &ТН";

Рассмотрим, что изменилось. Теперь условие по свойству "Причина" проверяется в момент соединения. Система берет документ, ищет в регистре запись, которая соответствует ссылке документа И имеет нужное свойство. Если такая запись найдена — данные подставляются. Если не найдена — документ остается в выборке, а поля регистра заполняются NULL. Секция ГДЕ теперь фильтрует только основную таблицу (по ссылке на документ), не влияя на результат соединения.

Решение №2: Использование функции ЕСТЬNULL

Часто в интерфейсе или отчетах вместо пустого значения (NULL) логично вывести какой-то текст-заглушку или значение по умолчанию. Для этого воспользуемся функцией ЕСТЬNULL. Разберем по шагам реализацию в секции ВЫБРАТЬ:


"ВЫБРАТЬ
|    ТребованиеНакладная.Номер КАК Номер,
|    ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, ""Не указано"") КАК ЗначениеПричины
|ИЗ
|    Документ.ТребованиеНакладная КАК ТребованиеНакладная
|        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
|        ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
|            И ЗначенияСвойствОбъектов.Свойство = &Причина
|ГДЕ
|    ТребованиеНакладная.Ссылка = &ТН";

Функция ЕСТЬNULL проверяет первый параметр. Если там NULL, она возвращает второй параметр. Это отличный способ сделать отчет более читаемым для пользователя.

Идеологическое обоснование: Порядок выполнения инструкции SELECT

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

  1. FROM — определение источников данных.
  2. ON (ПО) — условия соединения таблиц.
  3. JOIN — само выполнение соединения.
  4. WHERE (ГДЕ) — фильтрация получившихся строк.
  5. GROUP BY — группировка данных.
  6. HAVING — фильтрация после группировки.
  7. SELECT — выбор полей и вычисление выражений.
  8. DISTINCT — исключение дублей.
  9. ORDER BY — финальная сортировка.

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

Продвинутые советы по производительности

Рассмотрим несколько важных нюансов, которые помогут писать не только правильные, но и быстрые запросы:

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

2. Временные таблицы. В крупных базах (с миллионами записей) прямое левое соединение к большим регистрам может замедлять работу. Профессиональным подходом считается использование временных таблиц. Сначала выберем нужные нам ссылки на документы во временную таблицу ВТ_Документы, а затем соединим ее с регистром. Это позволит оптимизатору запросов построить более стабильный план выполнения.

3. Проблема размножения строк. Помните, что ЛЕВОЕ СОЕДИНЕНИЕ может увеличить количество строк в результате. Если для одного документа в регистре по какой-то причине окажется две записи с одинаковым свойством, документ в результате запроса продублируется. Всегда проверяйте уникальность данных в присоединяемых таблицах или используйте агрегатные функции (например, МАКСИМУМ), если вам нужно гарантированно получить только одно значение.

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

Подведем итог: использование условий в секции ПО при левом соединении — это не просто "хитрость", а стандарт грамотной разработки в 1С. Это гарантирует целостность вашей выборки и высокую производительность системы.

← На главную