Как правильно проверять NULL в запросах 1С и не терять строки

· 9 мин

Содержание

В запросе 1С поле сравнивают с NULL только конструкцией Поле ЕСТЬ NULL (или НЕ Поле ЕСТЬ NULL), а не знаком равенства. Условие Поле = NULL синтаксически допустимо, но никогда не даёт ИСТИНА, поэтому отбор молча возвращает пустой результат. Если NULL нужно заменить на ноль, пустую строку или пустую ссылку, используют функцию ЕСТЬNULL(Поле, Замена).

Это правило общее для платформы 1С:Предприятие 8.3 и не зависит от конфигурации: одинаково работает в Бухгалтерии, ЗУП, УТ и в собственных разработках, в файловой и клиент-серверной базе.

Откуда в запросе берётся NULL и чем он отличается от НЕОПРЕДЕЛЕНО

NULL в 1С — это не «пусто», а «значения нет вообще». Обычные реквизиты справочников и документов хранятся в базе с пустым значением своего типа: ноль, пустая строка, пустая дата, пустая ссылка. NULL появляется уже при выполнении запроса.

Типичные источники NULL:

  • 🔗 Левое или правое соединение. Для строки без пары все поля присоединённой таблицы равны NULL.
  • 📦 Виртуальные таблицы регистров. Соединили документы с Остатки или ОстаткиИОбороты, а остатков по товару нет — количество и сумма придут как NULL, а не 0.
  • 🪜 Разыменование через точку. Документ.Контрагент.ИНН при пустом Контрагент даёт NULL: платформа делает неявное левое соединение.
  • 🧩 Составной тип. Поле может ссылаться на договор или на контрагента. Обращение Поле.Владелец для значения, у которого такого реквизита нет, вернёт NULL.
  • 📁 Группы иерархического справочника. Реквизит, заданный только для элементов, у групп в запросе равен NULL.
  • ❓ ВЫБОР без ИНАЧЕ. Если ни одна ветка КОГДА не сработала, результат — NULL.
  • Σ Агрегаты и ИТОГИ. СУММА по набору, где все значения NULL, возвращает NULL. В строках итогов поля, по которым не группировали, тоже NULL.

NULL против НЕОПРЕДЕЛЕНО

НЕОПРЕДЕЛЕНО — пустое значение реквизита составного типа. Оно реально хранится в базе и означает «тип не выбран». NULL — отсутствие данных в результате соединения.

ЧтоГде возникаетКак проверить в запросе
NULLсоединения, разыменование, итогиПоле ЕСТЬ NULL
НЕОПРЕДЕЛЕНОпустой реквизит составного типаПоле = НЕОПРЕДЕЛЕНО

Пустой реквизит составного типа не попадёт под ЕСТЬ NULL. А строка из левого соединения не попадёт под = НЕОПРЕДЕЛЕНО. Если нужны оба случая, пишите оба условия через ИЛИ.

Проверка поля на NULL: конструкция ЕСТЬ NULL

Синтаксис простой: выражение, затем ЕСТЬ NULL. Отрицание ставят перед выражением.

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

Запрос выдаёт товары, по которым нет ни одной строки остатков. Это классический приём «найти записи без пары»: соединить левым соединением и отобрать строки, где ключ правой таблицы равен NULL.

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

ГДЕ
	НЕ Остатки.Номенклатура ЕСТЬ NULL

Проверяйте на NULL поле, по которому шло соединение (здесь Остатки.Номенклатура), а не числовой ресурс. Ресурс теоретически может быть NULL и у найденной строки, а ключ соединения — только у строки без пары.

Знак равенства с NULL не работает : условия Поле = NULL и Поле <> NULL никогда не истинны. Ошибки при этом нет — просто пустой результат, и найти причину бывает непросто.

ЕСТЬ NULL можно использовать не только в ГДЕ, но и в полях выборки, в ВЫБОР, в условии ИМЕЮЩИЕ и в условии соединения ПО.

Признак «есть пара» прямо в выборке

Иногда строки отбирать не нужно, а нужно пометить. Тогда проверку выносят в поле:

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

Получается булево поле без NULL. По нему удобно группировать в отчёте, ставить отбор в СКД и раскрашивать строки условным оформлением.

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

ЕСТЬNULL(Выражение, Замена) возвращает первый параметр, если он не NULL, и второй — если NULL. Функцией закрывают дыры в данных перед вычислениями и выводом.

ВЫБРАТЬ
	Номенклатура.Ссылка КАК Товар,
	ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток,
	ЕСТЬNULL(Цены.Цена, 0) * ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Сумма
ИЗ
	...

Подставляйте значение того же типа, что и поле:

Тип поляЧто подставить
Число0
Строка""
ДатаДАТАВРЕМЯ(1, 1, 1)
БулевоЛОЖЬ
СсылкаЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)

