NULL, пустая ссылка и пустая строка в запросах 1С: как их проверять
Содержание
NULL ловят оператором ЕСТЬ NULL, а подменяют функцией ЕСТЬNULL(поле, значение). Пустую ссылку сравнивают со значением ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка), а пустую строку — с "". Это три разные проверки, и путать их нельзя: поле из левого соединения может быть NULL, реквизит-ссылка — пустой ссылкой, а текстовый реквизит — пустой строкой.
Чаще всего на пустоту проверяют результат соединения («у товара нет цены», «у документа не заполнен договор») или ресурс регистра. Ниже — как это делать правильно для каждого типа данных, чем ЕСТЬ NULL отличается от ЕСТЬNULL, и почему Поле = NULL молча не работает
.
Три разных «пустых» значения в запросе
В языке запросов 1С «пусто» — это не одно состояние, а минимум три. Они хранятся по-разному и проверяются разными средствами.
- 🔹 NULL — это не значение, а отсутствие значения. Появляется, когда в источнике данных строки просто нет (левое соединение не нашло пару, у регистра нет записи).
- 🔸 Пустая ссылка — это полноценное значение ссылочного типа с нулевым GUID. Тип у неё есть, значение «никто не выбран».
- 🔹 Пустая строка
""— обычная строка нулевой длины. Тоже полноценное значение, не NULL.
Для числа и даты «пусто» обычно означает 0 и пустую дату ('00010101000000'), и это тоже значения, а не NULL.
Главное следствие: NULL нельзя сравнивать через =. Выражение Поле = NULL не вернёт ИСТИНА никогда, даже если поле действительно NULL. Для NULL есть отдельный оператор — ЕСТЬ NULL.
ЕСТЬ NULL в условии или функцию ЕСТЬNULL(Поле, значение) для подстановки.Поле = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).Поле = "". Если поле приходит из соединения — оберните в ЕСТЬNULL(Поле, "").ЕСТЬNULL(Ресурс, 0) = 0 — это учитывает и отсутствие записи, и честный ноль.Откуда в запросе берётся NULL
Прежде чем проверять, стоит понимать, откуда NULL вообще взялся. Его не бывает «просто так» в реквизитах основной таблицы — заполненный реквизит ссылочного типа содержит либо ссылку, либо пустую ссылку, но не NULL.
NULL приходит из трёх основных источников:
- Левое, правое или полное соединение, в котором не нашлась пара. Поля неприсоединённой таблицы заполняются NULL.
- Ресурсы и реквизиты регистров, по которым нет записей в выбранном периоде или разрезе.
- Оператор
ВЫБОРбез веткиИНАЧЕ, когда ни одно условиеКОГДАне сработало.
Типичный пример — список товаров с ценами из регистра сведений:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
Цены.Цена КАК Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Номенклатура.Ссылка
Для товаров без записи о цене поле Цена будет не нулём, а именно 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(Поле, 0) до суммирования.ЕСТЬNULL(Поле, "") = "".0 для числа, "" для строки.ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка), а не "" и не 0.Ещё одна ловушка — путаница между ЕСТЬ NULL и ЕСТЬNULL. Пробел меняет смысл полностью: с пробелом это оператор-проверка (возвращает да/нет), без пробела — функция-подстановка (возвращает значение). Поставили не то — либо синтаксическая ошибка, либо тихо неверный результат.
Частые вопросы
Почему сравнение `Поле = NULL` всегда даёт ложь?
=, <>, > и так далее даёт неопределённый результат, который в условии трактуется как ложь. Для NULL существует единственный корректный оператор — ЕСТЬ NULL (и обратный ЕСТЬ НЕ NULL).Чем `ЗначениеЗаполнено()` отличается от проверок в запросе?
ЗначениеЗаполнено() — функция встроенного языка, она работает в коде, а не в тексте запроса. Зато она универсальна: сама определяет тип и считает «пустыми» NULL, Неопределено, пустую ссылку, пустую строку, ноль и пустую дату. В запросе такой единой функции нет — там проверку выбирают под конкретный тип поля.Нужно ли проверять на NULL реквизиты основной таблицы без соединений?
Как одним условием отсечь и NULL, и ноль, и пустую ссылку?
ЕСТЬNULL, подставив «пустой» эталон нужного типа: для чисел ЕСТЬNULL(Ресурс, 0) = 0, для строк ЕСТЬNULL(Поле, "") = "", для ссылок ЕСТЬNULL(Поле, ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка)) = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).Работает ли проверка на пустоту в отборах СКД и динамических списков?
ЕСТЬ NULL вручную не нужно. В произвольном запросе динамического списка оператор ЕСТЬ NULL и функция ЕСТЬNULL работают как в обычном запросе.