Пустая ссылка в запросе: проверка, NULL и составной тип

Дата: 18.09.2026 23:23:09
Запросы
Язык запросов
Пустая ссылка
NULL
Составной тип
Комментарий:
0
Платформа:
8.3
Конфигурация:
Любая
Формы:
Любые

Пустая ссылка в запросе 1С — частый источник тихих ошибок: нужно найти документы без контрагента или товары без цены, а запрос возвращает то пустой результат, то лишние строки. Обычно синтаксис ни при чём: условие сравнивает поле с пустой ссылкой, а в поле на самом деле лежит NULL или Неопределено. В статье разобрано условие для каждого из этих значений в платформе 8.3 (файловый и клиент-серверный вариант) и приведён полный пример проверки документов для отчёта или обработки.

Как проверить поле на пустую ссылку в запросе

Пустая ссылка — полноценное значение ссылочного типа (например, СправочникСсылка.Контрагенты), которое не указывает ни на один объект в базе. Именно она лежит в реквизите, если пользователь оставил поле «Контрагент» пустым и записал документ. Чтобы проверить в условии запроса пустую ссылку на справочник или документ, используют конструкцию ЗНАЧЕНИЕ():

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

Имена объектов в примерах условные — подставьте свои (в УТ 11, например, документ заказа называется ЗаказКлиента).

Внутри скобок указывается вид объекта в единственном числе (Справочник, Документ, Перечисление), имя объекта из дерева конфигурации и слово ПустаяСсылка — без скобок, это не вызов метода. Та же конструкция служит для предопределённых элементов и значений перечислений: ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС20). Опечатка в имени всплывёт только при выполнении: для модуля текст запроса — просто строка. Проверить его заранее можно в конструкторе запроса: он сообщит о неверном имени.

Через параметр запроса

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

&НаСервереБезКонтекста
Процедура НайтиДокументыБезДоговораНаСервере()

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Реализация.Ссылка КАК Документ
	|ИЗ
	|	Документ.РеализацияТоваровУслуг КАК Реализация
	|ГДЕ
	|	Реализация.Договор = &ПустойДоговор
	|	И НЕ Реализация.ПометкаУдаления";

	Запрос.УстановитьПараметр("ПустойДоговор", Справочники.ДоговорыКонтрагентов.ПустаяСсылка());

	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		// В конфигурации на БСП 3.x вместо Сообщить() используйте
		// ОбщегоНазначения.СообщитьПользователю().
		Сообщить(СтрШаблон("Не указан договор: %1", Выборка.Документ));
	КонецЦикла;

КонецПроцедуры

Процедура не обращается к данным формы — отсюда &НаСервереБезКонтекста: контекст формы на сервер не передаётся. СтрШаблон() есть с платформы 8.3.6; в более старых релизах или режиме совместимости замените её конкатенацией строк.

По скорости способы равноценны: СУБД сравнивает поле с конкретным значением и может использовать индекс. Зато запрос с ЗНАЧЕНИЕ() самодостаточен — его можно выполнить в консоли без подготовки параметров.

Пустая ссылка на справочник, документ и другие объекты

Синтаксис одинаков для всех ссылочных типов. Правило: в тексте запроса — единственное число, в коде — менеджер во множественном числе и вызов метода.

Вид объектаВ тексте запросаВ коде (для параметра)
СправочникЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)Справочники.Контрагенты.ПустаяСсылка()
ДокументЗНАЧЕНИЕ(Документ.ЗаказПокупателя.ПустаяСсылка)Документы.ЗаказПокупателя.ПустаяСсылка()
ПеречислениеЗНАЧЕНИЕ(Перечисление.СтавкиНДС.ПустаяСсылка)Перечисления.СтавкиНДС.ПустаяСсылка()
План видов характеристикЗНАЧЕНИЕ(ПланВидовХарактеристик.ВидыСубконто.ПустаяСсылка)ПланыВидовХарактеристик.ВидыСубконто.ПустаяСсылка()
План счетовЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка)ПланыСчетов.Хозрасчетный.ПустаяСсылка()

На клиенте менеджеров объектов нет, и в коде формы пустую ссылку получают функцией ПредопределенноеЗначение() по полному имени:

&НаКлиенте
Процедура ОчиститьЗаказ(Команда)

	// Менеджеры объектов на клиенте недоступны, поэтому пустую ссылку
	// получаем по полному имени.
	Объект.Заказ = ПредопределенноеЗначение("Документ.ЗаказПокупателя.ПустаяСсылка");

КонецПроцедуры

Чем пустая ссылка отличается от NULL