Если поле — составного типа, второй параметр подбирают под тот смысл, который нужен дальше в обработке. Чаще всего это пустая ссылка основного типа.

ЕСТЬNULL или ВЫБОР

Ту же замену можно записать длиннее:

ВЫБОР
	КОГДА Остатки.КоличествоОстаток ЕСТЬ NULL
		ТОГДА 0
	ИНАЧЕ Остатки.КоличествоОстаток
КОНЕЦ

Результат одинаковый, но ЕСТЬNULL() короче и читается легче. ВЫБОР нужен, когда реакция на NULL сложнее одной подстановки: например, вывести текст «нет данных» в строковое поле или проверить сразу несколько полей.

Где ставить ЕСТЬNULL при группировке

Ставьте функцию снаружи агрегата: ЕСТЬNULL(СУММА(Т.Сумма), 0). Сама СУММА пропускает NULL при сложении, но если NULL все значения группы, итог тоже NULL. Запись СУММА(ЕСТЬNULL(Т.Сумма, 0)) даёт тот же результат, только функция вычисляется для каждой строки, а не один раз на группу.

NULL, пустая строка, ноль и пустая ссылка

Это четыре разных значения, и каждое проверяется по-своему. NULL не равен ни нулю, ни пустой строке, ни пустой ссылке.

ЗначениеУсловие в запросе
NULLПоле ЕСТЬ NULL
НольПоле = 0
Пустая строкаПоле = ""
Пустая датаПоле = ДАТАВРЕМЯ(1, 1, 1)
Пустая ссылкаПоле = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Пустой составнойПоле = НЕОПРЕДЕЛЕНО

Задача «найти документы, где контрагент не заполнен» в запросе с разыменованием требует двух условий:

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

Первое условие ловит пустую ссылку в самом документе. Второе — случай, когда Док пришёл из левого соединения и строки нет вовсе.

Строки неограниченной длины

Реквизит строки неограниченной длины (например, Комментарий) нельзя сравнивать знаком = — запрос выдаст ошибку. Обрежьте значение функцией ПОДСТРОКА:

ГДЕ
	ПОДСТРОКА(Док.Комментарий, 1, 1) = ""

С NULL такое поле проверяют как обычно: Док.Комментарий ЕСТЬ NULL работает и для неограниченных строк.

Параметр запроса со значением NULL

Если в параметр передать Null и написать Поле = &Параметр, условие тоже не сработает. Когда параметр может быть пустым, лучше заранее решить в коде, какой текст условия подставлять, или передавать пустую ссылку нужного типа.

Трёхзначная логика: ИСТИНА, ЛОЖЬ и NULL в условиях

Запросы 1С выполняются средствами СУБД, а там логика трёхзначная. Любое сравнение с участием NULL даёт не ИСТИНА и не ЛОЖЬ, а «неизвестно». Строка попадает в результат, только если условие ГДЕ равно ИСТИНА.

Как ведут себя выражения:

ВыражениеРезультат
NULL = NULLнеизвестно → строка отбрасывается
NULL <> 5неизвестно → строка отбрасывается
NULL + 10NULL
"Склад " + NULLNULL
НЕ (NULL = 5)неизвестно → строка отбрасывается
ИСТИНА ИЛИ (NULL = 5)ИСТИНА
ЛОЖЬ И (NULL = 5)ЛОЖЬ

Ловушка с отрицанием самая коварная. Допустим, у части номенклатуры булев реквизит Весовой пришёл из соединения как NULL:

ГДЕ
	НЕ Т.Весовой

Кажется, что запрос вернёт всё, что не весовое. На деле строки с NULL выпадут — НЕ NULL тоже неизвестно. Правильно так:

ГДЕ
	ЕСТЬNULL(Т.Весовой, ЛОЖЬ) = ЛОЖЬ

Та же история с конкатенацией строк и арифметикой: одно NULL-слагаемое обнуляет всё выражение. Сумма Цена * Количество + Доставка, где Доставка равна NULL, вернёт NULL, а не цену товара. Оборачивайте в ЕСТЬNULL() каждое поле, которое может прийти пустым.

Проверьте себя
Что вернёт условие ГДЕ Т.Сумма <> 0 для строки, где Сумма равна NULL?
Как найти товары без единой записи в регистре?
Что вернёт ЕСТЬNULL(СУММА(Т.Сумма), 0), если все значения Сумма в группе равны NULL?

Проверки на NULL и скорость запроса

