Как получить реквизит из составного типа Регистратор в запросе 1С без потери производительности?

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

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

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

Анализ причин проблемы

Рассмотрим, с какими сложностями сталкивается разработчик при неоптимальной работе с составным типом:

  1. Неявные соединения в СУБД: когда платформа 1С видит обращение к реквизиту через составной ссылочный тип (Регистратор.Подразделение), транслятор запросов строит соединение с каждой физической таблицей документов из состава типа. Если в конфигурации более 100 видов документов, генерируется более 100 LEFT JOIN, что перегружает оптимизатор СУБД и приводит к тайм-аутам.
  2. Сравнение с NULL: проверка вида КОГДА НЕ Поле = NULL в языке запросов 1С является некорректной, так как результатом сравнения с NULL через операторы равенства или неравенства всегда является ЛОЖЬ. Необходимо использовать конструкцию ЕСТЬ NULL или НЕ Поле ЕСТЬ NULL.
  3. Промежуточная выгрузка в память: выгрузка выборки в ТаблицаЗначений на сторону сервера с последующей повторной передачей во временную таблицу расходует оперативную память и процессорное время на сериализацию данных. Оптимальнее выполнять все действия в рамках одного пакетного запроса.
  4. Некорректный синтаксис приведения типов: обращение к метаданным напрямую вида Документ.ИмяДокумента.Реквизит в тексте запроса недопустимо — необходимо явно приводить тип поля ссылки через ВЫРАЗИТЬ(Поле КАК Документ.ИмяДокумента).Реквизит.

Способ 1. Использование конструкции ВЫБОР КОГДА и ВЫРАЗИТЬ в пакетном запросе

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

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


ВЫБРАТЬ
    ХозрасчетныйОборотыДтКт.Регистратор КАК Регистратор,
    ХозрасчетныйОборотыДтКт.Организация КАК Организация,
    ХозрасчетныйОборотыДтКт.СуммаОборот КАК СуммаОборот,
    NULL КАК Подразделение
ПОМЕСТИТЬ ВТ_ДанныеРегистра
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(
        НАЧАЛОПЕРИОДА(&НачалоПериода, ДЕНЬ),
        КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ),
        Регистратор,
        ,
        ,
        СчетКт В ИЕРАРХИИ (&СчетБУДоходыКт),
        ,
    ) КАК ХозрасчетныйОборотыДтКт
ИНДЕКСИРОВАТЬ ПО
    Регистратор
;

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

Способ 2. Паттерн «Временная таблица уникальных ссылок + ОБЪЕДИНИТЬ ВСЕ»

Проанализируем альтернативный и наиболее производительный подход согласно стандартам разработки 1С. Когда в выборке участвуют сотни тысяч записей, конструкция ВЫБОР КОГДА ... ВЫРАЗИТЬ может уступать по скорости явному соединению по кластерному индексу Ссылка.

Рассмотрим структуру такого запроса:

  1. Выбираем движения регистра во временную таблицу ВТ_Обороты.
  2. Выбираем только неповторяющиеся ссылки регистраторов во временную таблицу ВТ_Регистраторы с обязательной индексацией по полю Регистратор.
  3. Объединяем точечные выборки через ВНУТРЕННЕЕ СОЕДИНЕНИЕ с таблицами конкретных документов.
  4. Соединяем полученный результат с исходными оборотами.

ВЫБРАТЬ
    ХозрасчетныйОборотыДтКт.Регистратор КАК Регистратор,
    ХозрасчетныйОборотыДтКт.Организация КАК Организация,
    ХозрасчетныйОборотыДтКт.СуммаОборот КАК СуммаОборот
ПОМЕСТИТЬ ВТ_Обороты
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(
        &НачалоПериода,
        &КонецПериода,
        Регистратор,
        ,
        ,
        СчетКт В ИЕРАРХИИ (&СчетБУДоходыКт),
        ,
    ) КАК ХозрасчетныйОборотыДтКт
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВТ.Регистратор КАК Регистратор
ПОМЕСТИТЬ ВТ_Регистраторы
ИЗ
    ВТ_Обороты КАК ВТ
ИНДЕКСИРОВАТЬ ПО
    Регистратор
;

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

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

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

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

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

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

Способ 3. Программная генерация текста запроса по метаданным

Если конфигурация дорабатывается, состав документов меняется или имена реквизитов подразделения различаются (например, Подразделение, ПодразделениеОрганизации, Подразделение_САГ), конструировать текст запроса вручную не всегда удобно. В этом случае сформируем текст условий автоматически во встроенном языке через коллекцию Метаданные.Документы один раз перед выполнением запроса.

Разберем пример генерации условий:


ТекстУсловий = "";

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

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

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

Рекомендации по оптимизации

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