Почему в запросах 1С NULL не равен NULL и как правильно сравнивать пустые значения?

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

При работе с запросами в системе «1С:Предприятие» начинающие (а иногда и опытные) разработчики сталкиваются с неочевидным поведением: когда в двух таблицах в полях содержатся значения NULL, их сравнение через обычный оператор равенства = не дает ожидаемого результата. В этой статье мы подробно разберем, почему так происходит, как работает троичная логика SQL и какие методы позволяют корректно соединять таблицы по полям, содержащим неопределенные значения.

Причина проблемы: Троичная логика (3VL)

Разберем ситуацию, с которой столкнулся автор темы. При выполнении левого соединения двух временных таблиц по полю, в котором в обеих таблицах стоит NULL, связь не устанавливается. На первый взгляд это кажется странным: если «ничего» сравнивается с «ничем», они должны быть равны. Однако в языке запросов SQL (на котором базируется язык запросов 1С) NULL — это не значение, а маркер отсутствия значения или неизвестности.

Проанализируем работу системы. Согласно стандартам SQL, любое сравнение с NULL возвращает не «Истину» и не «Ложь», а состояние «Неизвестно» (Unknown). Поскольку условия в секциях ГДЕ или ПО (в соединениях) пропускают только те записи, для которых результат условия строго равен «Истина», строки с NULL отсекаются. Интересно, что даже инверсия сравнения (НЕ NULL = NULL) также вернет «Неизвестно», и результат не изменится.

Важный нюанс: В языке программирования 1С (в программном коде) выражение NULL = NULL вернет Истина. Это ключевое различие между поведением платформы в коде и в языке запросов, которое нужно запомнить.

Решение 1: Использование оператора ЕСТЬ NULL

Если нам необходимо проверить поле на наличие NULL, мы не можем использовать Поле = NULL. Для этого в языке запросов существует специальный оператор ЕСТЬ NULL. Рассмотрим правильный синтаксис:


ВЫБРАТЬ
    ВТ_Первая.Ссылка
ИЗ
    ВТ_Первая КАК ВТ_Первая
ГДЕ
    ВТ_Первая.Реквизит ЕСТЬ NULL

Если же мы хотим найти записи, где значение заполнено, используем обратную конструкцию: ЕСТЬ НЕ NULL.

Решение 2: Корректное соединение таблиц по NULL-полям

Когда стоит задача соединить две таблицы по полю, которое может содержать NULL, и при этом мы хотим, чтобы NULL из первой таблицы «зацепился» за NULL из второй, классическое условие ПО Т1.Поле = Т2.Поле не сработает. Рассмотрим, как прописать условие соединения правильно, чтобы избежать потери данных:


ВЫБРАТЬ
    ВТ_Первая.Поле1,
    ВТ_Вторая.Поле1 КАК Поле11
ИЗ
    ВТ_Первая КАК ВТ_Первая
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Вторая КАК ВТ_Вторая
    ПО (ВТ_Первая.ПолеСвязи = ВТ_Вторая.ПолеСвязи)
    ИЛИ (ВТ_Первая.ПолеСвязи ЕСТЬ NULL И ВТ_Вторая.ПолеСвязи ЕСТЬ NULL)

Внимание: Будьте осторожны с таким подходом. Как отмечают участники обсуждения, если в обеих таблицах много строк со значением NULL, такое условие приведет к «размножению» записей (декартову произведению для этих строк). Например, если в первой таблице 100 строк с NULL и во второй 100 строк с NULL, вы получите 10 000 строк в результате.

Решение 3: Использование функции ЕСТЬNULL()

Одним из самых популярных способов обхода проблемы является замена NULL на какое-то конкретное значение «заглушку» прямо в процессе сравнения или соединения. Для этого используется функция ЕСТЬNULL(Значение, Подстановка).

Рассмотрим пример, где мы заменяем NULL на 0 или пустую ссылку для корректного сравнения:


ВЫБРАТЬ
    ВТ_Первая.Поле1
ИЗ
    ВТ_Первая КАК ВТ_Первая
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Вторая КАК ВТ_Вторая
    ПО ЕСТЬNULL(ВТ_Первая.ПолеNULL, 0) = ЕСТЬNULL(ВТ_Вторая.ПолеNULL, 0)

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

  1. Производительность: На тяжелых запросах и больших таблицах использование функций в условиях соединения (JOIN) может привести к тому, что СУБД не сможет использовать индексы. Это значительно замедлит выполнение запроса.
  2. Уникальность заглушки: В качестве второго параметра функции ЕСТЬNULL нужно выбирать значение, которое гарантированно не встречается в реальных данных таблицы, иначе вы получите ложные срабатывания соединения.

Где еще прячется NULL в 1С?

Выясним причины появления NULL в данных, помимо результата левого соединения. Это поможет лучше понимать структуру базы данных:

Специфика СКД и параметров периода

В ходе обсуждения был затронут важный нюанс работы Системы компоновки данных (СКД), связанный с параметрами виртуальных таблиц. Если в отчете используются стандартные параметры &НачалоПериода и &КонецПериода, СКД может игнорировать ваши пользовательские настройки периодов для виртуальных таблиц (например, Хозрасчетный.Обороты), если они не оформлены должным образом.

Чтобы избежать ошибок при формировании отчетов, когда система подставляет «дефолтные» даты вместо указанных вами, рекомендуется:

  1. Устанавливать использование параметров в значение «Всегда» в настройках набора данных.
  2. Оформлять параметры виртуальных таблиц с использованием фигурных скобок {} в тексте запроса, чтобы СКД корректно подхватывала их при автозаполнении.

Различия между NULL, Неопределено и Пустой ссылкой

Для качественной разработки важно четко разделять эти три понятия, так как их часто путают в контексте пустых значений:

  1. NULL: Означает полное отсутствие данных. Не имеет типа. Появляется в запросах при левых соединениях или в реквизитах, не имеющих значения на уровне БД.
  2. Неопределено: Значение особого типа Неопределено. Используется в коде 1С (например, когда переменная объявлена, но не инициализирована) или в полях составного типа на формах. В запросе NULL и Неопределено — это разные вещи.
  3. Пустая ссылка: Это реальное значение конкретного типа. Она имеет тип (например, СправочникСсылка.Контрагенты) и уникальный идентификатор, состоящий из нулей. Пустая ссылка никогда не равна NULL.

Посмотрим на классический пример ошибки. Если вы попытаетесь проверить пустую ссылку в запросе так:

ГДЕ Контрагенты.Ссылка ЕСТЬ NULL // Это не найдет пустые ссылки!

Правильный вариант для поиска пустых ссылок:

ГДЕ Контрагенты.Ссылка = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)

Интересный факт о ИЕРАРХИИ

Разберем еще одну особенность, упомянутую в обсуждении. Если выполнить запрос с условием В ИЕРАРХИИ(&Параметр) и передать в параметр Пустую ссылку, запрос вернет все элементы справочника. Это поведение платформы по умолчанию: пустая ссылка считается корнем любого иерархического справочника, поэтому в иерархию «корня» попадает всё дерево данных. Если ваша задача — выбрать только те элементы, у которых нет родителя, используйте условие ГДЕ Родитель = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).

Подводя итог, можно сказать, что работа с NULL требует особого внимания к деталям. Основное правило: никогда не используйте = или <> с NULL в запросах. Используйте специализированный оператор ЕСТЬ NULL или функцию ЕСТЬNULL(), предварительно проанализировав объем данных и возможные потери производительности.

← На главную