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

· 10 мин

Содержание

Проверить переменную на 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 переходит в выборку и в таблицу значений без изменений. Есть три разных «пустоты», которые проверяют по-разному.

  1. Результат пустой — запрос не вернул ни одной строки. Проверка: Результат.Пустой().
  2. Строки нет в выборке — Выборка.Следующий() вернул Ложь.
  3. Строка есть, поле равно 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)?
Как отобрать в запросе строки, где поле правой таблицы пустое после левого соединения?
Что вернёт Соответствие.Получить(Ключ), если ключа нет?

Частые вопросы

Чем NULL отличается от Неопределено в 1С?
NULL означает отсутствие данных в источнике и приходит из запроса. Неопределено — незаданное значение в коде: необъявленная переменная, пустой составной реквизит. Это разные значения, и NULL = Неопределено вернёт Ложь.
Можно ли записать NULL в реквизит справочника или документа?
Нет, реквизит объекта хранит значение своего типа. Перед записью замените NULL значением по умолчанию — через ЕСТЬNULL в запросе или условием в коде.
Почему СУММА в запросе вернула NULL, а не 0?
Агрегатная функция без группировки по пустому набору возвращает одну строку с NULL. Оберните выражение: ЕСТЬNULL(СУММА(Поле), 0).
Что быстрее — проверить NULL в запросе или в коде?
ЕСТЬNULL в запросе выполняется на стороне СУБД вместе с выборкой и не требует проверок в цикле. Для больших выборок это проще и не медленнее, чем условие на каждой строке.
Есть ли в 1С метод IsNull для переменных?
Во встроенном языке такого метода нет. Есть функция запроса ЕСТЬNULL (ISNULL) и конструкция ЕСТЬ NULL, а в коде — сравнение = NULL или ТипЗнч(...) = Тип("NULL").