Почему долго формируется отчет в 1С при соединении регистра накопления с документом?

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

При разработке отчетов в системе компоновки данных (СКД) разработчики нередко сталкиваются с ситуацией, когда простой по структуре запрос выполняется десятки минут или полностью подвешивает базу данных. Типичный сценарий такой проблемы — необходимость вывести данные из регистра накопления и дополнить их реквизитами из табличной части документа-регистратора.

Разберем детально, почему запрос может выполняться медленно, какие грубые ошибки допускаются при связывании таблиц и как правильно оптимизировать запрос в платформе 1С:Предприятие.

Анализ исходной ошибки в тексте запроса

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


ВЫБРАТЬ
    ДилЗаказТабличнаяЧасть1.Срок КАК Срок,
    Потолки.Код,
    Потолки.Заказчик,
    Потолки.Материал,
    Потолки.Площадь
ИЗ
    РегистрНакопления.Потолки КАК Потолки
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДилЗаказ.ТабличнаяЧасть1 КАК ДилЗаказТабличнаяЧасть1
        ПО Потолки.Регистратор.Ссылка = ДилЗаказТабличнаяЧасть1.Срок
ГДЕ
    ДилЗаказТабличнаяЧасть1.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
СГРУППИРОВАТЬ ПО
    ДилЗаказТабличнаяЧасть1.Срок,
    Потолки.Код,
    Потолки.Заказчик,
    Потолки.Материал,
    Потолки.Площадь

Проанализируем этот код и выделим ключевые узкие места:

  1. Несоответствие типов в условии соединения: В конструкции ПО Потолки.Регистратор.Ссылка = ДилЗаказТабличнаяЧасть1.Срок выполняется попытка соединить ссылку на документ с реквизитом даты или числа. На уровне СУБД такое сравнение либо возвращает пустой результат, либо заставляет платформу выполнять тяжелое неявное приведение типов на каждой строке.
  2. Обращение через точку к составному типу Регистратор: Поле Регистратор в регистрах накопления имеет составной ссылочный тип (все документы, являющиеся регистраторами данного регистра). Конструкция Потолки.Регистратор.Ссылка приводит к тому, что платформа генерирует неявные левые соединения со всеми физическими таблицами документов, входящих в состав регистраторов. Это многократно усложняет план запроса СУБД. Кроме того, само разыменование .Ссылка от ссылочного поля бессмысленно, так как Регистратор уже содержит ссылку.
  3. Декартово произведение (размножение строк): Если в документе содержится несколько строк, и регистр делает движения по каждой строке, связь таблицы регистра только с документом без учета номера строки приводит к соединению каждой записи регистра со всеми строками табличной части. Если в документе 100 строк и 100 движений по регистру, результатом соединения станут 10 000 строк.
  4. Игнорирование кластерного индекса регистра: Условие на период наложено в секции ГДЕ через дату документа (ДилЗаказТабличнаяЧасть1.Ссылка.Дата). Физическая таблица регистра накопления проиндексирована по полям Период и Регистратор. Отсутствие фильтра по полю Потолки.Период заставляет СУБД сканировать всю таблицу регистра целиком (Table Scan) вместо эффективного поиска по диапазону индекса.
  5. Тяжелая группировка без агрегатов: Конструкция СГРУППИРОВАТЬ ПО используется для устранения дублей, возникших из-за декартова произведения. Выгрузка огромного промежуточного объема некорректных записей во временную базу TempDB для последующей сортировки и дедупликации окончательно блокирует работу базы.

Способ 1. Исправление соединения и явное приведение типов

Рассмотрим, как правильно составить прямой запрос, если реквизит Срок требуется получить из табличной части документа.

Разберем по шагам необходимые исправления:

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

Посмотрим на пример оптимизированного запроса:


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

Способ 2. Архитектурная доработка регистра (рекомендуемый подход)

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

  1. Добавим реквизит Срок (тип Дата) в метаданные регистра накопления Потолки.
  2. В модуле объекта документа ДилЗаказ при формировании движений по регистру заполним значение реквизита: Движение.Срок = СтрокаТабличнойЧасти.Срок;.
  3. Выполним перепроведение документов или обновление движений за рабочий период.

После изменения структуры метаданных необходимость в каких-либо соединениях полностью отпадает. Запрос становится предельно простым и быстрым:


ВЫБРАТЬ
    Потолки.Срок КАК Срок,
    Потолки.Код КАК Код,
    Потолки.Заказчик КАК Заказчик,
    Потолки.Материал КАК Материал,
    Потолки.Площадь КАК Площадь
ИЗ
    РегистрНакопления.Потолки КАК Потолки
ГДЕ
    Потолки.Период МЕЖДУ &НачалоПериода И &ОкончаниеПериода

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

Способ 3. Оптимизация через связи наборов данных в СКД

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

  1. Создадим НаборДанных1 (Запрос) для регистра накопления:
    
    ВЫБРАТЬ
        Потолки.Регистратор КАК Регистратор,
        Потолки.НомерСтроки КАК НомерСтроки,
        Потолки.Код КАК Код,
        Потолки.Заказчик КАК Заказчик,
        Потолки.Материал КАК Материал,
        Потолки.Площадь КАК Площадь
    ИЗ
        РегистрНакопления.Потолки КАК Потолки
    ГДЕ
        Потолки.Период МЕЖДУ &НачалоПериода И &ОкончаниеПериода
    
  2. Создадим НаборДанных2 (Запрос) для табличной части документа:
    
    ВЫБРАТЬ
        ДилЗаказ.Ссылка КАК Ссылка,
        ДилЗаказ.НомерСтроки КАК НомерСтроки,
        ДилЗаказ.Срок КАК Срок
    ИЗ
        Документ.ДилЗаказ.ТабличнаяЧасть1 КАК ДилЗаказ
    ГДЕ
        ДилЗаказ.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
    
  3. На вкладке Связи наборов данных укажем:
    • Источник связи: НаборДанных1;
    • Приемник связи: НаборДанных2;
    • Условие связи: Регистратор = Ссылка И НомерСтроки = НомерСтроки.

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

← На главную