При разработке отчетов и обработок часто возникает задача получить срез последних данных по контрагенту сразу по нескольким ролям или видам ответственных лиц. Типичная ошибка при реализации такого алгоритма — создание множества отдельных пакетных запросов к виртуальной таблице СрезПоследних с последующей некорректной фильтрацией, из-за чего отчет выполняется медленно, а часть клиентов полностью исчезает из результата. Разберем детально, почему это происходит и как правильно построить оптимальный запрос.
Рассмотрим две ключевые проблемы, с которыми сталкиваются разработчики при построении подобных запросов:
ЛЕВОЕ СОЕДИНЕНИЕ и затем в секции ГДЕ указывает условие вида ГДЕ Таблица1.Включен И Таблица2.Включен, СУБД отсекает все строки, где хотя бы по одной таблице вернулся NULL. В итоге клиенты с неполным набором ролей полностью пропадают из итогового результата.РегистрСведений.Регистр2.СрезПоследних(&Период, Клиент В (ВЫБРАТЬ ...)) приводят к формированию неоптимального SQL-кода с предикатом IN (SELECT ...). СУБД не может эффективно использовать индексы, что вызывает полное сканирование таблиц и многократное замедление выполнения.СрезПоследних 6 раз подряд с наложением условий по каждой роли отдельно создает избыточную нагрузку на сервер баз данных.Выясним причину долгой работы: если требуется ограничить выборку среза только определенным пулом клиентов, необходимо сначала поместить их в отдельную временную таблицу и обязательно ее проиндексировать. Это позволит СУБД использовать соединение через индексный поиск.
Посмотрим на пример подготовки исходного списка задач и клиентов:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Задачи.Клиент КАК Клиент,
Задачи.ВидОтветственного КАК ВидОтветственного
ПОМЕСТИТЬ ВТ_КлиентыЗадачи
ИЗ
РегистрСведений.ЗадачиКлиентов КАК Задачи
ГДЕ
Задачи.Включен = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
Клиент
;
Вместо 6 отдельных обращений к регистру сведений получим все данные за один раз. Передадим список нужных ролей в параметрах виртуальной таблицы через оператор В, а отбор по актуальным клиентам выполним с помощью внутреннего соединения с подготовленной временной таблицей ВТ_КлиентыЗадачи.
ВЫБРАТЬ
Срез.Клиент КАК Клиент,
Срез.РольОтветственного КАК РольОтветственного,
Срез.Сотрудник КАК Сотрудник,
Срез.Актуально КАК Актуально
ПОМЕСТИТЬ ВТ_ОтветственныеСрез
ИЗ
ВТ_КлиентыЗадачи КАК Клиенты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица.СрезПоследних(
&Период,
РольОтветственного В (&СписокРолей)) КАК Срез
ПО Клиенты.Клиент = Срез.Клиент
ГДЕ
Срез.Актуально = ИСТИНА
ИНДЕКСИРОВАТЬ ПО
Клиент,
РольОтветственного
;
Проанализируем ситуацию с выводом данных в итоговые колонки (например, «Ответственный» и «Руководитель»). Чтобы не терять клиентов, у которых назначены не все ответственные, перенесем условия фильтрации и сопоставления непосредственно в условие связи ПО или применим агрегатные конструкции с оператором ВЫБОР КОГДА.
Рассмотрим пример итогового запроса с разворачиванием ролей в результирующие поля:
ВЫБРАТЬ
Задачи.Клиент КАК Клиент,
Задачи.ВидОтветственного КАК ВидОтветственного,
ВЫБОР
КОГДА Задачи.ВидОтветственного = &Вид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).
Если отчет разрабатывается на СКД, нет необходимости хардкодить распределение ролей по колонкам через множество соединений в тексте запроса. Оптимальным решением будет получение «плоской» таблицы:
ВЫБРАТЬ
Задачи.Клиент КАК Клиент,
Задачи.ВидОтветственного КАК ВидОтветственного,
Срез.РольОтветственного КАК РольОтветственного,
Срез.Сотрудник КАК Сотрудник
ИЗ
ВТ_КлиентыЗадачи КАК Задачи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОтветственныеСрез КАК Срез
ПО Задачи.Клиент = Срез.Клиент
Далее в настройках СКД достаточно выполнить следующие действия:
Клиент.РольОтветственного.Максимум(Сотрудник), что позволит СКД автоматически развернуть все роли в кросс-таблицу без усложнения SQL-запроса.