При составлении запросов в системе 1С:Предприятие левое соединение (LEFT JOIN) является одной из самых часто используемых операций. Однако у начинающих разработчиков нередко возникает вопрос: в каких именно полях результирующего набора данных может появиться значение NULL? Всегда ли оно образуется только в правой таблице, или при определенных обстоятельствах его можно встретить и в левой?
В этой статье мы подробно разберем механизм работы левого соединения, выясним причины появления значений NULL в левой и правой таблицах, а также рассмотрим особенности работы с трехзначной логикой в СУБД.
Для начала вспомним классическое определение левого внешнего соединения. При левом соединении Таблицы 1 (левой) и Таблицы 2 (правой) в результат запроса гарантированно попадают абсолютно все строки из левой таблицы.
Если для строки из левой таблицы в правой таблице находится соответствие по условию соединения (секция ПО), то поля обеих таблиц заполняются сопоставленными данными. Если же соответствия в правой таблице не нашлось, то на месте полей правой таблицы в результирующем наборе данных СУБД возвращает специальное значение — NULL.
Проанализируем классический практический пример. Нам необходимо вывести список всех контрагентов и суммы их продаж из оборотного регистра. Мы соединяем справочник Контрагенты (левая таблица) с виртуальной таблицей Продажи.Обороты (правая таблица):
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
ПродажиОбороты.СуммаОборот КАК Сумма
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты
ПО Контрагенты.Ссылка = ПродажиОбороты.Контрагент
Если в нашей базе есть контрагенты, по которым не было совершено ни одной продажи, то строки с этими контрагентами все равно попадут в итоговый отчет. Однако в поле Сумма (которое берется из правой таблицы) для них запишется значение NULL, так как в регистре продаж по ним нет записей.
Ответ на этот вопрос однозначный: да, может. Однако само по себе левое соединение не способно сгенерировать NULL в левой таблице. Если в левой таблице появилось это значение, значит, оно находилось там еще до момента соединения. Выясним основные причины, почему это происходит:
NULL (например, из-за предыдущих соединений или расчетов), то эти значения перейдут и в итоговое левое соединение.NULL. Например, при выводе поля Контрагент.ИНН для папки контрагентов СУБД вернет NULL, поскольку у папки ИНН существовать не может.Помимо явного левого соединения, платформа 1С и СУБД могут возвращать NULL в ряде других типовых ситуаций, которые важно учитывать при разработке:
Документ.Контрагент.ОсновнойБанковскийСчет, платформа под капотом транслирует это в левое соединение с соответствующей таблицей. Если ссылка на контрагента в документе не заполнена или является «битой» (указывает на несуществующий объект), результатом обращения к реквизиту через точку станет NULL.Сделка.АдресДоставки), то для строк со вторым типом документа результатом будет NULL.СУММА, МИНИМУМ, МАКСИМУМ и СРЕДНЕЕ, если они рассчитываются по пустой таблице или пустой группе строк, всегда возвращают NULL. Исключением является функция КОЛИЧЕСТВО, которая в аналогичной ситуации вернет число 0.NULL напрямую.Работа со значением NULL требует четкого понимания правил трехзначной логики (Истина, Ложь, Неизвестно). Проанализируем ключевые нюансы:
Поле = NULL или Поле <> NULL в языке запросов 1С всегда будут возвращать значение «Ложь». Проверить поле на неопределенность СУБД можно исключительно с помощью операторов ЕСТЬ NULL или НЕ ЕСТЬ NULL.Таб1.Поле = Таб2.Поле, и в обеих таблицах в этом поле содержится NULL, такие строки не соединятся, так как в SQL равенство NULL = NULL не считается истинным.NULL, вернет в результате NULL. Например, если сложить число и NULL, результат будет не определен (вернется NULL).ЕСТЬNULL() внутри секций ГДЕ или в условиях связи ПО. Конструкции вида ГДЕ ЕСТЬNULL(Таб.Реквизит, 0) = 0 делают невозможным использование индексов по этому полю СУБД, что приводит к полному сканированию таблицы (Table Scan) и критическому падению производительности.NULL с пустыми значениями других типов. Значение NULL (отсутствие данных на уровне базы) не равно пустой ссылке справочника, числу 0, пустой строке "", пустой дате '00010101' или системному типу Неопределено.Для корректного вывода данных и предотвращения ошибок при вычислениях используем проверенные на практике приемы:
Функция ЕСТЬNULL(Поле, ЗначениеЗамены) проверяет значение первого параметра. Если оно равно NULL, функция возвращает второй параметр. Обязательно применяем её при сложении числовых показателей, полученных из правых таблиц:
ВЫБРАТЬ
ЕСТЬNULL(Продажи.СуммаОборот, 0) + ЕСТЬNULL(Закупки.СуммаОборот, 0) КАК ОбщийОборот
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК Продажи ПО ...
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Закупки.Обороты КАК Закупки ПО ...
В случаях, когда требуется более сложная логика замены (например, вывод текстового сообщения или подстановка другого поля), используем стандартную условную конструкцию:
ВЫБРАТЬ
ВЫБОР
КОГДА ПродажиОбороты.СуммаОборот ЕСТЬ NULL ТОГДА "Продаж не было"
ИНАЧЕ "Продажи зарегистрированы"
КОНЕЦ КАК СтатусАктивности
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты ПО ...
Если в вашем пакете запросов планируется дальнейшее соединение по полям, которые могут содержать NULL, или по ним будет выполняться частая фильтрация, преобразуйте их к фиксированным дефолтным значениям (например, с помощью ЕСТЬNULL) на этапе записи во временную таблицу. Это позволит СУБД построить эффективные индексы для временной таблицы и ускорить выполнение последующих запросов пакета.