NULL, Неопределено и пустая ссылка: как отличить и обработать в коде 1С 8.3
Содержание
Проверить переменную на NULL во встроенном языке 1С можно сравнением Значение = NULL или проверкой типа ТипЗнч(Значение) = Тип("NULL"). В тексте запроса работает только Поле ЕСТЬ NULL, а подменить NULL значением по умолчанию помогает функция ЕСТЬNULL(Поле, 0).
Типичная ситуация: отчёт по остаткам соединяет номенклатуру с регистром левым соединением. У товаров без движения поле количества приходит пустым, и строка Итого = Итого + Выборка.Количество падает с ошибкой преобразования типа. Чтобы такого не было, проверку на NULL в 1С делают либо в запросе, либо перед арифметикой в коде.
Откуда в 1С берётся NULL и чем он отличается от других «пустых» значений
NULL — отдельный тип с единственным значением. Он означает «данных нет вообще», а не «значение пустое». Во встроенном языке NULL почти всегда приходит из базы данных через запрос.
🔹 Основные источники NULL:
- 🔗 Левое или правое соединение. Если для записи левой таблицы нет пары в правой, все поля правой таблицы равны NULL.
- 📦 Виртуальные таблицы остатков. Нулевых остатков в
РегистрНакопления.Остатки.Остаткинет. Соединили со справочником — получили NULL у товаров без остатка. - 🧩 Обращение через точку к составному типу. Поле
Регистратор.Складдаст NULL для тех документов, у которых реквизитаСкладнет. - ➕ Агрегатные функции над пустым набором.
СУММА(Сумма)без группировки по пустой выборке вернёт одну строку со значением NULL.
Чтобы не путать «пустые» значения, держите под рукой сравнение:
| Значение | Что означает | Где встречается | Как проверить |
|---|---|---|---|
NULL | Данных нет в источнике | Результат запроса, выборка, выгруженная таблица | = NULL, ТипЗнч(...) = Тип("NULL") |
Неопределено | Значение не задано | Переменная после Перем, пустой реквизит составного типа, результат Получить() | = Неопределено |
| Пустая ссылка | Ссылка заданного типа, но никуда не указывает | Незаполненный реквизит-ссылка | .Пустая(), ЗначениеЗаполнено() |
0, "", '00010101' | Значение по умолчанию примитивного типа | Незаполненные реквизиты Число, Строка, Дата | Сравнение или ЗначениеЗаполнено() |
NULL, Неопределено и пустая ссылка — три разных значения. NULL = Неопределено во встроенном языке вернёт Ложь, и пустая ссылка тоже не равна ни одному из них.
Проверка на NULL через операторы сравнения во встроенном языке
Самый прямой способ — оператор =. Во встроенном языке NULL сравнивается как обычное значение, поэтому NULL = NULL даёт Истина.
Если Выборка.Количество = NULL Тогда
Количество = 0;
Иначе
Количество = Выборка.Количество;
КонецЕсли;
Компактный вариант — тернарный оператор ?():
Количество = ?(Выборка.Количество = NULL, 0, Выборка.Количество);
Для обратной проверки используйте <>:
Если Выборка.Цена <> NULL Тогда
Сумма = Выборка.Цена * Выборка.Количество;
КонецЕсли;
Проверка через тип значения
Второй вариант — сравнить тип. Он удобен, когда в одной процедуре вы разбираете значения разных типов и уже пишете ветвление по ТипЗнч:
ТипЗначения = ТипЗнч(Значение);
Если ТипЗначения = Тип("NULL") Тогда
// данных нет
ИначеЕсли ТипЗначения = Тип("Неопределено") Тогда
// значение не задано
ИначеЕсли ТипЗначения = Тип("Число") Тогда
// обычная обработка числа
КонецЕсли;
Оба способа дают одинаковый результат. Сравнение = NULL короче и читается лучше, проверка типа уместна в универсальных функциях.
Нельзя вызывать методы у значения, которое может оказаться NULL. Строка Если Выборка.Контрагент.Пустая() Тогда при NULL упадёт с ошибкой Метод объекта не обнаружен (Пустая). Сначала проверьте на NULL, потом вызывайте метод.
Функция ЗначениеЗаполнено: одна проверка вместо трёх
ЗначениеЗаполнено() возвращает Ложь, если значение не отличается от «пустого» для своего типа. Для NULL и Неопределено результат тоже Ложь.
Что функция считает незаполненным:
- ❌
NULL; - ❌
Неопределено; - ❌ пустую ссылку любого справочника или документа;
- ❌ число
0; - ❌ пустую строку;
- ❌ пустую дату
'00010101'.
Поэтому вместо цепочки условий достаточно одной строки:
// Длинно и легко забыть один из случаев
Если Значение = NULL Или Значение = Неопределено Или Значение.Пустая() Тогда
// Коротко и безопасно
Если Не ЗначениеЗаполнено(Значение) Тогда
Первый вариант к тому же опасен: если Значение — число, вызов .Пустая() упадёт.
Когда ЗначениеЗаполнено не подходит
Функция не различает, почему значение пустое. Иногда это важно:
- 📊 Ноль — это результат. Остаток 0 и «остатка нет в регистре» в отчёте могут означать разное.
ЗначениеЗаполнено(0)вернётЛожь, как и для NULL. - 🔍 Анализ соединения. Нужно понять, нашлась ли запись в правой таблице. NULL — «записи нет», пустая ссылка — «запись есть, реквизит не заполнен».
В этих случаях проверяйте именно = NULL, а не заполненность.
ЕСТЬ NULL и ЕСТЬNULL: проверка на NULL в запросе
В языке запросов правила другие. Сравнение Поле = NULL не вернёт строки с NULL, потому что любое сравнение с NULL в СУБД даёт «неизвестно». Для проверки есть отдельная конструкция.
Отбор по NULL: ЕСТЬ NULL и ЕСТЬ НЕ NULL
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ПО Номенклатура.Ссылка = Остатки.Номенклатура
ГДЕ
Остатки.Номенклатура ЕСТЬ NULL
Этот запрос — приём, который стоит запомнить: левое соединение плюс ЕСТЬ NULL по полю правой таблицы находит записи без пары. Так ищут товары без остатков, контрагентов без договоров, документы без движений.
Обратное условие пишут двумя равноценными способами:
ГДЕ Остатки.Номенклатура ЕСТЬ НЕ NULL
// или
ГДЕ НЕ Остатки.Номенклатура ЕСТЬ NULL
Для поиска «записей без пары» проверяйте поле, по которому шло соединение. Оно гарантированно заполнено у любой найденной записи, поэтому NULL в нём однозначно означает «записи нет».
Замена NULL значением: ЕСТЬNULL
Функция ЕСТЬNULL(Выражение, Замена) возвращает первый параметр, а если он NULL — второй. Английский вариант — ISNULL.
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток,
ЕСТЬNULL(Цены.Цена, 0) * ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Сумма
ИЗ
...
Обернуть поле в ЕСТЬNULL нужно до арифметики. В запросе NULL + 5 и NULL * 10 дают NULL, и строка тихо «обнулится» без всякой ошибки.
NULL и пустая ссылка в одном условии
Частая задача: отобрать строки, где реквизит не заполнен или записи нет вовсе. Одним условием это делается так:
ГДЕ
ЕСТЬNULL(Договоры.Ответственный, ЗНАЧЕНИЕ(Справочник.Пользователи.ПустаяСсылка))
= ЗНАЧЕНИЕ(Справочник.Пользователи.ПустаяСсылка)
Для сложных случаев подходит ВЫБОР:
ВЫБОР
КОГДА Остатки.КоличествоОстаток ЕСТЬ NULL
ТОГДА "Нет на складе"
ИНАЧЕ "В наличии"
КОНЕЦ КАК Наличие
| Конструкция | Где работает | Для чего |
|---|---|---|
Поле ЕСТЬ NULL | ГДЕ, ИМЕЮЩИЕ, ВЫБОР, условие соединения | Отбор строк с NULL |
Поле ЕСТЬ НЕ NULL | Там же | Отбор строк без NULL |
ЕСТЬNULL(Поле, Замена) | Любое выражение запроса | Подстановка значения по умолчанию |
Поле = NULL | — | Не работает как ожидается, не используйте |
Работа с выборкой и результатом запроса
После выполнения запроса NULL переходит в выборку и в таблицу значений без изменений. Есть три разных «пустоты», которые проверяют по-разному.
- Результат пустой — запрос не вернул ни одной строки. Проверка:
Результат.Пустой(). - Строки нет в выборке —
Выборка.Следующий()вернулЛожь. - Строка есть, поле равно NULL — проверка
Выборка.Поле = NULL.
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Сообщить("Данных нет");
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Итого = 0;
Пока Выборка.Следующий() Цикл
Если Выборка.Остаток <> NULL Тогда
Итого = Итого + Выборка.Остаток;
КонецЕсли;
КонецЦикла;
Надёжнее убрать NULL ещё в запросе через ЕСТЬNULL, чем ловить его в каждом цикле. Тогда код обработки выборки не зависит от того, какое соединение в запросе.
Агрегат по пустому набору
Запрос ВЫБРАТЬ СУММА(Сумма) КАК Итог ИЗ ... без группировки при отсутствии данных вернёт одну строку с NULL, а не ноль строк. Поэтому Результат.Пустой() здесь вернёт Ложь, и проверять нужно само поле. Проще сразу написать ЕСТЬNULL(СУММА(Сумма), 0).
Структуры, соответствия и проверка наличия свойства
В коллекциях вопрос обычно звучит иначе: «есть ли такой ключ». NULL здесь встречается реже, зато легко спутать «ключа нет» и «значение пустое».
Структура
Обращение к несуществующему ключу через точку вызывает исключение Поле объекта не обнаружено (ИмяКлюча). Для проверки есть метод Свойство():
Параметры = Новый Структура("Организация, Период");
// Только факт наличия ключа
Если Параметры.Свойство("Склад") Тогда
// ...
КонецЕсли;
// Наличие ключа и значение одним вызовом
Склад = Неопределено;
Если Параметры.Свойство("Склад", Склад) И ЗначениеЗаполнено(Склад) Тогда
// ключ есть и значение заполнено
КонецЕсли;
Второй параметр Свойство() получает значение ключа. Если ключа нет, метод вернёт Ложь, а переменная получит Неопределено. Так же работает ФиксированнаяСтруктура.
Соответствие
У соответствия обращение Соответствие[Ключ] и метод Получить(Ключ) для отсутствующего ключа возвращают Неопределено без исключения.
Цена = ЦеныТоваров.Получить(Товар);
Если Цена = Неопределено Тогда
Цена = 0;
КонецЕсли;
Если в соответствии хранятся значения Неопределено, по результату Получить() нельзя понять, есть ли ключ. В таком случае не кладите туда Неопределено: храните NULL или отдельный признак, либо ведите параллельную структуру ключей.
Есть ли реквизит у объекта
Для объектов и ссылок метода Свойство() нет. Надёжный способ — метаданные:
МетаданныеОбъекта = Документ.Метаданные();
ЕстьСклад = МетаданныеОбъекта.Реквизиты.Найти("Склад") <> Неопределено;
Учтите: Реквизиты содержит только реквизиты, добавленные в конфигураторе. Стандартные (Ссылка, Дата, Номер) там не ищутся.
Универсальный приём практиков — проверка через ЗаполнитьЗначенияСвойств с уникальным маркером. Он работает для любого объекта, у которого свойства читаются через точку, включая строки табличной части и строки выборки:
Маркер = Новый УникальныйИдентификатор;
Проверка = Новый Структура("Склад", Маркер);
ЗаполнитьЗначенияСвойств(Проверка, Объект);
ЕстьСклад = (Проверка.Склад <> Маркер);
Если у объекта есть свойство Склад, его значение перезапишет маркер. Если нет — маркер останется на месте.
Массивы и NULL
В массив NULL попадает, когда его собирают из выборки: Массив.Добавить(Выборка.Поле).
Проверить наличие NULL в массиве:
ЕстьПустые = (Массив.Найти(NULL) <> Неопределено);
Найти() возвращает индекс найденного элемента или Неопределено, поэтому сравнивать результат нужно именно с Неопределено, а не с нулём: индекс первого элемента как раз 0.
Удалить все NULL из массива — обратным циклом, чтобы удаление не сдвигало ещё не проверенные элементы:
Для Сч = 1 По Массив.Количество() Цикл
Индекс = Массив.Количество() - Сч;
Если Массив[Индекс] = NULL Тогда
Массив.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Здесь граница цикла вычисляется один раз при входе, а индекс пересчитывается от текущего размера. Удаление идёт с конца, поэтому элементы с меньшими индексами не смещаются.
Если массив передаётся в параметр запроса для условия В (&Список), лучше вообще не допускать в нём NULL: фильтруйте значения ещё при сборке.
Неопределено, составные типы и типовые ошибки с NULL
Неопределено — ближайший родственник NULL во встроенном языке. Его получает:
- 🆕 переменная, объявленная через
Перембез присвоения; - 🧩 реквизит составного типа, пока в нём не выбран ни один тип;
- 📭 результат
Получить()у соответствия при отсутствии ключа иНайти()у массива при отсутствии элемента.
Разница с NULL на практике: Неопределено рождается в коде и в объектах, NULL — в данных запроса. Если вы не работаете с запросом, проверять на NULL обычно незачем.
Где чаще всего ошибаются
- ❗
Поле = NULLв тексте запроса. Синтаксис допустим, но строки с NULL не отберутся. НужноЕСТЬ NULL. - ❗ Арифметика без проверки во встроенном языке.
Итого + Выборка.Суммапри NULL вызывает исключение — в отличие от запроса, где результат молча станет NULL. - ❗ Метод у возможного NULL.
Выборка.Ссылка.Пустая()после левого соединения падает на строках без пары. - ❗ Проверка через точку по составному типу.
Регистратор.Складв запросе даёт NULL для документов без реквизита — это не «склад не заполнен». - ❗ Сравнение
Найти()с нулём. Ноль — валидный индекс, «не найдено» — это Неопределено.
Частые вопросы
Чем NULL отличается от Неопределено в 1С?
NULL = Неопределено вернёт Ложь.Можно ли записать NULL в реквизит справочника или документа?
Почему СУММА в запросе вернула NULL, а не 0?
ЕСТЬNULL(СУММА(Поле), 0).Что быстрее — проверить NULL в запросе или в коде?
Есть ли в 1С метод IsNull для переменных?
= NULL или ТипЗнч(...) = Тип("NULL").