Почему не срабатывает условие в запросе при левом соединении с пустой таблицей?

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

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

Анализ исходной проблемы

Рассмотрим ситуацию на примере запроса, предоставленного автором темы. В нем выбираются данные из справочника ЗначенияСвойствОбъектов и к ним левым соединением подтягиваются данные из регистра сведений НастройкаАдресовДоставки. Условие по владельцу справочника (левая таблица) ошибочно вынесено в секцию ПО.


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

Автор заметил, что если регистр НастройкаАдресовДоставки пуст, то условие ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки как будто игнорируется: в результат попадают абсолютно все записи справочника, независимо от того, кто является их владельцем. Это происходит потому, что логика работы секции ПО принципиально отличается от логики секции ГДЕ.

Разница между секциями ПО и ГДЕ

Проанализируем, как СУБД выполняет ЛЕВОЕ СОЕДИНЕНИЕ. Этот тип соединения гарантирует, что все записи из левой таблицы (основной) попадут в итоговый результат. Секция ПО в данном случае является не фильтром всей выборки, а лишь инструкцией по поиску пары в правой таблице.

Алгоритм работы выглядит следующим образом:

  1. Берется строка из левой таблицы (ЗначенияСвойствОбъектов).
  2. Проверяются условия в секции ПО.
  3. Если условия выполняются, к строке левой таблицы приклеиваются данные из соответствующей строки правой таблицы.
  4. Если условия не выполняются (или правая таблица пуста), строка из левой таблицы всё равно остается в выборке, но во всех полях, относящихся к правой таблице, проставляется значение NULL.

Таким образом, когда мы пишем И (ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки) внутри ПО, мы буквально говорим системе: "Попробуй найти запись в регистре только для тех элементов справочника, у которых владелец совпадает с параметром. Если владелец другой — просто поставь NULL в колонках регистра, но сам элемент справочника из отчета не удаляй".

Решение №1: Использование секции ГДЕ

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

Исправленный вариант запроса:


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

Теперь запрос отработает корректно: сначала будут отобраны только нужные владельцы, а затем для них (и только для них) будет произведена попытка найти данные в регистре сведений.

Решение №2: Предварительная фильтрация во временной таблице

Для оптимизации производительности, особенно в сложных запросах с большим объемом данных, "золотым стандартом" разработки на платформе 1С считается использование временных таблиц. Вместо того чтобы соединять всю таблицу справочника с регистром, а потом фильтровать результат, мы можем сначала отобрать нужные записи.

Рассмотрим этот метод по шагам:

  1. Создаем временную таблицу, куда выбираем только нужные элементы справочника с фильтром по владельцу.
  2. Индексируем временную таблицу по полю связи.
  3. Соединяем эту маленькую отфильтрованную таблицу с остальными данными.

// Шаг 1: Отбор нужных данных во временную таблицу
ВЫБРАТЬ
    ЗначенияСвойствОбъектов.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_СпособыДоставки
ИЗ
    Справочник.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
    ЗначенияСвойствОбъектов.Владелец = &СвойствоСпособДоставки
ИНДЕКСИРОВАТЬ ПО
    Ссылка;

// Шаг 2: Финальная выборка с соединением
ВЫБРАТЬ
    ВТ_СпособыДоставки.Ссылка КАК СпособДоставки,
    НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки
ИЗ
    ВТ_СпособыДоставки КАК ВТ_СпособыДоставки
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НастройкаАдресовДоставки КАК НастройкаАдресовДоставки
    ПО ВТ_СпособыДоставки.Ссылка = НастройкаАдресовДоставки.СпособДоставки

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

Ловушка: Превращение левого соединения во внутреннее

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

Пример ошибки:

ГДЕ НастройкаАдресовДоставки.СпособЗаданияАдресаДоставки = &Параметр

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

Работа с NULL и итоги

Выясним, на что еще стоит обратить внимание при работе с соединениями и пустыми таблицами:

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

← На главную