В процессе разработки на платформе 1С:Предприятие начинающие разработчики неизбежно сталкиваются со значениями, которые обозначают «пустоту» или отсутствие данных. Наиболее часто путаница возникает между двумя системными понятиями: НЕОПРЕДЕЛЕНО и NULL. В некоторых неофициальных учебных материалах и статьях прошлых лет даже можно встретить грубую ошибку о том, что тип значения НЕОПРЕДЕЛЕНО равен NULL. Разберем подробно, почему это утверждение в корне неверно, как эти два значения хранятся в базе данных, как они ведут себя во встроенном языке и в запросах, а также проанализируем тонкости трехзначной логики СУБД.
Для начала зафиксируем ключевое правило: НЕОПРЕДЕЛЕНО и NULL — это абсолютно разные литералы, принадлежащие к разным типам данных. Они никогда не были равны друг другу и никогда не будут.
Заблуждение о том, что тип значения НЕОПРЕДЕЛЕНО равен NULL, часто рождается из-за особенностей визуализации данных в различных консолях запросов или отчетах. Если в результате запроса поле содержит НЕОПРЕДЕЛЕНО, некоторые некорректно написанные инструменты вывода могут отображать его как пустую ячейку или выводить слово NULL. Это связано исключительно с логикой обработки результата запроса в коде конкретной консоли, а не с поведением самой платформы 1С. В официальной литературе фирмы 1С четко разграничиваются эти типы.
Давайте проанализируем природу каждого из этих значений, чтобы понять их истинное назначение.
Значение НЕОПРЕДЕЛЕНО (в англоязычном варианте Undefined) — это значение системного типа данных Неопределено. Оно используется платформой в следующих случаях:
НЕОПРЕДЕЛЕНО.НЕОПРЕДЕЛЕНО. Это маркер того, что конкретный тип для поля еще не определен.Возврат или процедура вызывается в качестве функции, результатом будет НЕОПРЕДЕЛЕНО.На уровне базы данных (СУБД) значение НЕОПРЕДЕЛЕНО для полей составного типа хранится в виде комбинации нескольких колонок. СУБД создает отдельную колонку под каждый тип из состава составного поля, а также служебную колонку для хранения информации о типе. Если поле не заполнено, в колонке типа записывается специальный маркер, сигнализирующий 1С, что это значение НЕОПРЕДЕЛЕНО.
Значение NULL — это значение специального типа данных Null. Оно имеет совершенно иную природу и пришло из теории реляционных баз данных. В 1С оно применяется для обозначения физического отсутствия данных или несуществующего значения. Основные источники появления NULL:
NULL.NULL.СУММА(), СРЕДНЕЕ(), МИНИМУМ() к пустой таблице или выборке, результатом работы этих функций станет NULL.В отличие от НЕОПРЕДЕЛЕНО, значение NULL напрямую поддерживается на уровне СУБД как стандартное состояние отсутствия значения в ячейке таблицы.
Рассмотрим, как ведет себя встроенный язык 1С при работе с этими значениями. Напишем небольшой программный код для проверки типов и сравнения значений:
// Пример проверки типов во встроенном языке
Значение1 = Неопределено;
Значение2 = Null;
// Проверяем типы значений
Тип1 = ТипЗнч(Значение1); // Возвращает тип "Неопределено"
Тип2 = ТипЗнч(Значение2); // Возвращает тип "Null"
Если Тип1 = Тип2 Тогда
Сообщить("Типы равны");
Иначе
Сообщить("Типы НЕ равны! Тип Неопределено не равен типу Null.");
КонецЕсли;
Если запустить этот код, мы гарантированно увидим сообщение о том, что типы не равны. Теперь разберем поведение операций сравнения во встроенном языке. Это очень важный момент, так как логика встроенного языка отличается от логики запросов.
// Сравнение значений напрямую во встроенном языке
РезультатСравнения1 = (Неопределено = Null); // Возвращает Ложь
РезультатСравнения2 = (Null = Null); // Возвращает Истина
РезультатСравнения3 = (Неопределено = Неопределено); // Возвращает Истина
Обратите внимание на вторую строку: во встроенном языке 1С операция Null = Null возвращает Истина. Это стандартное поведение для объектно-ориентированного программирования. Однако, если мы перейдем к языку запросов, правила игры кардинально изменятся.
Также важно помнить о работе функции ТипЗнч() при проверке на пустоту. Посмотрим на следующий пример:
// Распространенная ошибка проверки типа
Если ТипЗнч(Значение1) = Неопределено Тогда
// Этот блок никогда не выполнится!
КонецЕсли;
// Правильный вариант проверки
Если ТипЗнч(Значение1) = Тип("Неопределено") Тогда
// Блок выполнится успешно
КонецЕсли;
Функция ТипЗнч() возвращает объект типа Тип, а не само проверяемое значение. Поэтому сравнивать результат функции нужно именно с результатом системной функции Тип("Неопределено") или Тип("Null").
Язык запросов 1С работает непосредственно на уровне СУБД, где действует так называемая троичная логика (3-valued logic). В рамках этой логики результат любого сравнения может принимать три состояния: Истина, Ложь и Неизвестно (которое на практике приравнивается к значению NULL).
Главное и самое коварное правило работы с NULL в запросах звучит так: любое сравнение значения NULL с любым другим значением (даже с самим собой) всегда возвращает NULL.
Проанализируем это на примере логического выражения в запросе:
ВЫБРАТЬ
(NULL = NULL) КАК РезультатСравнения, // Результат будет NULL!
(0 = NULL) КАК РезультатСравненияСНулем, // Результат будет NULL!
(NULL <> NULL) КАК РезультатНеравенства // Результат будет NULL!
Поскольку результатом любого такого сравнения в СУБД является NULL, оно не может выступать в качестве логической истины в условиях отбора. Если вы напишете в запросе условие ГДЕ Таблица.Поле = NULL, то СУБД вычислит это условие как NULL. Платформа 1С интерпретирует итоговое значение NULL в блоке фильтрации как ЛОЖЬ, и в выборку не попадет ни одна строка, даже те, у которых в поле действительно записано NULL!
Поэтому для проверки на NULL в языке запросов используется исключительно специальный оператор ЕСТЬ NULL:
// Правильная проверка на NULL в запросе
ВЫБРАТЬ
Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Артикул ЕСТЬ NULL
Также в логических выражениях запроса важно знать, как NULL взаимодействует с булевыми операторами И, ИЛИ, НЕ. Рассмотрим эти правила, которые часто используются при оптимизации сложных условий в запросах:
NULL И ИСТИНА дает NULLNULL И ЛОЖЬ дает ЛОЖЬ (так как для Лжи достаточно одного ложного операнда)NULL ИЛИ ИСТИНА дает ИСТИНА (так как для Истины достаточно одного истинного операнда)NULL ИЛИ ЛОЖЬ дает NULLНЕ NULL дает NULLДля предотвращения попадания значений NULL в конечный результат запроса (что может вызвать ошибки при дальнейшей обработке данных во встроенном языке) разработчики используют встроенную функцию языка запросов ЕСТЬNULL(). Рассмотрим ее работу.
Синтаксис функции выглядит следующим образом: ЕСТЬNULL(ПроверяемоеПоле, ЗначениеЗамены). Если проверяемое поле имеет значение NULL, то функция вернет значение замены. В противном случае вернется исходное значение поля.
Очень часто в качестве значения замены используют как раз НЕОПРЕДЕЛЕНО или пустую ссылку. Посмотрим на практический пример:
ВЫБРАТЬ
Города.Ссылка КАК Город,
// Если у города нет отличительного признака (NULL при левом соединении),
// заменяем его на значение НЕОПРЕДЕЛЕНО
ЕСТЬNULL(Города.ОтличительныйПризнак, НЕОПРЕДЕЛЕНО) КАК ОтличительныйПризнак
ИЗ
Справочник.Города КАК Города
В данном запросе, если поле ОтличительныйПризнак из подчиненной таблицы окажется физически пустым (например, в результате левого соединения), функция ЕСТЬNULL корректно подставит значение НЕОПРЕДЕЛЕНО. В результате в итоговой таблице значений мы получим корректный тип данных.
В практике разработки существует серьезная проблема, о которой должен знать каждый программист 1С. Она связана с выгрузкой полей типа Неопределено во временные таблицы.
Рассмотрим ситуацию: вы создали запрос, который выбирает поле со значением НЕОПРЕДЕЛЕНО (например, с помощью конструкции ВЫБРАТЬ НЕОПРЕДЕЛЕНО КАК Поле или если это поле составного типа осталось пустым). Вы выгрузили этот результат в Таблицу Значений во встроенном языке.
У колонки этой Таблицы Значений тип данных определится как Неопределено. Если вы попытаетесь передать эту Таблицу Значений в качестве параметра в другой запрос и поместить ее во временную таблицу с помощью конструкции ПОМЕСТИТЬ ВТ, система выдаст критическую ошибку:
"Тип не может быть выбран в запросе"
Это происходит потому, что тип Неопределено не может быть явно назначен в качестве физического типа колонки таблицы базы данных вне контекста составного типа. Чтобы избежать этой ошибки, всегда принудительно приводите типы в исходном запросе с помощью функции ВЫРАЗИТЬ или заранее программно описывайте типы колонок в Таблице Значений перед ее передачей в менеджер временных таблиц.
Пример правильного приведения типов в запросе для составного поля:
ВЫБРАТЬ
// Явно приводим составное неопределенное поле к конкретному ссылочному типу
ВЫРАЗИТЬ(Города.ОтличительныйПризнак КАК Справочник.Признаки) КАК ОтличительныйПризнак
ИЗ
Справочник.Города КАК Города
Мы подробно разобрали физическую и логическую суть двух понятий отсутствия данных в платформе 1С:Предприятие. Подытожим ключевые различия:
НЕОПРЕДЕЛЕНО — это значение по умолчанию во встроенном языке и маркер незаполненности полей составного типа. У него свой собственный системный тип Неопределено.NULL — это маркер отсутствия данных в базе данных, появляющийся преимущественно в результате левых/правых соединений в запросах. У него тип данных Null.NULL = NULL возвращает NULL, а не истину. Для поиска пустых полей в условиях запроса всегда пишите ЕСТЬ NULL.Null = Null возвращает Истина, а проверка типа осуществляется через ТипЗнч(Значение) = Тип("Null").Понимание этих нюансов убережет вас от трудноуловимых ошибок при построении сложных отчетов, проектировании архитектуры метаданных составных типов и оптимизации высоконагруженных запросов.