NULL — не «пустое значение», а отсутствие значения. В реквизите записанного объекта его, как правило, нет: там лежит ссылка, пустая ссылка или (для составного типа) Неопределено. Исключение — иерархические справочники: реквизит с использованием «Для элемента» у групп (и «Для группы» у элементов) в запросе возвращает NULL. Условие Номенклатура.ЕдиницаИзмерения = ЗНАЧЕНИЕ(Справочник.ЕдиницыИзмерения.ПустаяСсылка) группы не найдёт. Кроме того, NULL рождается внутри запроса:

  • в левом (правом, полном) соединении: если пары не нашлось, все поля присоединённой таблицы получают NULL;
  • при обращении через точку от пустой ссылки: Реализация.Контрагент.ИНН для документа без контрагента вернёт NULL (платформа строит здесь левое соединение);
  • при обращении через точку от поля составного типа и при ВЫРАЗИТЬ(), если в поле лежит значение другого типа;
  • в агрегатных функциях (СУММА, МАКСИМУМ) над пустым набором строк и в ВЫБОР без ветки ИНАЧЕ, если ни одно условие не выполнилось.

Главное следствие: пустая ссылка и NULL не равны друг другу, и строки с NULL сравнением с пустой ссылкой не найти.

Почему «= NULL» не работает

В клиент-серверном варианте запрос 1С транслируется в SQL, а файловая СУБД реализует ту же логику: любое сравнение с NULL (=, <>, >) даёт не ИСТИНА и не ЛОЖЬ, а «неизвестно». Секция ГДЕ пропускает только строки, для которых условие истинно, так что ГДЕ Цены.Цена = NULL не вернёт ни одной строки. Для проверки существует отдельный оператор: Поле ЕСТЬ NULL, а отрицание записывается как Поле ЕСТЬ НЕ NULL или НЕ Поле ЕСТЬ NULL.

Строки с NULL не проходят и условия с неравенством: ГДЕ Цены.Цена <> 0 отбросит не только нулевые цены, но и товары вообще без записи в регистре.

ЕСТЬ NULL и функция ЕСТЬNULL()

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

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

Наличие пары проверяем по полю из условия соединения — Цены.Номенклатура. Если бы в ГДЕ стояло только Цены.Цена = 0, товары без записи в регистре в результат не попали бы.

Та же функция помогает сравнить NULL с пустой ссылкой: условие ЕСТЬNULL(Реализация.Контрагент.ГоловнойКонтрагент, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)) = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) истинно и когда контрагент не указан, и когда у него не заполнен головной контрагент. Но функция над полем мешает СУБД использовать индекс: на больших таблицах добавьте отбор по периоду или другому индексированному полю.

Незаполненный реквизит составного типа

С составным типом сложнее. Пока пользователь не выбрал тип значения, в реквизите лежит Неопределено. Если тип выбран, а значение нет — пустая ссылка этого типа. Значит, «реквизит не заполнен» для составного типа — это сразу несколько условий:

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

Аналога ЗначениеЗаполнено() в языке запросов нет — варианты приходится перечислять явно. Если типов много или состав типа меняется при обновлениях, надёжнее собрать массив пустых значений в коде по метаданным и передать его параметром, как в полном примере ниже. Он учитывает и примитивные типы: если в составной тип входит Строка, Число или Дата, незаполненными будут и "", 0, пустая дата. А для поля из левого соединения добавьте ещё ЕСТЬ NULL.

У приёма два ограничения. Строки неограниченной длины в условиях запроса сравнивать нельзя: если такая строка входит в составной тип, задайте ей фиксированную длину или проверяйте поле в коде после запроса. А для ЛюбаяСсылка и широких определяемых типов массив разрастётся до сотен значений — там проще явно проверить Неопределено и нужные типы.

Чтобы проверить не заполненность, а тип значения, используйте оператор ССЫЛКА: Реализация.Основание ССЫЛКА Документ.ЗаказПокупателя. Он проверяет только тип, поэтому истинен и для пустой ссылки на заказ.

Полный пример: документы с незаполненными реквизитами

Функция для серверного общего модуля собирает всё сказанное в пакетный запрос. Она находит проведённые реализации за период, у которых не указан контрагент или заказ, не заполнено основание составного типа или нет движений по регистру продаж.

// Серверный общий модуль (или модуль объекта отчета/обработки).
// Имена объектов метаданных условные: замените их на имена из своей конфигурации.