Сама по себе проверка ЕСТЬ NULL дешёвая. Тормозить запрос начинает функция, обёрнутая вокруг поля в условии отбора или соединения.

  • ⚠️ ГДЕ ЕСТЬNULL(Т.Склад, ...) = &Склад — СУБД вычисляет функцию для каждой строки и не может применить индекс по Склад.
  • ✅ ГДЕ (Т.Склад = &Склад ИЛИ Т.Склад ЕСТЬ NULL) — тот же смысл, но поле в условии «голое».
  • ⚠️ ПО ЕСТЬNULL(А.Ключ, ...) = Б.Ключ в соединении — то же самое, индекс по Ключ не используется.

Ещё один приём: отбор по правой таблице левого соединения переносите из ГДЕ в ПО или в параметры виртуальной таблицы. Это и быстрее, и правильнее по смыслу (подробнее — в разделе про ошибки).

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

Отбор в параметрах виртуальной таблицы срабатывает до соединения, и СУБД читает меньше данных.

ЕСТЬNULL() в полях выборки на скорость почти не влияет: функция считается уже по найденным строкам. Ограничение касается только ГДЕ, ПО и ИМЕЮЩИЕ.

Длинные цепочки разыменования (Док.Договор.Владелец.ОсновнойБанковскийСчет.Банк) порождают NULL на каждом шаге и по неявному соединению на каждую точку. Если такое поле нужно для отбора, лучше выбрать его во временную таблицу и проиндексировать.

NULL в результате запроса: проверка во встроенном языке

После выполнения запроса NULL превращается в значение типа Null встроенного языка. Здесь логика уже обычная, двузначная, и знак равенства работает:

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Если Выборка.Остаток = Null Тогда
		Остаток = 0;
	Иначе
		Остаток = Выборка.Остаток;
	КонецЕсли;
КонецЦикла;

Другие способы:

  • ТипЗнч(Выборка.Остаток) = Тип("Null") — явная проверка типа.
  • ЗначениеЗаполнено(Выборка.Остаток) — вернёт Ложь и для Null, и для 0. Удобно, когда разница не важна.

Помните, что Null не равен Неопределено и не равен 0. Сложение Null + 5 во встроенном языке вызовет ошибку выполнения, а не вернёт NULL, как в запросе. Поэтому проще закрыть NULL ещё в тексте запроса через ЕСТЬNULL(), чем потом проверять в коде каждое поле.

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

Частые ошибки при сравнении с NULL

1. Условие по правой таблице в ГДЕ. Запрос с левым соединением и условием ГДЕ Остатки.Склад = &Склад теряет все строки без остатков: у них Склад равен NULL, условие не истинно. Левое соединение по факту становится внутренним. Переносите такое условие в ПО или в параметры виртуальной таблицы.

2. <> 0 вместо проверки на NULL. ГДЕ Остатки.КоличествоОстаток <> 0 отсекает и нули, и NULL. Если нужны товары без остатка, пишите ЕСТЬNULL(Остатки.КоличествоОстаток, 0) = 0.

3. Разный тип у ЕСТЬNULL. ЕСТЬNULL(Т.Контрагент, 0) смешивает ссылку и число. Поле станет составным, и сравнение со ссылкой дальше будет вести себя неожиданно. Подставляйте пустую ссылку того же справочника.

4. ИТОГИ без учёта NULL. В строках общих итогов поля группировок равны NULL. Код, который выводит Выборка.Склад.Наименование, на итоговой строке упадёт. Проверяйте тип записи выборки или поле на Null.

5. Ожидание NULL в обычной таблице. ГДЕ Док.Комментарий ЕСТЬ NULL по таблице документа без соединений ничего не найдёт: пустой комментарий хранится как пустая строка. NULL ищут только там, где он может возникнуть.

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

Можно ли написать ЕСТЬ НЕ NULL, как в SQL?
Надёжный вариант для 1С — НЕ Поле ЕСТЬ NULL. Он работает во всех версиях платформы 8.3 и однозначно читается.
Почему консоль запросов показывает пустую ячейку, а условие = "" не срабатывает?
Консоль выводит NULL как пустое место. Если ячейка пустая, но равенство с пустой строкой не проходит, значит, там NULL. Проверьте ЕСТЬ NULL.
Учитывает ли КОЛИЧЕСТВО строки с NULL?
КОЛИЧЕСТВО(*) считает все строки, КОЛИЧЕСТВО(Поле) — только строки, где поле не NULL. На этом построен подсчёт найденных пар в левом соединении.
Сработает ли ЕСТЬ NULL для пустого реквизита составного типа?
Нет. Пустой составной реквизит хранится как НЕОПРЕДЕЛЕНО. Для него нужно условие Поле = НЕОПРЕДЕЛЕНО.
Как в СКД показать ноль вместо пустой ячейки?
Надёжнее всего обернуть поле в ЕСТЬNULL(Поле, 0) прямо в тексте запроса набора данных. Тогда пустых значений не будет ни в детальных записях, ни в итогах.