При написании запросов в платформе «1С:Предприятие» разработчики часто сталкиваются с ситуацией, когда классическое ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) внезапно начинает вести себя как жесткое ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN). Из итоговой выборки пропадают строки левой (основной) таблицы, для которых не нашлось соответствия в правой (присоединяемой) таблице.
Рассмотрим эту проблему на практическом примере: нам необходимо вывести все строки из табличной части «Товары» документов РеализацияТоваровУслуг, сопоставив их со штрихкодами определенного типа из регистра сведений Штрихкоды. Мы хотим видеть абсолютно все номенклатурные позиции документа, независимо от того, зарегистрирован ли для них штрихкод в базе данных.
Посмотрим на исходный вариант запроса, который возвращает некорректный результат (выводятся только товары со штрихкодами):
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Ссылка,
РеализацияТоваровУслугТовары.НомерСтроки КАК НомерСтроки,
РеализацияТоваровУслугТовары.Номенклатура,
РеализацияТоваровУслугТовары.ЕдиницаИзмерения,
Штрихкоды.Штрихкод,
РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
ПО РеализацияТоваровУслугТовары.Номенклатура = Штрихкоды.Владелец
И РеализацияТоваровУслугТовары.ЕдиницаИзмерения = Штрихкоды.ЕдиницаИзмерения
И РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры = Штрихкоды.ХарактеристикаНоменклатуры
ГДЕ
РеализацияТоваровУслугТовары.Ссылка.Проведен = ИСТИНА
И РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И РеализацияТоваровУслугТовары.Ссылка.Контрагент = &Контрагент
И Штрихкоды.ТипШтрихкода = &ТипШтрихкода
Выясним причину, почему этот тривиальный запрос отсекает строки товаров без штрихкодов. Для этого обратимся к логическому порядку выполнения операций СУБД под капотом:
Штрихкоды.ТипШтрихкода) образуются значения NULL.Штрихкоды.ТипШтрихкода = &ТипШтрихкода, система пытается сравнить NULL = &ТипШтрихкода.По правилам трехзначной логики SQL, любое сравнение значения NULL с конкретным параметром (даже проверка на неравенство) всегда возвращает логический результат UNKNOWN (неизвестно). При фильтрации строк это значение приравнивается к лжи, и СУБД отбрасывает данную строку. В итоге все строки левой таблицы, для которых штрихкод не был найден, просто удаляются из результата.
Чтобы левое соединение сохранило свою суть, условия фильтрации по правой (присоединяемой) таблице необходимо перенести из секции ГДЕ в секцию ПО (условие связи). В таком случае СУБД сначала отфильтрует правую таблицу по типу штрихкода, а затем выполнит левое соединение со всеми строками товаров.
Разберем исправленный вариант запроса:
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Ссылка,
РеализацияТоваровУслугТовары.НомерСтроки КАК НомерСтроки,
РеализацияТоваровУслугТовары.Номенклатура,
РеализацияТоваровУслугТовары.ЕдиницаИзмерения,
ЕСТЬNULL(Штрихкоды.Штрихкод, "") КАК Штрихкод,
РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
ПО РеализацияТоваровУслугТовары.Номенклатура = Штрихкоды.Владелец
И РеализацияТоваровУслугТовары.ЕдиницаИзмерения = Штрихкоды.ЕдиницаИзмерения
И РеализацияТоваровУслугТовары.ХарактеристикаНоменклатуры = Штрихкоды.ХарактеристикаНоменклатуры
И Штрихкоды.ТипШтрихкода = &ТипШтрихкода
ГДЕ
РеализацияТоваровУслугТовары.Ссылка.Проведен = ИСТИНА
И РеализацияТоваровУслугТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И РеализацияТоваровУслугТовары.Ссылка.Контрагент = &Контрагент
Обратите внимание на использование функции ЕСТЬNULL(Штрихкоды.Штрихкод, "") в списке выбора. Она заменяет системные значения NULL пустой строкой, улучшая визуальное восприятие данных пользователем.
Хотя исправленный запрос решает функциональную задачу, с точки зрения стандартов производительности 1С он содержит критические недостатки. Проанализируем их и выполним профессиональную оптимизацию.
В секции ГДЕ мы видим обращения к реквизитам шапки документа через поле Ссылка табличной части (Ссылка.Проведен, Ссылка.Дата, Ссылка.Контрагент). Каждое такое обращение «через точку» заставляет СУБД неявно создавать дополнительные левые соединения с таблицей документов. Три таких обращения создают три скрытых соединения, серьезно нагружая планировщик СУБД.
Для оптимизации мы заменим неявные обращения на одно явное ВНУТРЕННЕЕ СОЕДИНЕНИЕ табличной части с шапкой документа.
Если регистр сведений Штрихкоды содержит сотни тысяч или миллионы записей, соединение «на лету» может сильно снизить скорость выполнения. В соответствии с лучшими практиками разработки 1С, рекомендуется предварительно отбирать штрихкоды нужного типа во временную таблицу, индексировать ее по связующим полям и только после этого соединять с табличной частью товаров.
Посмотрим на финальный, полностью оптимизированный вариант запроса:
ВЫБРАТЬ
Штрихкоды.Владелец КАК Номенклатура,
Штрихкоды.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
Штрихкоды.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
Штрихкоды.Штрихкод КАК Штрихкод
ПОМЕСТИТЬ ВТ_Штрихкоды
ИЗ
РегистрСведений.Штрихкоды КАК Штрихкоды
ГДЕ
Штрихкоды.ТипШтрихкода = &ТипШтрихкода
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
ХарактеристикаНоменклатуры,
ЕдиницаИзмерения
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Товары.Ссылка КАК Ссылка,
Товары.НомерСтроки КАК НомерСтроки,
Товары.Номенклатура КАК Номенклатура,
Товары.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
ЕСТЬNULL(ВТ_Штрихкоды.Штрихкод, "") КАК Штрихкод,
Товары.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры
ИЗ
Документ.RealizacijaТоваровУслуг.Товары КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК ШапкаДокумента
ПО Товары.Ссылка = ШапкаДокумента.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Штрихкоды КАК ВТ_Штрихкоды
ПО Товары.Номенклатура = ВТ_Штрихкоды.Номенклатура
И Товары.ХарактеристикаНоменклатуры = ВТ_Штрихкоды.ХарактеристикаНоменклатуры
И Товары.ЕдиницаИзмерения = ВТ_Штрихкоды.ЕдиницаИзмерения
ГДЕ
ШапкаДокумента.Проведен = ИСТИНА
И ШапкаДокумента.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
И ШапкаДокумента.Контрагент = &Контрагент
Иногда на форумах предлагают обходной путь — оставить условие в секции ГДЕ, но дополнить его проверкой на пустое значение:
ГДЕ
...
И (Штрихкоды.ТипШтрихкода = &ТипШтрихкода ИЛИ Штрихкоды.ТипШтрихкода ЕСТЬ NULL)
Важное предупреждение: использование логического оператора ИЛИ (или конструкции ВЫБОР КОГДА...) в секции ГДЕ по полям правой таблицы левого соединения является грубой ошибкой оптимизации! Подобное условие полностью исключает возможность использования индексов СУБД. База данных будет вынуждена принудительно сканировать всю таблицу штрихкодов (Index Scan / Table Scan), что приведет к катастрофическому падению скорости работы запроса и избыточным блокировкам в многопользовательском режиме.
Придерживаясь правила переноса условий фильтрации в секцию ПО и используя временные таблицы, вы гарантируете стабильно высокую скорость работы системы даже при колоссальных объемах данных в информационной базе.