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