// Находит проведенные реализации за период, в которых:
//  - не указан контрагент (пустая ссылка на справочник);
//  - не указан заказ (пустая ссылка на документ);
//  - не заполнено основание (реквизит составного типа);
//  - нет ни одной записи в регистре накопления Продажи (NULL после левого соединения).
//
// Параметры:
//  ДатаНачала    - Дата - начало периода.
//  ДатаОкончания - Дата - конец периода.
//
// Возвращаемое значение:
//  ТаблицаЗначений - колонки Документ, Дата, НетКонтрагента, НетЗаказа, НетОснования, НетДвижений.
//
Функция РеализацииСНезаполненнымиДанными(Знач ДатаНачала, Знач ДатаОкончания) Экспорт

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Реализация.Ссылка КАК Ссылка,
	|	Реализация.Дата КАК Дата,
	|	Реализация.Контрагент КАК Контрагент,
	|	Реализация.Заказ КАК Заказ,
	|	Реализация.Основание КАК Основание
	|ПОМЕСТИТЬ ВТ_Документы
	|ИЗ
	|	Документ.РеализацияТоваровУслуг КАК Реализация
	|ГДЕ
	|	Реализация.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
	|	И Реализация.Проведен
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	Ссылка
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	Продажи.Регистратор КАК Регистратор
	|ПОМЕСТИТЬ ВТ_Движения
	|ИЗ
	|	РегистрНакопления.Продажи КАК Продажи
	|ГДЕ
	|	Продажи.Регистратор В
	|			(ВЫБРАТЬ
	|				ВТ_Документы.Ссылка
	|			ИЗ
	|				ВТ_Документы КАК ВТ_Документы)
	|
	|ИНДЕКСИРОВАТЬ ПО
	|	Регистратор
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Реализация.Ссылка КАК Документ,
	|	Реализация.Дата КАК Дата,
	|	ВЫБОР
	|		КОГДА Реализация.Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
	|			ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК НетКонтрагента,
	|	ВЫБОР
	|		КОГДА Реализация.Заказ = ЗНАЧЕНИЕ(Документ.ЗаказПокупателя.ПустаяСсылка)
	|			ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК НетЗаказа,
	|	ВЫБОР
	|		КОГДА Реализация.Основание = НЕОПРЕДЕЛЕНО
	|				ИЛИ Реализация.Основание В (&ПустыеЗначенияОснования)
	|			ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК НетОснования,
	|	ВЫБОР
	|		КОГДА Движения.Регистратор ЕСТЬ NULL
	|			ТОГДА ИСТИНА
	|		ИНАЧЕ ЛОЖЬ
	|	КОНЕЦ КАК НетДвижений
	|ИЗ
	|	ВТ_Документы КАК Реализация
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Движения КАК Движения
	|		ПО Реализация.Ссылка = Движения.Регистратор
	|ГДЕ
	|	Реализация.Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
	|	ИЛИ Реализация.Заказ = ЗНАЧЕНИЕ(Документ.ЗаказПокупателя.ПустаяСсылка)
	|	ИЛИ Реализация.Основание = НЕОПРЕДЕЛЕНО
	|	ИЛИ Реализация.Основание В (&ПустыеЗначенияОснования)
	|	ИЛИ Движения.Регистратор ЕСТЬ NULL
	|
	|УПОРЯДОЧИТЬ ПО
	|	Дата";

	Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
	Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаОкончания));
	Запрос.УстановитьПараметр("ПустыеЗначенияОснования", ПустыеЗначенияСоставногоТипа(
		Метаданные.Документы.РеализацияТоваровУслуг.Реквизиты.Основание.Тип));

	Возврат Запрос.Выполнить().Выгрузить();

КонецФункции

// Возвращает массив "пустых" значений для каждого типа из описания типов:
// пустую ссылку для ссылочного типа, "" для Строки, 0 для Числа, пустую дату для Даты.
// Булево пропускается: Ложь - это осмысленное значение, а не "не заполнено".
//
// Ограничения: функция рассчитана на составной тип из нескольких конкретных типов.
// Для ЛюбаяСсылка и широких определяемых типов массив получится из сотен значений,
// в таком случае проверяйте Неопределено и нужные типы явно. Строку неограниченной
// длины в запросе нельзя сравнить через = или В, поэтому она тоже пропускается:
// такой реквизит проверяйте в коде после выполнения запроса.
//
// Параметры:
//  ОписаниеТиповРеквизита - ОписаниеТипов - тип реквизита из метаданных.
//
// Возвращаемое значение:
//  Массив - пустые значения допустимых типов, кроме Булево и Строки неограниченной длины.
//
Функция ПустыеЗначенияСоставногоТипа(ОписаниеТиповРеквизита)

	Результат = Новый Массив;

	Для Каждого ТекущийТип Из ОписаниеТиповРеквизита.Типы() Цикл

		Если ТекущийТип = Тип("Булево") Тогда
			Продолжить;
		КонецЕсли;

		Если ТекущийТип = Тип("Строка")
			И ОписаниеТиповРеквизита.КвалификаторыСтроки.Длина = 0 Тогда
			Продолжить;
		КонецЕсли;

		МассивТипов = Новый Массив;
		МассивТипов.Добавить(ТекущийТип);

		// Описание из одного типа приводит Неопределено к значению по умолчанию
		// этого типа: для ссылки - пустая ссылка, для Строки/Числа/Даты - "", 0, пустая дата.
		ОписаниеОдногоТипа = Новый ОписаниеТипов(МассивТипов);
		Результат.Добавить(ОписаниеОдногоТипа.ПривестиЗначение(Неопределено));

	КонецЦикла;

	Возврат Результат;

