Как в запросе 1С отличить Неопределено, NULL и пустую ссылку
Содержание
Код Если Поле = Неопределено Тогда работает во встроенном языке, но в запросе та же проверка часто молчит: строки с «пустым» полем в результат не попадают, а должны. Причина в том, что из соединения приходит не Неопределено, а NULL — другое значение с другими правилами сравнения.
Разберём пять рабочих способов проверить поле в тексте запроса: оператор ЕСТЬ NULL, конструкцию ВЫБОР КОГДА, функцию ЕСТЬNULL, приведение ВЫРАЗИТЬ и обработку через временные таблицы и соединения. По каждому — пример и случай, когда он уместен.
Почему NULL и Неопределено в запросе — это два разных значения
В тексте запроса поле может оказаться в одном из трёх состояний, и путать их нельзя:
- 🟦 NULL — значения нет вообще. Чаще всего появляется, когда левое соединение (
ЛЕВОЕ СОЕДИНЕНИЕ) не нашло пару. Это не «ноль» и не пустая строка, а отсутствие данных. - 🟨 Неопределено — конкретное значение типа
Неопределено. Хранится, например, в поле составного типа, которое не заполнили, или в ресурсе регистра сведений с типомХарактеристика. - 🟩 Пустая ссылка — заполненный реквизит-ссылка со значением «пусто» (
Справочник.Контрагенты.ПустаяСсылка). Это полноценное значение, а не отсутствие.
Главная ловушка в сравнении. Оператор = с NULL не работает
так, как ожидают: любое сравнение с NULL через = или <> не даёт «истину». Поэтому проверка вида ГДЕ Поле = НЕОПРЕДЕЛЕНО не поймает строки, где поле пришло из незаполненного соединения.
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
Цены.Цена КАК Цена
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Товары.Ссылка
ГДЕ
Цены.Цена = НЕОПРЕДЕЛЕНО
Этот отбор вернёт пустой результат, даже если цен нет ни у одной позиции. Для позиций без цены поле Цены.Цена равно NULL, а не Неопределено, поэтому условие = НЕОПРЕДЕЛЕНО ложно.
Правильно — специальный оператор ЕСТЬ NULL:
ГДЕ
Цены.Цена ЕСТЬ NULL
А для обратной задачи (оставить только заполненные строки) — ЕСТЬ NOT NULL. Запомните правило: значения из соединений проверяют на ЕСТЬ NULL, а собственные значения поля — на = НЕОПРЕДЕЛЕНО.
Способ 1. Подмена значения через ВЫБОР КОГДА
Конструкция ВЫБОР КОГДА … ТОГДА … ИНАЧЕ … КОНЕЦ — аналог CASE WHEN. Она позволяет прямо в выборке заменить NULL на понятное значение и дальше работать уже с ним.
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ВЫБОР
КОГДА Цены.Цена ЕСТЬ NULL
ТОГДА 0
ИНАЧЕ Цены.Цена
КОНЕЦ КАК Цена
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Товары.Ссылка
ВЫБОР незаменим, когда замена зависит от нескольких условий или когда нужно вернуть разные типы в разных ветках — например, текстовую метку «нет данных» вместо числа.
Внутри одного ВЫБОР можно перечислить несколько КОГДА. Проверки идут сверху вниз, срабатывает первая истинная — порядок условий имеет значение.
Минус способа — громоздкость. Если задача только «поставить ноль вместо пустоты», проще взять функцию из следующего раздела.
Способ 2. Функция ЕСТЬNULL для значения по умолчанию
ЕСТЬNULL(<выражение>, <значение_замены>) — короткая форма того же приёма. Если первый аргумент равен NULL, возвращается второй; иначе — само выражение.
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Товары.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
ПО Остатки.Номенклатура = Товары.Ссылка
Два важных нюанса:
ЕСТЬNULLзаменяет толькоNULL. ЗначениеНеопределеноона не трогает — для него нужна проверка= НЕОПРЕДЕЛЕНОилиВЫБОР.- Тип значения замены должен быть совместим с типом поля. Подставлять строку
"нет"вместо числовой цены нельзя — результат получит составной тип, и дальнейшая арифметика по нему сломается.
Отдельный частый случай — агрегаты. СУММА, МАКСИМУМ, МИНИМУМ по пустому набору строк возвращают NULL, а не ноль. Поэтому итоговые суммы почти всегда оборачивают:
ВЫБРАТЬ
ЕСТЬNULL(СУММА(Продажи.Выручка), 0) КАК Выручка
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
Ещё одно применение проверки — необязательный отбор по параметру запроса. Когда параметр могут не заполнить (передали Неопределено), условие в ГДЕ строят так, чтобы при пустом параметре фильтр отключался:
ГДЕ
(&Контрагент = НЕОПРЕДЕЛЕНО ИЛИ Продажи.Контрагент = &Контрагент)
Если в параметр &Контрагент ничего не передали, первая часть условия истинна и в результат попадают все строки. Передали конкретного контрагента — работает отбор. Этот приём избавляет от сборки текста запроса из кусков в коде: один запрос обслуживает и отчёт по всем, и отчёт по одному. Для параметра здесь используют именно = НЕОПРЕДЕЛЕНО, потому что это собственное значение параметра, а не NULL из соединения.
Способ 3. Приведение типа через ВЫРАЗИТЬ
С полями составного типа возникает своя задача. Когда поле может хранить ссылки разных типов (классический пример — Регистратор в регистре накопления), обратиться к его реквизитам напрямую нельзя: у документа реализации есть Контрагент, а у документа списания его нет.
Оператор ВЫРАЗИТЬ(<поле> КАК <тип>) приводит значение к одному типу. Если фактический тип строки не совпал — результат становится NULL, и его уже можно проверить привычным ЕСТЬ NULL.
ВЫБРАТЬ
Движения.Регистратор,
ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент
ИЗ
РегистрНакопления.Продажи КАК Движения
В строках, где регистратор — не реализация, поле Контрагент вернёт NULL. Это ожидаемо и правильно: тип просто не подошёл.
Без ВЫРАЗИТЬ попытка взять реквизит через точку у поля составного типа либо вернёт неожиданный NULL по всем строкам, либо приведёт к неоптимальному соединению со всеми таблицами типов сразу. Приведение типа — не только про проверку, но и про скорость запроса.
ВЫРАЗИТЬ нужен именно для составных типов. Для простого поля (число, дата, одиночная ссылка) он избыточен.
Способ 4. Отбор пустых значений через временные таблицы
Когда в запросе несколько уровней соединений, вложенные проверки на NULL разрастаются и мешают читать текст. В таких случаях данные удобно разложить по временным таблицам: сначала собрать промежуточный набор, затем работать с ним как с обычной таблицей.
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура,
ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ПОМЕСТИТЬ ВТ_Цены
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Товары.Ссылка
;
ВЫБРАТЬ
ВТ_Цены.Номенклатура,
ВТ_Цены.Цена
ИЗ
ВТ_Цены КАК ВТ_Цены
ГДЕ
ВТ_Цены.Цена = 0
Что это даёт:
- 🧱
NULL«гасится» один раз на входе, во второй части запроса поле уже гарантированно заполнено. - ⚡ По временной таблице можно построить индекс (
ИНДЕКСИРОВАТЬ ПО Номенклатура), и последующие соединения ускоряются. - 🔍 Логику проще отлаживать: промежуточную таблицу видно в консоли запросов отдельным шагом.
Способ особенно выручает в сложных отчётах на СКД и в пакетных запросах, где одно и то же проверенное множество используется несколько раз. Собранную таблицу можно соединять с другими и фильтровать повторно, не возвращаясь каждый раз к исходным регистрам и не плодя проверки на NULL в каждом соединении.
Если проверенное множество нужно только для того, чтобы отсеять по нему строки, вместо соединения иногда удобнее конструкция В (ВЫБРАТЬ …) или НЕ В (ВЫБРАТЬ …) — она не добавляет полей и не создаёт новых NULL.
Способ 5. Проверка в условии соединения и в отборе ГДЕ
Пятый приём — не отдельная функция, а правильное размещение проверки. От того, где стоит условие — в ПО, в ГДЕ или в выборке, — зависит, какие строки останутся.
Чтобы найти записи без пары (например, номенклатуру, которой ни разу не назначали цену), отбор по ЕСТЬ NULL ставят после левого соединения:
ВЫБРАТЬ
Товары.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Цены.Номенклатура = Товары.Ссылка
ГДЕ
Цены.Цена ЕСТЬ NULL
Здесь важная тонкость, на которой спотыкаются чаще всего:
Любой обычный отбор по полю правой таблицы в ГДЕ (скажем, Цены.Цена > 100) отбрасывает строки с NULL и фактически превращает левое соединение во внутреннее. Если нужно сохранить все строки слева, переносите условие в секцию ПО соединения или оборачивайте поле в ЕСТЬNULL.
Сравните два варианта. В первом останутся только товары с ценой выше 100, товары без цены исчезнут:
// ГДЕ — левое соединение превращается во внутреннее
ГДЕ Цены.Цена > 100
Во втором сохранятся все товары, а дорогие будут помечены:
// ЕСТЬNULL в ВЫБОР — все строки на месте
ВЫБОР КОГДА ЕСТЬNULL(Цены.Цена, 0) > 100 ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ КАК ДорогойТовар
Какой способ выбрать
Все пять приёмов решают разные задачи — их не выбирают «по вкусу», а берут под ситуацию.
| Задача | Чем проверять |
|---|---|
| Подставить значение вместо пустоты | ЕСТЬNULL(Поле, 0) |
| Замена по нескольким условиям | ВЫБОР КОГДА … ТОГДА … |
| Найти строки без пары в соединении | Поле ЕСТЬ NULL в ГДЕ |
| Поле составного типа (Регистратор и т.п.) | ВЫРАЗИТЬ(Поле КАК Тип) + ЕСТЬ NULL |
| Сложный многоуровневый запрос | Временная таблица, гашение NULL на входе |
| Собственное значение Неопределено | Поле = НЕОПРЕДЕЛЕНО |
| Незаполненный реквизит-ссылка | = ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка) |
Чтобы быстро определить свой случай, сверьтесь с деревом решений — оно отталкивается от того, откуда взялось «пустое» поле.
NULL. Проверяйте Поле ЕСТЬ NULL, подставляйте значение через ЕСТЬNULL(Поле, …).ВЫРАЗИТЬ(Поле КАК Тип) и проверьте результат на ЕСТЬ NULL.ЗНАЧЕНИЕ(Справочник.Имя.ПустаяСсылка).NULL. Оборачивайте в ЕСТЬNULL(СУММА(…), 0).Поле = НЕОПРЕДЕЛЕНО.Ошибки, из-за которых проверка срабатывает не так
Собрал промахи, которые чаще всего приводят к «пустому» или, наоборот, раздутому результату.
Сравнение NULL через знак равенства. Запись ГДЕ Поле = NULL или Поле <> NULL не работает: сравнение с NULL оператором = никогда не истинно. Только ЕСТЬ NULL и ЕСТЬ NOT NULL.
= НЕОПРЕДЕЛЕНО для поля из соединения. Самый частый случай. Поле, которого не оказалось в правой таблице, равно NULL, а не Неопределено. Проверка на НЕОПРЕДЕЛЕНО его пропустит.
Путаница пустой ссылки и NULL. Незаполненный реквизит Контрагент в документе — это пустая ссылка, конкретное значение. Он не равен NULL и не равен Неопределено. Чтобы отобрать документы без контрагента, сравнивайте с ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка).
Несовместимый тип в ЕСТЬNULL. Если подставить замену другого типа (строку вместо числа), поле получит составной тип. Дальнейшие суммирование, группировка или сортировка по такому полю дадут неожиданный результат.
Отбор правой таблицы в ГДЕ вместо ПО. Разобрали выше: это молча превращает левое соединение во внутреннее и теряет строки, которые вы рассчитывали сохранить.
Частые вопросы
Чем NULL отличается от пустой ссылки?
NULL — это отсутствие значения, обычно из несработавшего соединения. Пустая ссылка — заполненное поле со значением «пусто» конкретного типа справочника или документа. Проверяются они по-разному: ЕСТЬ NULL против = ЗНАЧЕНИЕ(…ПустаяСсылка).Почему `Поле = НЕОПРЕДЕЛЕНО` иногда работает, а иногда нет?
Неопределено (например, незаполненный ресурс составного типа). Не работает, когда «пустота» пришла из соединения — там лежит NULL, и нужен оператор ЕСТЬ NULL.Можно ли проверить на Неопределено прямо в условии ГДЕ?
ГДЕ Поле ЕСТЬ NULL или ЕСТЬ NOT NULL. Для собственного значения поля — ГДЕ Поле = НЕОПРЕДЕЛЕНО. Смешивать операторы нельзя.Как заменить NULL на пустую дату или строку?
ЕСТЬNULL(Поле, ДАТАВРЕМЯ(1, 1, 1)) для даты, ЕСТЬNULL(Поле, "") для строки. Главное, чтобы тип замены совпадал с типом поля.Нужен ли ВЫРАЗИТЬ для обычного реквизита?
ВЫРАЗИТЬ нужен только для полей составного типа, когда вы обращаетесь к реквизитам через точку. Для числа, даты или одиночной ссылки он лишний.