Как оптимизировать условие виртуальной таблицы с обращением через точку к реквизитам измерений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Причины падения производительности при разыменовании полей

Проанализируем механизм работы платформы и СУБД при вычислении итогов виртуальной таблицы:

  1. Неявные левые соединения (LEFT JOIN): Конструкция вида Договор.Владелец.Реквизит заставляет платформу формировать соединение таблиц справочников непосредственно внутри подзапроса, вычисляющего срез итогов регистра.
  2. Потеря индексного поиска (Index Seek): Таблицы итогов регистров оптимизированы для поиска по кластерному индексу, включающему измерения и период. Наличие соединений внутри подзапроса виртуальной таблицы мешает оптимизатору СУБД эффективно использовать индекс, что приводит к полному сканированию таблицы (Index Scan или Table Scan). При большом объеме данных в регистре это вызывает колоссальные задержки.
  3. Проблемы трехзначной логики SQL: При неявном левом соединении для записей с пустыми ссылками или отсутствующими владельцами возвращается значение NULL. Выражение НЕ NULL в SQL оценивается как UNKNOWN (ложь), из-за чего такие строки будут неожиданно исключены из выборки.

Оптимальное решение: использование проиндексированной временной таблицы

Стандарты разработки 1С предписывают исключать обращение к реквизитам через точку в параметрах виртуальных таблиц. Наиболее эффективный подход заключается в предварительной выборке разрешенных ссылок во временную таблицу с ее последующей индексацией.

Разберем по шагам построение такого запроса:

  1. В первом запросе пакета выбираем только те договоры (или владельцев договоров), которые соответствуют условию, и помещаем их во временную таблицу.
  2. Обязательно добавляем инструкцию ИНДЕКСИРОВАТЬ ПО для поля связи, чтобы СУБД могла соединять таблицы максимально быстро.
  3. В основном запросе в параметрах виртуальной таблицы регистра используем оператор В (ВЫБРАТЬ ...) из созданной временной таблицы.

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


ВЫБРАТЬ
    Договоры.Ссылка КАК Договор
ПОМЕСТИТЬ ВТ_ОтфильтрованныеДоговоры
ИЗ
    Справочник.ДоговорыКонтрагентов КАК Договоры
ГДЕ
    ЕСТЬNULL(Договоры.Владелец.НеПоказыватьВоВзаиморасчетах, ЛОЖЬ) = ЛОЖЬ
ИНДЕКСИРОВАТЬ ПО
    Договор
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Взаиморасчеты.Договор КАК Договор,
    Взаиморасчеты.СуммаОстаток КАК СуммаОстаток
ИЗ
    РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(
        &Период,
        Договор В (ВЫБРАТЬ ВТ.Договор ИЗ ВТ_ОтфильтрованныеДоговоры КАК ВТ)
    ) КАК Взаиморасчеты

Если контрагентов в базе существенно меньше, чем договоров, мы можем отбирать во временную таблицу непосредственно владельцев (контрагентов). Однако в этом случае в регистре должно присутствовать отдельное измерение Контрагент, по которому и будет выполняться фильтрация в виртуальной таблице.

Особенности работы с булевыми полями и значениями NULL

Обратим внимание на то, как обрабатывается условие отрицания флага. Прямое сравнение Поле = ЛОЖЬ или НЕ Поле может отсечь строки, где значение не определено или равно NULL (например, поврежденные ссылки или договоры без владельца).

Чтобы исключить потерю данных и избежать ошибок выполнения, рекомендуется явно обрабатывать возможные неопределенные значения с помощью функции ЕСТЬNULL:


ГДЕ
    ЕСТЬNULL(Договоры.Владелец.НеПоказыватьВоВзаиморасчетах, ЛОЖЬ) = ЛОЖЬ

Такая конструкция гарантирует, что если у договора отсутствует владелец или реквизит не заполнен, строка будет интерпретирована как разрешенная для взаиморасчетов.

Почему не стоит переносить условие в секцию ГДЕ или передавать массив параметров

Рассмотрим альтернативные варианты, которые часто предлагают на практике, и выясним их недостатки:

  1. Перенос условия в секцию ГДЕ основного запроса: Если убрать фильтр из параметров виртуальной таблицы и поместить его в общее ГДЕ, виртуальная таблица сначала рассчитает итоги по абсолютно всем договорам базы данных, и только затем отбросит лишние строки. Это оправдано только в редких случаях, когда общих записей итогов в регистре крайне мало, но на крупных рабочих базах создает избыточную нагрузку на дисковую подсистему и память сервера.
  2. Передача массива через параметр запроса (&МассивДоговоров): Выборка списка ссылок в коде встроенного языка и передача массива через параметр УстановитьПараметр() допустима только при гарантированно малом объеме данных (до нескольких сотен элементов). Если список превышает 1000–2000 записей, СУБД (MS SQL Server, PostgreSQL) не сможет построить эффективный план выполнения из-за отсутствия статистики распределения, а также возрастает риск превышения лимитов на размер текста запроса и число параметров.

Таким образом, использование проиндексированной временной таблицы в рамках одного пакетного запроса является наиболее универсальным, производительным и безопасным решением для оптимизации условий виртуальных таблиц.

← На главную