NULL, пустая ссылка и пустая строка в запросах 1С: как их проверять

· 9 мин

Содержание

NULL ловят оператором ЕСТЬ NULL, а подменяют функцией ЕСТЬNULL(поле, значение). Пустую ссылку сравнивают со значением ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка), а пустую строку — с "". Это три разные проверки, и путать их нельзя: поле из левого соединения может быть NULL, реквизит-ссылка — пустой ссылкой, а текстовый реквизит — пустой строкой.

Чаще всего на пустоту проверяют результат соединения («у товара нет цены», «у документа не заполнен договор») или ресурс регистра. Ниже — как это делать правильно для каждого типа данных, чем ЕСТЬ NULL отличается от ЕСТЬNULL, и почему Поле = NULL молча не работает .

Три разных «пустых» значения в запросе

В языке запросов 1С «пусто» — это не одно состояние, а минимум три. Они хранятся по-разному и проверяются разными средствами.

  • 🔹 NULL — это не значение, а отсутствие значения. Появляется, когда в источнике данных строки просто нет (левое соединение не нашло пару, у регистра нет записи).
  • 🔸 Пустая ссылка — это полноценное значение ссылочного типа с нулевым GUID. Тип у неё есть, значение «никто не выбран».
  • 🔹 Пустая строка "" — обычная строка нулевой длины. Тоже полноценное значение, не NULL.

Для числа и даты «пусто» обычно означает 0 и пустую дату ('00010101000000'), и это тоже значения, а не NULL.

Главное следствие: NULL нельзя сравнивать через =. Выражение Поле = NULL не вернёт ИСТИНА никогда, даже если поле действительно NULL. Для NULL есть отдельный оператор — ЕСТЬ NULL.

Что именно вы проверяете на пустоту?

Откуда в запросе берётся NULL

Прежде чем проверять, стоит понимать, откуда NULL вообще взялся. Его не бывает «просто так» в реквизитах основной таблицы — заполненный реквизит ссылочного типа содержит либо ссылку, либо пустую ссылку, но не NULL.

NULL приходит из трёх основных источников:

  1. Левое, правое или полное соединение, в котором не нашлась пара. Поля неприсоединённой таблицы заполняются NULL.
  2. Ресурсы и реквизиты регистров, по которым нет записей в выбранном периоде или разрезе.
  3. Оператор ВЫБОР без ветки ИНАЧЕ, когда ни одно условие КОГДА не сработало.

Типичный пример — список товаров с ценами из регистра сведений:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Товар,
    Цены.Цена КАК Цена
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
    ПО Цены.Номенклатура = Номенклатура.Ссылка

Для товаров без записи о цене поле Цена будет не нулём, а именно NULL. Если потом сложить такие цены или сравнить с числом, результат окажется непредсказуемым — отсюда и растёт большинство ошибок.

Оператор ЕСТЬ NULL: отловить отсутствие значения

Чтобы в условии ГДЕ или в выражении ВЫБОР проверить поле именно на NULL, используют оператор ЕСТЬ NULL. Обратите внимание: между словами ЕСТЬ и NULL есть пробел — это оператор, а не функция.

Отобрать товары, у которых цена не задана:

ГДЕ
    Цены.Цена ЕСТЬ NULL

Обратное условие — только те, у кого цена есть:

ГДЕ
    Цены.Цена ЕСТЬ НЕ NULL

Форма ЕСТЬ НЕ NULL и англоязычная IS NOT NULL равнозначны. В ВЫБОР оператор работает так же:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Товар,
    ВЫБОР
        КОГДА Цены.Цена ЕСТЬ NULL
            ТОГДА "Цена не задана"
        ИНАЧЕ "Цена есть"
    КОНЕЦ КАК Статус

Если в ВЫБОР убрать ветку ИНАЧЕ, а ни одно КОГДА не сработает — результатом станет NULL. Поэтому в вычисляемых полях ИНАЧЕ лучше задавать всегда.

Функция ЕСТЬNULL: подставить значение вместо пустоты

ЕСТЬNULL(Выражение, ЗначениеЗамены) — это функция (пишется без пробела). Она возвращает первый аргумент, если он не NULL, и второй — если NULL. Прямой аналог ISNULL в SQL.