КонецФункции

Разбор ключевых строк

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

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

  • ГДЕ Поле = NULL. Запрос выполняется без ошибок и ничего не находит. Правильно — Поле ЕСТЬ NULL.
  • Сравнение с пустой ссылкой после левого соединения. Поля присоединённой таблицы без пары равны NULL, а не пустой ссылке. Используйте ЕСТЬ NULL или оберните поле в ЕСТЬNULL().
  • Множественное число, скобки или опечатка внутри ЗНАЧЕНИЕ. ЗНАЧЕНИЕ(Справочники.Контрагенты.ПустаяСсылка), ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка()) или имя несуществующего объекта — запрос не скомпилируется. Платформа сообщит об ошибке в тексте запроса и укажет строку и позицию в фигурных скобках, например {(7, 32)}.
  • Забытый параметр. Если в тексте есть &ПустойДоговор, а УстановитьПараметр() для него не вызван, выполнение прервётся с ошибкой о незаданном значении параметра. Имя в тексте и в коде должно совпадать до буквы.
  • Составной тип проверяется одним сравнением. Сравнение с пустой ссылкой одного типа пропустит Неопределено и пустые значения других типов.
  • Отбор по присоединённой таблице в ГДЕ. Условие на поле правой таблицы после левого соединения (например, Цены.ТипЦен = &ТипЦен) отсекает строки с NULL, и соединение фактически становится внутренним. Такой отбор ставьте в условие ПО или в параметры виртуальной таблицы. Исключение — ЕСТЬ NULL и ЕСТЬNULL(): их в ГДЕ ставят как раз для поиска строк без пары.

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

Есть ли в языке запросов функция ЗначениеЗаполнено?

Нет. В тексте запроса незаполненность проверяется сравнением с пустой ссылкой, НЕОПРЕДЕЛЕНО и ЕСТЬ NULL. В коде, после выполнения запроса, проверяют функцией ЗначениеЗаполнено(): она возвращает Ложь и для пустой ссылки, и для NULL. Есть и метод ссылки Пустая(), но если в поле пришёл NULL, его вызов даст ошибку.

Как отобрать, наоборот, только заполненные ссылки?

Условием Поле <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка). Строки с NULL такое условие тоже не пропустит, исключать их отдельно не нужно. Для составного типа — НЕ (Поле = НЕОПРЕДЕЛЕНО ИЛИ Поле В (&ПустыеЗначения)).

Как заменить NULL на пустую ссылку в результате запроса?

Функцией ЕСТЬNULL(Поле, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)) в секции ВЫБРАТЬ. Это полезно, когда результат дальше обрабатывает код, который не ожидает NULL.

Работает ли ЗНАЧЕНИЕ() в СКД и динамическом списке?

Да. Текст набора данных СКД и запрос динамического списка пишутся на том же языке запросов, так что ЗНАЧЕНИЕ(), ЕСТЬ NULL и ЕСТЬNULL() в них работают так же. Параметры задаются через настройки компоновки или параметры списка.

Как проверить в запросе пустую дату?

Сравнением с ДАТАВРЕМЯ(1, 1, 1): ГДЕ Реализация.СрокОплаты = ДАТАВРЕМЯ(1, 1, 1). Незаполненная дата — это конкретное значение 01.01.0001 00:00:00, а не NULL, и ЕСТЬ NULL её не найдёт.

Когда проще заказать

Если вместо одного условия нужен полноценный контроль — отчёт на СКД, который ищет незаполненные документы по всей базе, или проверка, не дающая провести документ без обязательных реквизитов, — такую задачу быстрее отдать программисту. На 1c-only.ru можно заказать разработку отчётов и обработок 1С или просто разместить задачу: оценка бесплатная, ответ — в течение рабочего дня.

По теме

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.


1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.