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

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

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

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

Посмотрим на пример запроса, который выдает ошибочный, на первый взгляд, результат. По задумке автора, запрос должен выбрать всю номенклатуру, соединить ее с регистром сведений и оставить только свойство "Ширина":


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

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

Затем в дело вступает секция ГДЕ, которая фильтрует уже соединенную, результирующую таблицу. Условие ДополнительныеСведения.Свойство.Заголовок = &Ширина будет проверяться для каждой строки. Если у товара нет нужного свойства, в поле Свойство будет NULL. Сравнение NULL = &Ширина возвращает ложь, и такие строки безжалостно отсекаются платформой. Более того, на уровне СУБД оптимизатор видит это жесткое условие и сразу меняет план запроса с LEFT OUTER JOIN на INNER JOIN для ускорения работы. В итоге левое соединение превращается во внутреннее.

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

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

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


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

Решение 2: Сохранение левого соединения в секции ГДЕ

Рассмотрим альтернативный вариант. Иногда логика построения сложного запроса (например, в СКД или динамических списках) требует, чтобы отбор оставался именно в секции ГДЕ. Чтобы при этом записи из левой таблицы не исчезали, мы должны явно объяснить системе, что пустые значения нас тоже устраивают.

Для этого используем логическое ИЛИ и проверяем поле правой таблицы на ЕСТЬ NULL:


ГДЕ
    (ДополнительныеСведения.Свойство.Заголовок = &Ширина 
    ИЛИ ДополнительныеСведения.Свойство ЕСТЬ NULL)

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

Дополнительные важные нюансы оптимизации запросов

Проанализируем еще несколько скрытых проблем в изначальном запросе, которые стоит избегать профессиональным программистам.

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

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

← На главную