При работе с запросами в системе «1С:Предприятие» начинающие (а иногда и опытные) разработчики сталкиваются с неочевидным поведением: когда в двух таблицах в полях содержатся значения NULL, их сравнение через обычный оператор равенства = не дает ожидаемого результата. В этой статье мы подробно разберем, почему так происходит, как работает троичная логика SQL и какие методы позволяют корректно соединять таблицы по полям, содержащим неопределенные значения.
Разберем ситуацию, с которой столкнулся автор темы. При выполнении левого соединения двух временных таблиц по полю, в котором в обеих таблицах стоит NULL, связь не устанавливается. На первый взгляд это кажется странным: если «ничего» сравнивается с «ничем», они должны быть равны. Однако в языке запросов SQL (на котором базируется язык запросов 1С) NULL — это не значение, а маркер отсутствия значения или неизвестности.
Проанализируем работу системы. Согласно стандартам SQL, любое сравнение с NULL возвращает не «Истину» и не «Ложь», а состояние «Неизвестно» (Unknown). Поскольку условия в секциях ГДЕ или ПО (в соединениях) пропускают только те записи, для которых результат условия строго равен «Истина», строки с NULL отсекаются. Интересно, что даже инверсия сравнения (НЕ NULL = NULL) также вернет «Неизвестно», и результат не изменится.
Важный нюанс: В языке программирования 1С (в программном коде) выражение NULL = NULL вернет Истина. Это ключевое различие между поведением платформы в коде и в языке запросов, которое нужно запомнить.
Если нам необходимо проверить поле на наличие NULL, мы не можем использовать Поле = NULL. Для этого в языке запросов существует специальный оператор ЕСТЬ NULL. Рассмотрим правильный синтаксис:
ВЫБРАТЬ
ВТ_Первая.Ссылка
ИЗ
ВТ_Первая КАК ВТ_Первая
ГДЕ
ВТ_Первая.Реквизит ЕСТЬ NULL
Если же мы хотим найти записи, где значение заполнено, используем обратную конструкцию: ЕСТЬ НЕ NULL.
Когда стоит задача соединить две таблицы по полю, которое может содержать NULL, и при этом мы хотим, чтобы NULL из первой таблицы «зацепился» за NULL из второй, классическое условие ПО Т1.Поле = Т2.Поле не сработает. Рассмотрим, как прописать условие соединения правильно, чтобы избежать потери данных:
ВЫБРАТЬ
ВТ_Первая.Поле1,
ВТ_Вторая.Поле1 КАК Поле11
ИЗ
ВТ_Первая КАК ВТ_Первая
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Вторая КАК ВТ_Вторая
ПО (ВТ_Первая.ПолеСвязи = ВТ_Вторая.ПолеСвязи)
ИЛИ (ВТ_Первая.ПолеСвязи ЕСТЬ NULL И ВТ_Вторая.ПолеСвязи ЕСТЬ NULL)
Внимание: Будьте осторожны с таким подходом. Как отмечают участники обсуждения, если в обеих таблицах много строк со значением NULL, такое условие приведет к «размножению» записей (декартову произведению для этих строк). Например, если в первой таблице 100 строк с NULL и во второй 100 строк с NULL, вы получите 10 000 строк в результате.
Одним из самых популярных способов обхода проблемы является замена NULL на какое-то конкретное значение «заглушку» прямо в процессе сравнения или соединения. Для этого используется функция ЕСТЬNULL(Значение, Подстановка).
Рассмотрим пример, где мы заменяем NULL на 0 или пустую ссылку для корректного сравнения:
ВЫБРАТЬ
ВТ_Первая.Поле1
ИЗ
ВТ_Первая КАК ВТ_Первая
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Вторая КАК ВТ_Вторая
ПО ЕСТЬNULL(ВТ_Первая.ПолеNULL, 0) = ЕСТЬNULL(ВТ_Вторая.ПолеNULL, 0)
Проанализируем этот метод. Он удобен, но имеет свои подводные камни:
JOIN) может привести к тому, что СУБД не сможет использовать индексы. Это значительно замедлит выполнение запроса.ЕСТЬNULL нужно выбирать значение, которое гарантированно не встречается в реальных данных таблицы, иначе вы получите ложные срабатывания соединения.Выясним причины появления NULL в данных, помимо результата левого соединения. Это поможет лучше понимать структуру базы данных:
NULL.NULL приходят напрямую из сторонних СУБД.В ходе обсуждения был затронут важный нюанс работы Системы компоновки данных (СКД), связанный с параметрами виртуальных таблиц. Если в отчете используются стандартные параметры &НачалоПериода и &КонецПериода, СКД может игнорировать ваши пользовательские настройки периодов для виртуальных таблиц (например, Хозрасчетный.Обороты), если они не оформлены должным образом.
Чтобы избежать ошибок при формировании отчетов, когда система подставляет «дефолтные» даты вместо указанных вами, рекомендуется:
{} в тексте запроса, чтобы СКД корректно подхватывала их при автозаполнении.Для качественной разработки важно четко разделять эти три понятия, так как их часто путают в контексте пустых значений:
Неопределено. Используется в коде 1С (например, когда переменная объявлена, но не инициализирована) или в полях составного типа на формах. В запросе NULL и Неопределено — это разные вещи.СправочникСсылка.Контрагенты) и уникальный идентификатор, состоящий из нулей. Пустая ссылка никогда не равна NULL.Посмотрим на классический пример ошибки. Если вы попытаетесь проверить пустую ссылку в запросе так:
ГДЕ Контрагенты.Ссылка ЕСТЬ NULL // Это не найдет пустые ссылки!
Правильный вариант для поиска пустых ссылок:
ГДЕ Контрагенты.Ссылка = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Разберем еще одну особенность, упомянутую в обсуждении. Если выполнить запрос с условием В ИЕРАРХИИ(&Параметр) и передать в параметр Пустую ссылку, запрос вернет все элементы справочника. Это поведение платформы по умолчанию: пустая ссылка считается корнем любого иерархического справочника, поэтому в иерархию «корня» попадает всё дерево данных. Если ваша задача — выбрать только те элементы, у которых нет родителя, используйте условие ГДЕ Родитель = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).
Подводя итог, можно сказать, что работа с NULL требует особого внимания к деталям. Основное правило: никогда не используйте = или <> с NULL в запросах. Используйте специализированный оператор ЕСТЬ NULL или функцию ЕСТЬNULL(), предварительно проанализировав объем данных и возможные потери производительности.