При работе с языком запросов 1С начинающие разработчики часто сталкиваются с ситуацией: выполняется конструкция ЛЕВОЕ СОЕДИНЕНИЕ, но если в присоединяемой (правой) таблице нет записей или они не подходят под фильтр, из итогового результата полностью пропадают строки левой таблицы. Разберем, почему так происходит, как работает механизм соединения таблиц СУБД и как правильно составлять такие запросы.
Рассмотрим стандартный порядок выполнения запроса СУБД. Когда мы пишем запрос с левым соединением, СУБД сначала формирует промежуточный результат соединения. В тех строках, где для записи из левой таблицы не нашлось пары из правой таблицы, поля правой таблицы заполняются значением NULL.
Если после этого в секции ГДЕ накладывается прямое условие на реквизит правой таблицы, например:
ГДЕ
ПоступлениеТоваровУслугРазделыГТД.Ссылка = &Ссылка
Происходит следующее: для строк, где записи из правой таблицы отсутствовали, выражение ПоступлениеТоваровУслугРазделыГТД.Ссылка имеет значение NULL. Сравнение NULL = &Ссылка возвращает значение ЛОЖЬ (или НЕОПРЕДЕЛЕНО). В результате секция ГДЕ отсекает абсолютно все строки с NULL. Таким образом, левое соединение фактически начинает отрабатывать как ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
Разберем три варианта решения этой задачи и проанализируем технические нюансы каждого подхода.
Самый простой и правильный способ сохранить логику левого соединения без создания дополнительных временных таблиц — перенести условия, относящиеся к присоединяемой таблице, непосредственно в предложение ПО (условие связи).
При таком подходе фильтрация правой таблицы происходит до или в момент соединения. Если правая таблица не содержит подходящих строк, слева данные все равно остаются, а поля правой таблицы заполняются значениями NULL.
Посмотрим на исправление исходного запроса:
ВЫБРАТЬ
ПоступлениеТоваровУслугТовары.НомерСтроки КАК НомерСтроки,
ПоступлениеТоваровУслугТовары.Номенклатура.КодТНВЭД КАК КодТНВЭД,
ПоступлениеТоваровУслугТовары.Сумма КАК Сумма,
ПоступлениеТоваровУслугТовары.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА ЕСТЬNULL(ПоступлениеТоваровУслугТовары.Вес, 0) > 0
ТОГДА ПоступлениеТоваровУслугТовары.Вес
ИНАЧЕ ПоступлениеТоваровУслугТовары.Количество
КОНЕЦ КАК Количество,
ПоступлениеТоваровУслугТовары.Цена КАК Цена,
ПоступлениеТоваровУслугТовары.Сумма * ПоступлениеТоваровУслугТовары.КурсУпр КАК СуммаРуб,
ПоступлениеТоваровУслугРазделыГТД.СуммаПошлины КАК СуммаПошлины,
ПоступлениеТоваровУслугРазделыГТД.СуммаНДС КАК СуммаНДС
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.РазделыГТД КАК ПоступлениеТоваровУслугРазделыГТД
ПО ПоступлениеТоваровУслугТовары.Ссылка = ПоступлениеТоваровУслугРазделыГТД.Ссылка
И ПоступлениеТоваровУслугТовары.Номенклатура.КодТНВЭД = ПоступлениеТоваровУслугРазделыГТД.КодТНВЭД
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &Ссылка
УПОРЯДОЧИТЬ ПО
НомерСтроки
ИТОГИ
СУММА(Сумма)
ПО
ОБЩИЕ,
КодТНВЭД
Другой надежный способ — предварительно выбрать данные из обеих табличных частей во временные таблицы, применив отбор по документу к каждой из них отдельно, и затем соединить уже подготовленные временные таблицы.
Проанализируем пример кода с временными таблицами:
ВЫБРАТЬ
ПоступлениеТоваровУслугТовары.НомерСтроки КАК НомерСтроки,
ПоступлениеТоваровУслугТовары.Номенклатура.КодТНВЭД КАК КодТНВЭД,
ПоступлениеТоваровУслугТовары.Сумма КАК Сумма,
ПоступлениеТоваровУслугТовары.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА ЕСТЬNULL(ПоступлениеТоваровУслугТовары.Вес, 0) > 0
ТОГДА ПоступлениеТоваровУслугТовары.Вес
ИНАЧЕ ПоступлениеТоваровУслугТовары.Количество
КОНЕЦ КАК Количество,
ПоступлениеТоваровУслугТовары.Цена КАК Цена,
ПоступлениеТоваровУслугТовары.Сумма * ПоступлениеТоваровУслугТовары.КурсУпр КАК СуммаРуб
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПоступлениеТоваровУслугРазделыГТД.КодТНВЭД КАК КодТНВЭД,
ПоступлениеТоваровУслугРазделыГТД.СуммаПошлины КАК СуммаПошлины,
ПоступлениеТоваровУслугРазделыГТД.СуммаНДС КАК СуммаНДС
ПОМЕСТИТЬ ВТ_РазделыГТД
ИЗ
Документ.ПоступлениеТоваровУслуг.РазделыГТД КАК ПоступлениеТоваровУслугРазделыГТД
ГДЕ
ПоступлениеТоваровУслугРазделыГТД.Ссылка = &Ссылка
ИНИЕКСИРОВАТЬ ПО
КодТНВЭД
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Товары.НомерСтроки КАК НомерСтроки,
ВТ_Товары.КодТНВЭД КАК КодТНВЭД,
ВТ_Товары.Сумма КАК Сумма,
ВТ_Товары.Номенклатура КАК Номенклатура,
ВТ_Товары.Количество КАК Количество,
ВТ_Товары.Цена КАК Цена,
ВТ_Товары.СуммаРуб КАК СуммаРуб,
ЕСТЬNULL(ВТ_РазделыГТД.СуммаПошлины, 0) КАК СуммаПошлины,
ЕСТЬNULL(ВТ_РазделыГТД.СуммаНДС, 0) КАК СуммаНДС
ИЗ
ВТ_Товары КАК ВТ_Товары
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РазделыГТД КАК ВТ_РазделыГТД
ПО ВТ_Товары.КодТНВЭД = ВТ_РазделыГТД.КодТНВЭД
УПОРЯДОЧИТЬ ПО
НомерСтроки
ИТОГИ
СУММА(Сумма)
ПО
ОБЩИЕ,
КодТНВЭД
При выборе решения следует учитывать технические особенности работы платформы и СУБД:
Ссылка.Ссылка: В исходных запросах встречается обращение Ссылка.Ссылка. Это создает лишнюю нагрузку при трансляции запроса platform1C в SQL-код. Всегда следует писать прямое обращение к полю: Ссылка = &Ссылка.ПоступлениеТоваровУслугТовары.Номенклатура.КодТНВЭД заставляет платформу автоматически создавать неявное ЛЕВОЕ СОЕДИНЕНИЕ со справочником «Номенклатура». Если это соединение используется непосредственно в блоке ПО, оптимизатору СУБД сложно построить эффективный план выполнения. При использовании временных таблиц реквизит вычисляется один раз, что упрощает итоговый план.КодТНВЭД обязательно используйте ключевое слово ИНДЕКСИРОВАТЬ ПО. Без индекса СУБД будет вынуждена выполнять полное сканирование таблицы в памяти или файле tempdb.Для выборки по одному документу запрос из Способа 1 (без временных таблиц) отработает быстрее, так как отсутствует накладной расход на создание, запись и удаление временных таблиц в базе СУБД.
Для выборки данных по большому массиву документов предпочтительнее использовать временные таблицы с явной индексацией по полям связи.