При разработке запросов для проведения документов, формирования движений регистров или построения отчетов нередко возникает задача отобрать только те строки, где заполнен реквизит составного ссылочного типа. Однако стандартная конструкция вида НЕ Поле = Неопределено в таких случаях часто пропускает незаполненные строки. Выясним причину такого поведения платформы 1С:Предприятие и разберем проверенные способы решения задачи.
Рассмотрим, в каких состояниях может находиться реквизит составного ссылочного типа в базе данных. Для такого поля существует несколько вариантов отсутствия полезного значения:
Неопределено. В этом случае проверка НЕ Поле = Неопределено сработает корректно.00000000-0000-0000-0000-000000000000). Для СУБД пустая ссылка документа или справочника — это валидное типизированное значение. Соответственно, сравнение ПустаяСсылка = Неопределено возвращает Ложь, а отрицание НЕ ПустаяСсылка = Неопределено дает Истина. В результате пустая ссылка ошибочно считается заполненным полем.NULL. В трехзначной логике SQL проверка NULL = Неопределено дает результат NULL, и условие в секции ГДЕ не проходит.Таким образом, для составного типа «не заполнено» означает либо Неопределено, либо пустую ссылку любого из входящих в состав типов метаданных.
Проанализируем наиболее надежный и производительный вариант отбора. Чтобы не описывать в тексте запроса каждую пустую ссылку вручную и не создавать неявных соединений с таблицами базы данных, мы можем программно получить все возможные пустые значения для типа данного реквизита и передать их в запрос в виде массива.
Разберем функцию, аналогичную типовым механизмам «Библиотеки стандартных подсистем» (БСП):
Функция ПолучитьМассивПустыхЗначенийПоОписаниюТипов(ОписаниеТипов) Экспорт
МассивПустыхЗначений = Новый Массив;
МассивПустыхЗначений.Добавить(Неопределено);
Для Каждого Тип Из ОписаниеТипов.Типы() Цикл
МассивПустыхЗначений.Добавить(Новый(Тип));
КонецЦикла;
Возврат МассивПустыхЗначений;
КонецФункции
Посмотрим на пример использования этой функции при подготовке параметров запроса:
ОписаниеТиповСделки = Метаданные.Документы.ПоступлениеТоваровУслуг.ТабличныеЧасти.Товары.Реквизиты.ЗаказПоставщику.Тип;
МассивПустых = ПолучитьМассивПустыхЗначенийПоОписаниюТипов(ОписаниеТиповСделки);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаТовары.НомерСтроки КАК НомерСтроки,
| ТаблицаТовары.ЗаказПоставщику КАК Заказ
|ИЗ
| ВтТаблицаТовары КАК ТаблицаТовары
|ГДЕ
| НЕ ТаблицаТовары.ЗаказПоставщику В (&МассивПустыхЗначений)
| И ТаблицаТовары.ЗаказПоставщику ЕСТЬ НЕ NULL";
Запрос.УстановитьПараметр("МассивПустыхЗначений", МассивПустых);
В этом сценарии СУБД сравнивает только код типа и идентификатор ссылки, не обращаясь к реальным таблицам документов, что обеспечивает высокую скорость выполнения запроса.
На практике программисты часто используют конструкцию с обращением к свойству Ссылка через точку:
ГДЕ
НЕ ТаблицаТовары.ЗаказПоставщику.Ссылка ЕСТЬ NULL
Рассмотрим, как работает эта конструкция. При обращении «через точку» платформа выполняет соединение с таблицами тех объектов, типы которых могут храниться в реквизите. Для пустой ссылки или значения Неопределено соответствующей строки в физической таблице документа не существует, поэтому соединение вернет NULL, и условие отсечет незаполненное поле (включая «битые» ссылки).
Важное ограничение: данный подход категорически не рекомендуется использовать для полей с широким составным типом. Разыменование составного поля приводит к генерации неявных LEFT JOIN со всеми таблицами, входящими в описание типа реквизита. Если в тип реквизита включены десятки видов документов, запрос превратится в громоздкую конструкцию с десятками соединений, что приведет к резкому падению производительности и неоптимальному плану выполнения.
Если в конкретном алгоритме нас интересуют только определенные документы из всего списка доступных типов, разберем ограничение типа через функцию ВЫРАЗИТЬ:
ВЫБРАТЬ
ТаблицаТовары.НомерСтроки КАК НомерСтроки,
ВЫРАЗИТЬ(ТаблицаТовары.ЗаказПоставщику КАК Документ.ЗаказПоставщику) КАК Заказ
ИЗ
ВтТаблицаТовары КАК ТаблицаТовары
ГДЕ
ВЫРАЗИТЬ(ТаблицаТовары.ЗаказПоставщику КАК Документ.ЗаказПоставщику) <> ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка)
И НЕ ВЫРАЗИТЬ(ТаблицаТовары.ЗаказПоставщику КАК Документ.ЗаказПоставщику) ЕСТЬ NULL
При таком подходе платформа выполнит соединение исключительно с указанной таблицей Документ.ЗаказПоставщику, исключив лишнюю нагрузку на сервер баз данных.
Обратим внимание на источник данных. Если в запросе используется временная таблица (например, ВтТаблицаТовары), которая загружается из таблицы значений или табличной части документа, наиболее оптимальным решением часто является предварительная фильтрация данных встроенным языком 1С.
В языке 1С системная функция ЗначениеЗаполнено() универсальна: она возвращает Ложь для Неопределено, NULL и пустых ссылок абсолютно любых типов метаданных. Отфильтровав таблицу перед передачей в менеджер временных таблиц с помощью ЗначениеЗаполнено(), мы полностью исключаем необходимость проверки заполненности составного поля внутри текста запроса.