При работе с запросами в 1С начинающие разработчики часто сталкиваются с ситуацией, когда ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) внезапно начинает работать как ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN). Это приводит к тому, что из итоговой выборки исчезают строки основной (левой) таблицы, для которых не нашлось соответствий в правой таблице. Сегодня мы подробно разберем, почему это происходит и как правильно составлять такие запросы на примере работы с регистром сведений ДополнительныеСведения.
Рассмотрим ситуацию, описанную в теме. У разработчика есть временная таблица втТаб, содержащая идентификаторы товаров (в данном случае GoodsId). Он пытается присоединить к ней регистр ДополнительныеСведения, чтобы получить некое свойство объекта. Однако в результате он видит только те товары, которые уже есть в регистре, а товары, у которых свойство не заполнено, пропадают из результата.
Проанализируем исходный код запроса, который вызвал проблему:
ВЫБРАТЬ
втТаб.GoodsId КАК GoodsId,
ДополнительныеСведения.Объект КАК Объект
ИЗ
втТаб КАК втТаб
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
втТаб.GoodsId = ДополнительныеСведения.Значение
ГДЕ
ДополнительныеСведения.Свойство = &Свойство
Выясним причину такого поведения. Секция ГДЕ (WHERE) применяется к уже сформированному результату соединения таблиц. Когда для какого-то GoodsId в регистре сведений нет записи, поля правой таблицы (включая ДополнительныеСведения.Свойство) принимают значение NULL. Условие NULL = &Свойство всегда возвращает "Ложь" (или "Неизвестно"), и такая строка исключается из финальной выборки. Таким образом, левое соединение фактически превращается во внутреннее.
Самый простой и правильный способ решить эту задачу — перенести фильтрацию по свойствам правой таблицы непосредственно в условие связи ПО (ON). Проанализируем, как изменится логика выполнения запроса в этом случае.
При использовании условия в секции ПО, система сначала ограничивает записи правой таблицы (выбирает только те, где свойство соответствует параметру), а затем уже выполняет левое соединение с основной таблицей. Если для записи из левой таблицы не найдется подходящей записи в правой (с нужным свойством), запись все равно попадет в результат, а поля правой таблицы будут заполнены значениями NULL.
Рассмотрим правильный вариант запроса:
ВЫБРАТЬ
втТаб.GoodsId КАК GoodsId,
ДополнительныеСведения.Объект КАК Объект
ИЗ
втТаб КАК втТаб
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
втТаб.GoodsId = ДополнительныеСведения.Значение
И ДополнительныеСведения.Свойство = &Свойство
В этом случае мы получим именно тот результат, который ожидал автор темы: все записи из втТаб будут присутствовать в ответе, а колонка Объект будет заполнена либо ссылкой, либо значением NULL.
В некоторых случаях для повышения читаемости или при сложной логике фильтрации удобно использовать вложенный запрос. Мы можем сначала подготовить выборку из регистра сведений, а затем соединить её с нашей таблицей. Это позволяет четко разделить логику отбора данных и логику их объединения.
Разберем этот метод по шагам:
ВЫБРАТЬ
втТаб.GoodsId КАК GoodsId,
втПодзапрос.Объект КАК Объект
ИЗ
втТаб КАК втТаб
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
ДопСведения.Значение КАК Значение,
ДопСведения.Объект КАК Объект
ИЗ
РегистрСведений.ДополнительныеСведения КАК ДопСведения
ГДЕ
ДопСведения.Свойство = &Свойство
) КАК втПодзапрос
ПО
втТаб.GoodsId = втПодзапрос.Значение
Этот подход особенно эффективен, если в будущем вам потребуется накладывать дополнительные сложные условия на данные регистра, которые нежелательно загромождать в секции ПО.
При левом соединении очень важно правильно обрабатывать результат, когда запись в правой таблице не найдена. Вместо системного значения NULL пользователю часто удобнее видеть пустую ссылку или какой-то текст.
Рассмотрим использование функции ЕСТЬNULL. Она позволяет подставить заданное значение, если в поле пришел NULL. Посмотрим на пример:
ВЫБРАТЬ
втТаб.GoodsId КАК ИсходныйИдентификатор,
ЕСТЬNULL(ДополнительныеСведения.Объект, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК НайденныйОбъект
ИЗ
втТаб КАК втТаб
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
втТаб.GoodsId = ДополнительныеСведения.Значение
И ДополнительныеСведения.Свойство = &Свойство
Здесь, если объект не найден, в результате вместо NULL будет стоять пустая ссылка справочника Номенклатура, что более корректно для вывода в отчеты или дальнейшей обработки в коде 1С.
Проанализируем еще один важный аспект, упомянутый в контексте регистра ДополнительныеСведения. Поле Объект в этом регистре имеет составной тип данных. В запросах 1С работа с составными типами при соединении может быть медленной, так как СУБД приходится выполнять дополнительные проверки типов.
Для оптимизации рекомендуется использовать функцию ВЫРАЗИТЬ. Это позволяет явно указать системе, с каким типом данных мы работаем, что помогает оптимизатору запросов работать быстрее. Рассмотрим пример:
ВЫБРАТЬ
втТаб.GoodsId КАК GoodsId,
ВЫРАЗИТЬ(ДополнительныеСведения.Объект КАК Справочник.Номенклатура) КАК ОбъектНоменклатура
ИЗ
втТаб КАК втТаб
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрСведений.ДополнительныеСведения КАК ДополнительныеСведения
ПО
втТаб.GoodsId = ДополнительныеСведения.Значение
И ДополнительныеСведения.Свойство = &Свойство
Важный нюанс по производительности: Поле Значение в стандартном регистре сведений ДополнительныеСведения обычно не индексируется, так как оно предназначено для хранения данных, а не для поиска объектов по ним. Если ваша таблица втТаб содержит тысячи записей, поиск по ДополнительныеСведения.Значение = втТаб.GoodsId будет выполняться крайне медленно (сканирование всей таблицы регистра). Если есть возможность, старайтесь выполнять соединение по полям Объект и Свойство, так как они входят в состав основного индекса регистра.
Подведем итоги нашего анализа работы с левыми соединениями:
ГДЕ, если хотите сохранить все записи левой таблицы.ПО для наложения условий на правую таблицу в момент соединения.ЕСТЬNULL для обработки пустых результатов и приведения их к понятному виду.ВЫРАЗИТЬ.Значение в доп. сведениях) может существенно замедлить работу программы на больших объемах данных.Следуя этим правилам, вы сможете писать надежные и производительные запросы, избегая распространенных ошибок при разработке на платформе 1С:Предприятие.