Самое частое применение — заменить отсутствующую цену или остаток на ноль, чтобы корректно считать итоги:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Товар,
    ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
        ПО Цены.Номенклатура = Номенклатура.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
        ПО Остатки.Номенклатура = Номенклатура.Ссылка

Без обёртки ЕСТЬNULL сумма по колонке с NULL может «схлопнуться» или дать NULL целиком. С обёрткой итог считается честно.

Тип значения-замены должен совпадать с типом поля: для числа подставляйте 0, для строки — "", для даты — ДАТАВРЕМЯ(1, 1, 1), для ссылки — ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка). Если подставить строку вместо числа, поле получит составной тип, и дальнейшая арифметика сломается.

Таблица — чем отличаются два похожих имени:

КонструкцияЧто этоЧто делает
ЕСТЬ NULL (с пробелом)Оператор сравненияПроверяет: поле пустое или нет. Даёт ИСТИНА/ЛОЖЬ
ЕСТЬNULL(…, …) (без пробела)ФункцияВозвращает значение: либо поле, либо замену

Проверка пустой ссылки и пустой строки

Если поле лежит в основной таблице и не приходит из соединения, NULL в нём быть не может. Там проверяют не на NULL, а на пустую ссылку или пустую строку.

Пустая ссылка — это конкретное значение, которое получают через ЗНАЧЕНИЕ:

ВЫБРАТЬ
    Заказы.Ссылка
ИЗ
    Документ.ЗаказКлиента КАК Заказы
ГДЕ
    Заказы.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)

Имена справочника и документа зависят от конфигурации — подставьте свои. Обратное условие (договор заполнен) — через <>.

Пустую строку сравнивают напрямую:

ГДЕ
    Контрагенты.ИНН = ""

Самый надёжный вариант, когда поле может быть и NULL (из соединения), и пустой строкой одновременно — свести оба случая к одному через ЕСТЬNULL:

ГДЕ
    ЕСТЬNULL(Контрагенты.Комментарий, "") = ""

Так в выборку попадут и строки без записи (NULL), и строки с пустым комментарием. Для ресурсов тот же приём с нулём: ЕСТЬNULL(Ресурс, 0) = 0 ловит и отсутствие движения, и нулевой остаток.

В коде на встроенном языке (не в запросе) есть универсальная функция ЗначениеЗаполнено(). Она сама понимает тип: вернёт ЛОЖЬ для NULL, Неопределено, пустой ссылки, пустой строки, нуля и пустой даты. Но в тексте запроса её нет — там работают только перечисленные выше средства.

Отдельного внимания требуют составные условия, где NULL соседствует с И, ИЛИ и НЕ. Язык запросов использует трёхзначную логику: сравнение с NULL даёт не ИСТИНА и не ЛОЖЬ, а неопределённость. Из-за этого условие вида НЕ (Поле = "Значение") не отберёт строки, где Поле равно NULL — неопределённость под отрицанием так и остаётся неопределённостью, а в ГДЕ трактуется как ложь.

Поэтому когда в условии участвует поле из соединения, безопаснее сначала убрать NULL, а потом сравнивать:

ГДЕ
    НЕ ЕСТЬNULL(Цены.Цена, 0) = 100

Такой фильтр честно включит и товары с ценой, отличной от 100, и товары вообще без цены. Без ЕСТЬNULL последние молча выпали бы из результата.

Составные типы: ВЫРАЗИТЬ и оператор ССЫЛКА

Отдельная история — поля составного типа: регистратор движений, реквизит с несколькими допустимыми типами, поле «значение» из регистра сведений. Обратиться к реквизиту такого поля напрямую нельзя — сначала нужно привести его к конкретному типу.

Для приведения служит ВЫРАЗИТЬ. Если фактический тип значения не тот, в который приводят, результат будет NULL — и это удобно использовать как проверку:

ВЫБРАТЬ
    ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Номер КАК Номер
ИЗ
    РегистрНакопления.Продажи КАК Движения

У строк, где регистратор — не «Реализация», поле Номер окажется NULL. Дальше их отсекают привычным ЕСТЬ НЕ NULL.

Проверить тип, не приводя значение, помогает оператор ССЫЛКА:

ГДЕ
    Движения.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Он возвращает ИСТИНА, если значение — ссылка на объект указанного типа. Это чище, чем сравнивать результат ВЫРАЗИТЬ с NULL, и не создаёт лишнего приведения. Для проверки конкретного типа значения в составном поле подойдёт и функция ТИПЗНАЧЕНИЯ() в связке с ТИП().

Производительность: как проверки влияют на план запроса

Проверки на пустоту напрямую связаны со скоростью. Несколько правил, которые экономят время на больших таблицах.

  • ✅ Условие Поле ЕСТЬ NULL по полю соединения оптимизатор обрабатывает нормально — это стандартный приём «найти записи без пары».
  • ✅ Сравнение ссылки с ЗНАЧЕНИЕ(…ПустаяСсылка) использует индекс по этому реквизиту, если он есть.
  • ⚠️ Обёртка поля в функцию внутри ГДЕ мешает индексу. Условие ЕСТЬNULL(Поле, 0) = 0 по индексированному полю основной таблицы заставляет перебирать строки. Если поле не из соединения, разбейте на Поле = 0, а NULL там и так невозможен.
  • ⚠️ Не делайте лишних соединений только ради проверки «есть запись или нет». Иногда дешевле вложенный запрос с В или ОБЪЕДИНИТЬ, чем ЛЕВОЕ СОЕДИНЕНИЕ с проверкой на NULL по миллионам строк.

Отдельно про виртуальные таблицы регистров. Таблица .Остатки уже возвращает только ненулевые остатки, поэтому проверять её на ноль обычно не нужно — ноль там просто не появится. NULL возникнет лишь при левом соединении с таким остатком, и тогда ЕСТЬNULL(…, 0) закрывает вопрос.

Когда запрос «неожиданно медленный» и в нём есть ЕСТЬNULL по большим таблицам — посмотрите план в консоли запросов или в технологическом журнале. Часто причина именно в функции, обёрнутой вокруг индексированного поля.

Типичные ошибки

Ошибки с пустотой почти всегда повторяются по одному сценарию — значение не того «сорта», что ожидали.

Диагностика: выберите симптом

Ещё одна ловушка — путаница между ЕСТЬ NULL и ЕСТЬNULL. Пробел меняет смысл полностью: с пробелом это оператор-проверка (возвращает да/нет), без пробела — функция-подстановка (возвращает значение). Поставили не то — либо синтаксическая ошибка, либо тихо неверный результат.

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

Почему сравнение `Поле = NULL` всегда даёт ложь?
Потому что NULL — это не значение, а признак его отсутствия. Любое сравнение с NULL через =, <>, > и так далее даёт неопределённый результат, который в условии трактуется как ложь. Для NULL существует единственный корректный оператор — ЕСТЬ NULL (и обратный ЕСТЬ НЕ NULL).
Чем `ЗначениеЗаполнено()` отличается от проверок в запросе?
ЗначениеЗаполнено() — функция встроенного языка, она работает в коде, а не в тексте запроса. Зато она универсальна: сама определяет тип и считает «пустыми» NULL, Неопределено, пустую ссылку, пустую строку, ноль и пустую дату. В запросе такой единой функции нет — там проверку выбирают под конкретный тип поля.
Нужно ли проверять на NULL реквизиты основной таблицы без соединений?
Нет. В заполненной записи реквизит ссылочного типа содержит ссылку или пустую ссылку, строковый — строку или пустую строку, но не NULL. NULL в таких полях появляется только после левого, правого или полного соединения. Без соединений проверяйте на пустую ссылку или пустую строку.
Как одним условием отсечь и NULL, и ноль, и пустую ссылку?
Сведите значение к сравнимому виду через ЕСТЬNULL, подставив «пустой» эталон нужного типа: для чисел ЕСТЬNULL(Ресурс, 0) = 0, для строк ЕСТЬNULL(Поле, "") = "", для ссылок ЕСТЬNULL(Поле, ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)) = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).
Работает ли проверка на пустоту в отборах СКД и динамических списков?
В системе компоновки данных для этого есть виды сравнения «Заполнено» и «Не заполнено» — они сами учитывают и NULL, и пустые значения, писать ЕСТЬ NULL вручную не нужно. В произвольном запросе динамического списка оператор ЕСТЬ NULL и функция ЕСТЬNULL работают как в обычном запросе.