Как правильно использовать левое соединение в запросе 1С, чтобы не терять записи из основной таблицы

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

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

Разбор проблемы: почему пропадают записи

Рассмотрим ситуацию, описанную в теме. У разработчика есть временная таблица втТаб, содержащая идентификаторы товаров (в данном случае GoodsId). Он пытается присоединить к ней регистр ДополнительныеСведения, чтобы получить некое свойство объекта. Однако в результате он видит только те товары, которые уже есть в регистре, а товары, у которых свойство не заполнено, пропадают из результата.

Проанализируем исходный код запроса, который вызвал проблему:


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

Выясним причину такого поведения. Секция ГДЕ (WHERE) применяется к уже сформированному результату соединения таблиц. Когда для какого-то GoodsId в регистре сведений нет записи, поля правой таблицы (включая ДополнительныеСведения.Свойство) принимают значение NULL. Условие NULL = &Свойство всегда возвращает "Ложь" (или "Неизвестно"), и такая строка исключается из финальной выборки. Таким образом, левое соединение фактически превращается во внутреннее.

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

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

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

Рассмотрим правильный вариант запроса:


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

В этом случае мы получим именно тот результат, который ожидал автор темы: все записи из втТаб будут присутствовать в ответе, а колонка Объект будет заполнена либо ссылкой, либо значением NULL.

Решение №2: Использование вложенного запроса

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

Разберем этот метод по шагам:

  1. Создаем вложенный запрос, который выбирает данные только по нужному свойству.
  2. Присваиваем ему псевдоним.
  3. Выполняем левое соединение с этим запросом.

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

Этот подход особенно эффективен, если в будущем вам потребуется накладывать дополнительные сложные условия на данные регистра, которые нежелательно загромождать в секции ПО.

Работа с неопределенными значениями (ЕСТЬNULL)

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

Рассмотрим использование функции ЕСТЬNULL. Она позволяет подставить заданное значение, если в поле пришел NULL. Посмотрим на пример:


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

Здесь, если объект не найден, в результате вместо NULL будет стоять пустая ссылка справочника Номенклатура, что более корректно для вывода в отчеты или дальнейшей обработки в коде 1С.

Оптимизация: Составные типы и индексация

Проанализируем еще один важный аспект, упомянутый в контексте регистра ДополнительныеСведения. Поле Объект в этом регистре имеет составной тип данных. В запросах 1С работа с составными типами при соединении может быть медленной, так как СУБД приходится выполнять дополнительные проверки типов.

Для оптимизации рекомендуется использовать функцию ВЫРАЗИТЬ. Это позволяет явно указать системе, с каким типом данных мы работаем, что помогает оптимизатору запросов работать быстрее. Рассмотрим пример:


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

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

Резюме

Подведем итоги нашего анализа работы с левыми соединениями:

Следуя этим правилам, вы сможете писать надежные и производительные запросы, избегая распространенных ошибок при разработке на платформе 1С:Предприятие.

← На главную