Как в запросе 1С проверить реквизит составного типа на заполненность?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

В чем сложность проверки составного типа

В отличие от простого реквизита конкретного типа, невыбранное значение составного типа в базе данных может принимать разные представления:

  1. Значение Неопределено — когда значение в реквизите вообще не было выбрано пользователем при интерактивном вводе.
  2. Пустая ссылка определенного типа — например, когда тип документа уже был определен, но сам документ очищен. Значение имеет вид 00000000-0000-0000-0000-000000000000 с конкретным идентификатором типа метаданных.
  3. Битая ссылка — ситуация «Объект не найден», когда документ был удален непосредственным удалением, но ссылка на него осталась в базе.

Если обратиться через точку к реквизиту объекта (например, Сделка.Ответственный), платформа на уровне СУБД генерирует левые соединения (LEFT JOIN) абсолютно со всеми таблицами документов, входящими в составной тип. Если составной тип включает десятки документов, запрос превращается в тяжелую конструкцию из множества соединений и блоков CASE WHEN, что приводит к деградации производительности.

Способ 1. Проверка через разыменование ссылки и условие ЕСТЬ НЕ NULL

Проанализируем базовый способ отсечения незаполненных данных. В таблицах СУБД для пустой ссылки не существует реальной строки. Это означает, что при выполнении неявного соединения попытка получить идентификатор из целевой таблицы вернет NULL, если документа физически нет в базе.

Посмотрим на пример запроса, где проверяется не сам реквизит шапки, а поле Ссылка связанного документа:


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

Выясним причину, почему конструкция Реализация.Сделка.Ссылка ЕСТЬ НЕ NULL надежнее, чем обращение к Сделка.Ответственный:

  1. Поле Ссылка гарантированно присутствует у абсолютно любого ссылочного типа метаданных. Если в конфигурацию добавят новый документ в составной тип Сделка, у которого нет реквизита Ответственный, запрос с разыменованием Ссылка не упадет с ошибкой выполнения.
  2. Условие корректно отсекает значение Неопределено, пустые ссылки любых типов документов и битые ссылки («Объект не найден»).

Способ 2. Проверка через список пустых ссылок в параметрах

Рассмотрим альтернативный способ, когда разработчик передает список всех возможных пустых значений из встроенного языка 1С. В этом случае мы исключаем разыменование через точку на уровне СУБД:


МассивПустых = Новый Массив;
МассивПустых.Добавить(Неопределено);
МассивПустых.Добавить(Документы.ЗаказПокупателя.ПустаяСсылка());
МассивПустых.Добавить(Документы.ЗаказПоставщику.ПустаяСсылка());
МассивПустых.Добавить(Документы.СчетНаОплатуПокупателю.ПустаяСсылка());
МассивПустых.Добавить(Документы.СчетНаОплатуПоставщика.ПустаяСсылка());

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

Однако проанализируем ограничения данного метода: отрицательное условие НЕ ... В (&МассивПустых) по составному полю в большинстве СУБД приводит к полному сканированию таблицы (Scan) вместо поиска по индексу (Seek), так как составной реквизит хранится в виде нескольких физических колонок (код типа и двоичный идентификатор). Кроме того, если в базе останется «битая» ссылка, этот фильтр ее пропустит, поскольку битая ссылка не равна пустой ссылке.

Способ 3. Безопасное получение реквизитов через ВЫРАЗИТЬ и ССЫЛКА

Если в результирующей выборке действительно требуется вывести реквизит связанного документа (например, Ответственный), стандартами разработки 1С предписано использовать явное ограничение типов. Разберем по шагам построение такого запроса:


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

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

Способ 4. Оптимизация для высоконагруженных систем через ОБЪЕДИНИТЬ ВСЕ

Когда таблица содержит миллионы строк, даже одиночное левое соединение со всеми таблицами составного типа может создавать критическую нагрузку. Наиболее производительным решением является разделение запроса на части по каждому конкретному типу документа с последующим объединением результатов:


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

ОБЪЕДИНИТЬ ВСЕ

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

В этом сценарии оптимизатор СУБД обращается напрямую к индексам конкретных таблиц без лишних соединений с остальными документами, входящими в составной тип.

Рекомендации по поддержанию чистоты данных

Чтобы избежать сложностей в запросах, правильнее всего контролировать данные на уровне записи объекта. Рекомендуется в модуле объекта при событии ПередЗаписью проверять составной реквизит: если значение не заполнено (равно пустой ссылке любого вида), явно присваивать ему значение Неопределено. Это позволит стандартизировать хранение информации и избежать появления «фантомных» пустых ссылок в базе данных.

← На главную