NULL, Неопределено и пустые значения в 1С 8.3: чем проверять каждое и где они появляются

· 10 мин

Содержание

В коде 1С 8.3 значение проверяют на NULL простым сравнением Если Значение = NULL Тогда или через тип: ТипЗнч(Значение) = Тип("NULL"). В тексте запроса сравнение со знаком равенства не работает — там пишут Поле ЕСТЬ NULL, а подменяют NULL на нормальное значение функцией ЕСТЬNULL(Поле, 0).

Типичная ситуация: разработчик соединил остатки с ценами левым соединением, у части номенклатуры цены нет, и обработка падает на умножении с ошибкой преобразования к числу. Виноват NULL, пришедший из запроса. Ниже — как отличить NULL от Неопределено, пустой ссылки, пустой строки и нуля и какой проверкой ловить каждое.

Чем NULL отличается от Неопределено и «пустых» значений

В 1С пустота бывает разной. У каждого вида своя природа, и проверка, которая годится для одного, может не сработать для другого.

ЗначениеТипОткуда берётсяКак проверить
NULLNULLРезультат запроса: нет пары в левом соединении, ВЫБОР без ИНАЧЕ, реквизит через точку у составного поля= 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 КАК ЦенаНеУстановлена
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних КАК Цены
        ПО Остатки.Номенклатура = Цены.Номенклатура

Имена регистров здесь условные: в вашей конфигурации они могут называться иначе. Логика от этого не меняется.

Что даёт такой текст:

  1. ЕСТЬNULL(Цены.Цена, 0) — в выборке не будет NULL, код получит число.
  2. Сумма считается с подстановкой, иначе произведение с NULL само станет NULL.
  3. Поле ЦенаНеУстановлена типа Булево позволяет отличить «цена 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 не стоит — это маскирует ошибку данных.

Попытка
    Результат = ВнешнийОбъект.ПолучитьДанные();
Исключение
    ЗаписьЖурналаРегистрации("Обмен.Ошибка",
        УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    Результат = Неопределено;
КонецПопытки;

Если Результат = Неопределено Тогда
    Возврат;
КонецЕсли;

Не оставляйте пустую ветку Исключение. Ошибка исчезнет без следа, а неверные данные пойдут дальше в учёт. Как минимум запишите её в журнал регистрации.

Реквизит по ссылке без лишнего чтения

Обращение Ссылка.Реквизит в коде читает из базы весь объект. В типовых конфигурациях на БСП для чтения одного реквизита есть ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ИмяРеквизита"). Перед вызовом убедитесь, что ссылка заполнена, — это та же проверка ЗначениеЗаполнено(Ссылка).

Проверьте себя
Что вернёт ЗначениеЗаполнено(0)?
Как в тексте запроса отобрать строки, где цены нет?
Какое пустое значение у незаполненного реквизита составного типа?
Что произойдёт при вызове .Пустая() у значения NULL?

Ошибки, которые дают NULL и Неопределено, и как их избежать

Почти все сбои вокруг пустых значений сводятся к нескольким сценариям.

Сообщение или симптомПричинаРешение
Преобразование значения к типу Число не может быть выполненоВ арифметику попал NULL из выборкиЕСТЬNULL(Поле, 0) в тексте запроса
Метод объекта не обнаружен (Пустая).Пустая() вызван у NULL или НеопределеноЗначениеЗаполнено() или проверка типа перед вызовом
Поле объекта не обнаружено (...)Обращение к реквизиту у Неопределено или к отсутствующему ключу структурыСтруктура.Свойство(), проверка на Неопределено
Отбор ГДЕ Поле = NULL возвращает пустой результатВ запросе равенство с NULL не работаетГДЕ Поле ЕСТЬ NULL
Строки с нулевым количеством «теряются»Отбор через ЗначениеЗаполнено отсёк реальные нулиЯвная проверка на NULL вместо ЗначениеЗаполнено
В отчёте пустые суммы вместо 0Агрегат по группе без строк вернул NULLЕСТЬNULL(СУММА(...), 0)

Практики, которые снимают проблему заранее

  1. Убирайте NULL в запросе, а не в коде. Одна ЕСТЬNULL в тексте дешевле десятка проверок в цикле по выборке.
  2. Оставляйте флаг, если отсутствие данных важно. Поле вида Цены.Цена ЕСТЬ NULL КАК ЦенаНеУстановлена сохраняет информацию, которую стирает подстановка нуля.
  3. Выбирайте проверку под смысл. «Есть ли данные» — ЗначениеЗаполнено. «Это NULL или ноль» — явное сравнение.
  4. Инициализируйте переменные. Перед Структура.Свойство("Ключ", Значение) задайте Значение = Неопределено, чтобы ветка «ключа нет» читалась однозначно.
  5. Помните про составные типы. Для них пустое — Неопределено, и методы ссылки до проверки типа не вызывают.
  6. Проверяйте ссылку перед чтением реквизитов. Обращение к реквизитам пустой ссылки не даёт полезных данных, а лишний вызов сервера замедляет форму.
Почему NULL + 1 в запросе даёт NULL, а не 1?
В языке запросов любая арифметика с NULL возвращает NULL. Оборачивайте операнды в ЕСТЬNULL до вычисления.
Можно ли записать NULL в реквизит справочника?
Нет. Реквизиты объектов хранят значения своих типов, а пустой реквизит содержит значение по умолчанию: пустую ссылку, ноль, пустую строку или Неопределено для составного типа.
Чем ЕСТЬNULL отличается от ВЫБОР КОГДА ... ЕСТЬ NULL?
По результату ничем, ЕСТЬNULL короче. ВЫБОР нужен, когда вместо NULL подставляют разные значения по условию.
Видно ли NULL в консоли запросов?
Да, консоли запросов обычно показывают NULL отдельным представлением, отличным от пустой ячейки. Так проще найти, какое соединение не нашло пару.
Вернёт ли функция без Возврат значения NULL?
Нет. Функция, завершившаяся без возврата значения, возвращает Неопределено.