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

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

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

Анализ типовых ошибок: почему исчезают клиенты и падает скорость

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

  1. Неявное преобразование ЛЕВОГО СОЕДИНЕНИЯ во внутреннее. Если у клиента заполнены не все 6 видов ответственных (например, назначен только один сотрудник), а разработчик связывает таблицы через ЛЕВОЕ СОЕДИНЕНИЕ и затем в секции ГДЕ указывает условие вида ГДЕ Таблица1.Включен И Таблица2.Включен, СУБД отсекает все строки, где хотя бы по одной таблице вернулся NULL. В итоге клиенты с неполным набором ролей полностью пропадают из итогового результата.
  2. Подзапросы в параметрах виртуальной таблицы. Конструкции вида РегистрСведений.Регистр2.СрезПоследних(&Период, Клиент В (ВЫБРАТЬ ...)) приводят к формированию неоптимального SQL-кода с предикатом IN (SELECT ...). СУБД не может эффективно использовать индексы, что вызывает полное сканирование таблиц и многократное замедление выполнения.
  3. Множественные обращения к виртуальной таблице. Повторный вызов СрезПоследних 6 раз подряд с наложением условий по каждой роли отдельно создает избыточную нагрузку на сервер баз данных.

Шаг 1. Формирование базового списка клиентов с индексацией

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

Посмотрим на пример подготовки исходного списка задач и клиентов:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Задачи.Клиент КАК Клиент,
    Задачи.ВидОтветственного КАК ВидОтветственного
ПОМЕСТИТЬ ВТ_КлиентыЗадачи
ИЗ
    РегистрСведений.ЗадачиКлиентов КАК Задачи
ГДЕ
    Задачи.Включен = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
    Клиент
;

Шаг 2. Однократное получение среза последних по всем требуемым ролям

Вместо 6 отдельных обращений к регистру сведений получим все данные за один раз. Передадим список нужных ролей в параметрах виртуальной таблицы через оператор В, а отбор по актуальным клиентам выполним с помощью внутреннего соединения с подготовленной временной таблицей ВТ_КлиентыЗадачи.


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

Шаг 3. Корректное соединение данных и распределение по колонкам

Проанализируем ситуацию с выводом данных в итоговые колонки (например, «Ответственный» и «Руководитель»). Чтобы не терять клиентов, у которых назначены не все ответственные, перенесем условия фильтрации и сопоставления непосредственно в условие связи ПО или применим агрегатные конструкции с оператором ВЫБОР КОГДА.

Рассмотрим пример итогового запроса с разворачиванием ролей в результирующие поля:


ВЫБРАТЬ
    Задачи.Клиент КАК Клиент,
    Задачи.ВидОтветственного КАК ВидОтветственного,
    ВЫБОР
        КОГДА Задачи.ВидОтветственного = &Вид1 ТОГДА Отв1.Сотрудник
        КОГДА Задачи.ВидОтветственного = &Вид2 ТОГДА Отв2.Сотрудник
        ИНАЧЕ ОтвDefault.Сотрудник
    КОНЕЦ КАК Ответственный,
    ВЫБОР
        КОГДА Задачи.ВидОтветственного = &Вид1 ТОГДА Рук1.Сотрудник
        КОГДА Задачи.ВидОтветственного = &Вид2 ТОГДА Рук2.Сотрудник
        ИНАЧЕ РукDefault.Сотрудник
    КОНЕЦ КАК Руководитель
ИЗ
    ВТ_КлиентыЗадачи КАК Задачи
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК Отв1
        ПО Задачи.Клиент = Отв1.Клиент
        И Отв1.РольОтветственного = &Роль_Исполнитель1
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК Отв2
        ПО Задачи.Клиент = Отв2.Клиент
        И Отв2.РольОтветственного = &Роль_Исполнитель2
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК ОтвDefault
        ПО Задачи.Клиент = ОтвDefault.Клиент
        И ОтвDefault.РольОтветственного = &Роль_ИсполнительОсновной
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК Рук1
        ПО Задачи.Клиент = Рук1.Клиент
        И Рук1.РольОтветственного = &Роль_Руководитель1
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК Рук2
        ПО Задачи.Клиент = Рук2.Клиент
        И Рук2.РольОтветственного = &Роль_Руководитель2
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК РукDefault
        ПО Задачи.Клиент = РукDefault.Клиент
        И РукDefault.РольОтветственного = &Роль_РуководительОсновной
;

Важный момент: обратите внимание, что условие по конкретной роли (И Отв1.РольОтветственного = &Роль_Исполнитель1) находится внутри секции ПО, а не в ГДЕ. Благодаря этому левое соединение сохраняет строку клиента даже в том случае, если данный вид ответственного для него не назначен (в поле вернется значение NULL).

Альтернативное решение для Системы компоновки данных (СКД)

Если отчет разрабатывается на СКД, нет необходимости хардкодить распределение ролей по колонкам через множество соединений в тексте запроса. Оптимальным решением будет получение «плоской» таблицы:


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

Далее в настройках СКД достаточно выполнить следующие действия:

  1. Перейти на вкладку Настройки и добавить группировку строк по полю Клиент.
  2. Внутрь строки добавить группировку колонок по полю РольОтветственного.
  3. В выбранных полях указать ресурс Максимум(Сотрудник), что позволит СКД автоматически развернуть все роли в кросс-таблицу без усложнения SQL-запроса.
← На главную