NULL, Неопределено и пустые значения в 1С 8.3: чем проверять каждое и где они появляются
Содержание
В коде 1С 8.3 значение проверяют на NULL простым сравнением Если Значение = NULL Тогда или через тип: ТипЗнч(Значение) = Тип("NULL"). В тексте запроса сравнение со знаком равенства не работает
— там пишут Поле ЕСТЬ NULL, а подменяют NULL на нормальное значение функцией ЕСТЬNULL(Поле, 0).
Типичная ситуация: разработчик соединил остатки с ценами левым соединением, у части номенклатуры цены нет, и обработка падает на умножении с ошибкой преобразования к числу. Виноват NULL, пришедший из запроса. Ниже — как отличить NULL от Неопределено, пустой ссылки, пустой строки и нуля и какой проверкой ловить каждое.
Чем NULL отличается от Неопределено и «пустых» значений
В 1С пустота бывает разной. У каждого вида своя природа, и проверка, которая годится для одного, может не сработать для другого.
| Значение | Тип | Откуда берётся | Как проверить |
|---|---|---|---|
NULL | NULL | Результат запроса: нет пары в левом соединении, ВЫБОР без ИНАЧЕ, реквизит через точку у составного поля | = NULL, ТипЗнч() = Тип("NULL"), в запросе ЕСТЬ NULL |
Неопределено | Неопределено | Переменная без присваивания, пустое значение составного реквизита, Соответствие.Получить() без ключа, функция без Возврат значения | = Неопределено, ТипЗнч() = Тип("Неопределено") |
| Пустая ссылка | Ссылка на объект | Незаполненный реквизит ссылочного типа | Ссылка.Пустая(), = Справочники.Имя.ПустаяСсылка() |
| Пустая строка | Строка | Незаполненный строковый реквизит | ПустаяСтрока(), = "" |
| Пустая дата | Дата | Незаполненный реквизит даты | = Дата(1, 1, 1) |
| Ноль | Число | Незаполненное число | = 0 |
| Ложь | Булево | Незаполненный флажок | Не Значение |
Главное различие. NULL — это «данных нет в принципе», его производит СУБД при выполнении запроса. Неопределено — «значение не задано» внутри встроенного языка. Пустая ссылка, "", 0 и пустая дата — нормальные значения своего типа, просто равные значению по умолчанию.
В реквизитах объектов NULL не хранится. Если вы видите NULL в коде, почти всегда он пришёл из результата запроса или из выборки.
Где NULL появляется на практике
- 🔗 Левое соединение: в правой таблице нет строки для записи из левой — все её поля становятся NULL.
- 🔀 Конструкция
ВЫБОР КОГДА ... ТОГДА ... КОНЕЦбез веткиИНАЧЕ: для неподошедших строк результат NULL. - 🧩 Обращение через точку к реквизиту, которого нет у фактического типа составного поля. Например,
Регистратор.Контрагент, когда регистратором оказался документ без такого реквизита. - ➕ Агрегатная функция
СУММАпо группе, в которой нет ни одного значения, и арифметика с участием NULL:NULL + 5в запросе даёт NULL.
ЗначениеЗаполнено: одна проверка на «пусто» для любого типа
Функция ЗначениеЗаполнено(Значение) возвращает Ложь, если значение совпадает со значением по умолчанию своего типа или равно NULL либо Неопределено. Это самый короткий способ сказать «здесь ничего нет», не разбираясь, какой тип пришёл.
Если Не ЗначениеЗаполнено(Выборка.Цена) Тогда
// сюда попадут NULL, Неопределено и 0
Цена = 0;
Иначе
Цена = Выборка.Цена;
КонецЕсли;
Что функция считает незаполненным:
- ✅ NULL и Неопределено;
- ✅ число
0; - ✅ пустую строку
""; - ✅ пустую дату
01.01.0001 00:00:00; - ✅
Ложь; - ✅ пустую ссылку любого справочника, документа, перечисления.
Функция доступна и на клиенте, и на сервере, ей не нужен запрос к базе для проверки ссылки на пустоту.
Когда ЗначениеЗаполнено подводит
Удобство оборачивается ловушкой там, где ноль или Ложь — осмысленные данные.
- ⚠️ Количество
0в строке документа — это «ноль штук», а не «не указано».ЗначениеЗаполненоскажет «пусто», и код может подставить значение по умолчанию поверх реального нуля. - ⚠️ Флажок
Ложь— это ответ «нет». Проверка черезЗначениеЗаполненосмешает «нет» и «не задано» в одну ветку. - ⚠️ Функция не говорит, ЧТО именно пусто. Если дальше вы обращаетесь к значению как к ссылке, а пришёл NULL, разница между ними важна.
Правило выбора простое: нужно понять «есть данные или нет» — ЗначениеЗаполнено. Нужно отличить NULL от нуля или пустую ссылку от Неопределено — явная проверка значения или типа.
Явная проверка на NULL и Неопределено в коде
Во встроенном языке NULL и Неопределено — обычные значения, поэтому оператор = с ними работает корректно.
Если Значение = NULL Тогда
Сообщить("Из запроса не пришло данных");
ИначеЕсли Значение = Неопределено Тогда
Сообщить("Значение не задано");
КонецЕсли;
Вариант через тип точнее читается, когда в одной функции разбирают несколько возможных типов:
ТипЗначения = ТипЗнч(Значение);
Если ТипЗначения = Тип("NULL") Тогда
Результат = 0;
ИначеЕсли ТипЗначения = Тип("Неопределено") Тогда
Результат = 0;
ИначеЕсли ТипЗначения = Тип("Число") Тогда
Результат = Значение;
Иначе
ВызватьИсключение "Ожидалось число, пришло: " + Строка(ТипЗначения);
КонецЕсли;
Оба способа равнозначны по результату. Регистр букв неважен: NULL, Null и null — одно и то же.
Одной проверкой ловим и NULL, и Неопределено
Когда значение может прийти и из запроса, и из переменной, а ноль и пустая ссылка нужны как данные, удобна короткая функция:
Функция ЗначениеОтсутствует(Значение)
Возврат Значение = NULL Или Значение = Неопределено;
КонецФункции
Она не трогает 0, "" и пустые ссылки — в отличие от ЗначениеЗаполнено.
Неопределено в составных реквизитах
У реквизита составного типа (например, «Документ-основание» с несколькими видами документов) пустое значение — Неопределено, а не пустая ссылка. Метод .Пустая() у него вызвать нельзя: в Неопределено такого метода нет, и платформа выдаст ошибку Метод объекта не обнаружен (Пустая). Для составных реквизитов проверяйте ЗначениеЗаполнено(Основание) или сначала тип, потом метод.
Проверка на NULL в тексте запроса
В языке запросов логика трёхзначная: сравнение Поле = NULL даёт не «истину», а «неизвестно», и строка в результат не попадает. Поэтому для NULL есть свои конструкции.
| Задача | Конструкция |
|---|---|
| Отобрать строки, где значения нет | ГДЕ Цены.Цена ЕСТЬ NULL |
| Отобрать строки, где значение есть | ГДЕ НЕ Цены.Цена ЕСТЬ NULL |
| Подставить значение вместо NULL | ЕСТЬNULL(Цены.Цена, 0) |
| Проверить на Неопределено | ГДЕ Документ.Основание = НЕОПРЕДЕЛЕНО |
| Проверить на пустую ссылку | = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) |
Пример: остатки товаров с ценой, где у части номенклатуры цена не установлена.
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Количество,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
Остатки.КоличествоОстаток * ЕСТЬNULL(Цены.Цена, 0) КАК Сумма,
Цены.Цена ЕСТЬ NULL КАК ЦенаНеУстановлена
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Цены
ПО Остатки.Номенклатура = Цены.Номенклатура
Имена регистров здесь условные: в вашей конфигурации они могут называться иначе. Логика от этого не меняется.
Что даёт такой текст:
ЕСТЬNULL(Цены.Цена, 0)— в выборке не будет NULL, код получит число.- Сумма считается с подстановкой, иначе произведение с NULL само станет NULL.
- Поле
ЦенаНеУстановленатипа Булево позволяет отличить «цена 0» от «цены нет» уже в коде, без повторной проверки.
ЕСТЬNULL с агрегатами
При группировке оборачивайте в ЕСТЬNULL результат агрегата, а не только поле: ЕСТЬNULL(СУММА(Продажи.Сумма), 0). Если у группы нет ни одной строки из присоединённой таблицы, СУММА вернёт NULL.
Типы двух аргументов ЕСТЬNULL должны быть совместимы по смыслу. Для ссылочного поля вторым аргументом ставят пустую ссылку того же типа, а не 0, иначе поле в результате станет составным.
Как проверять пустоту строк, дат, чисел и ссылок
У каждого примитивного и ссылочного типа есть собственная, наиболее точная проверка.
Строка
ПустаяСтрока(Строка) возвращает Истина для пустой строки и для строки из одних пробелов, табуляций и переводов строк. Сравнение Строка = "" строку из пробелов пустой не считает. Если пользователь мог ввести пробел в поле ИНН или кода, используйте ПустаяСтрока либо сравнивайте СокрЛП(Строка) = "".
Функция рассчитана на строку. Значение из запроса, где может прийти NULL, сначала прогоните через ЕСТЬNULL(Поле, "") в тексте запроса.
Дата
Пустая дата — 01.01.0001 00:00:00. Проверка в коде:
Если ДатаОплаты = Дата(1, 1, 1) Тогда
// дата не указана
КонецЕсли;
Литерал '00010101' даёт то же значение. В запросе пустую дату сравнивают с ДАТАВРЕМЯ(1, 1, 1).
Число
Для числа проверка «пусто» и «равно нулю» совпадают: Сумма = 0. Чтобы отличить «ноль» от «ничего не пришло», проверяйте на NULL до того, как значение станет числом, — в запросе флагом ЕСТЬ NULL или в коде до вычислений.
Ссылка
Если Контрагент.Пустая() Тогда
// реквизит не заполнен
КонецЕсли;
// то же через сравнение
Если Контрагент = Справочники.Контрагенты.ПустаяСсылка() Тогда
КонецЕсли;
Метод .Пустая() вызывается только у значения ссылочного типа. Для NULL и Неопределено он не существует — отсюда частая ошибка в выборках из запросов с левым соединением.
Безопасный доступ к данным и обработка исключений
Проверка на NULL — частный случай задачи «обратиться к значению, которого может не быть». Платформа даёт несколько способов сделать это без падения.
Структура и соответствие
- 📦
Структура.Свойство("Ключ", Значение)возвращаетИстина, если ключ есть, и кладёт значение во вторую переменную. Прямое обращениеСтруктура.Ключпри отсутствии ключа вызывает ошибкуПоле объекта не обнаружено. - 🗂️
Соответствие.Получить(Ключ)при отсутствии ключа возвращает Неопределено без ошибки. Значит, послеПолучитьпроверяйте именно на Неопределено. - 📊
ТаблицаЗначений.Колонки.Найти("Имя")возвращает Неопределено, если колонки нет. Так проверяют колонку до обращения к ней.
Параметры = Новый Структура("Склад", Справочники.Склады.ПустаяСсылка());
Склад = Неопределено;
Если Параметры.Свойство("Склад", Склад) И ЗначениеЗаполнено(Склад) Тогда
// ключ есть и склад указан
КонецЕсли;
Попытка — когда проверки недостаточно
Блок Попытка ... Исключение ... КонецПопытки подходит для операций, исход которых нельзя проверить заранее: чтение внешнего файла, вызов COM-объекта, обращение к веб-сервису. Заменять им проверку на NULL не стоит — это маскирует ошибку данных.
Попытка
Результат = ВнешнийОбъект.ПолучитьДанные();
Исключение
ЗаписьЖурналаРегистрации("Обмен.Ошибка",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Результат = Неопределено;
КонецПопытки;
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
Не оставляйте пустую ветку Исключение. Ошибка исчезнет без следа, а неверные данные пойдут дальше в учёт. Как минимум запишите её в журнал регистрации.
Реквизит по ссылке без лишнего чтения
Обращение Ссылка.Реквизит в коде читает из базы весь объект. В типовых конфигурациях на БСП для чтения одного реквизита есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИмяРеквизита"). Перед вызовом убедитесь, что ссылка заполнена, — это та же проверка ЗначениеЗаполнено(Ссылка).
Ошибки, которые дают NULL и Неопределено, и как их избежать
Почти все сбои вокруг пустых значений сводятся к нескольким сценариям.
| Сообщение или симптом | Причина | Решение |
|---|---|---|
Преобразование значения к типу Число не может быть выполнено | В арифметику попал NULL из выборки | ЕСТЬNULL(Поле, 0) в тексте запроса |
Метод объекта не обнаружен (Пустая) | .Пустая() вызван у NULL или Неопределено | ЗначениеЗаполнено() или проверка типа перед вызовом |
Поле объекта не обнаружено (...) | Обращение к реквизиту у Неопределено или к отсутствующему ключу структуры | Структура.Свойство(), проверка на Неопределено |
Отбор ГДЕ Поле = NULL возвращает пустой результат | В запросе равенство с NULL не работает | ГДЕ Поле ЕСТЬ NULL |
| Строки с нулевым количеством «теряются» | Отбор через ЗначениеЗаполнено отсёк реальные нули | Явная проверка на NULL вместо ЗначениеЗаполнено |
| В отчёте пустые суммы вместо 0 | Агрегат по группе без строк вернул NULL | ЕСТЬNULL(СУММА(...), 0) |
Практики, которые снимают проблему заранее
- Убирайте NULL в запросе, а не в коде. Одна
ЕСТЬNULLв тексте дешевле десятка проверок в цикле по выборке. - Оставляйте флаг, если отсутствие данных важно. Поле вида
Цены.Цена ЕСТЬ NULL КАК ЦенаНеУстановленасохраняет информацию, которую стирает подстановка нуля. - Выбирайте проверку под смысл. «Есть ли данные» —
ЗначениеЗаполнено. «Это NULL или ноль» — явное сравнение. - Инициализируйте переменные. Перед
Структура.Свойство("Ключ", Значение)задайтеЗначение = Неопределено, чтобы ветка «ключа нет» читалась однозначно. - Помните про составные типы. Для них пустое — Неопределено, и методы ссылки до проверки типа не вызывают.
- Проверяйте ссылку перед чтением реквизитов. Обращение к реквизитам пустой ссылки не даёт полезных данных, а лишний вызов сервера замедляет форму.