При разработке отчетов в Системе компоновки данных (СКД) разработчики нередко сталкиваются с ситуацией, когда сложный запрос с пакетом временных таблиц корректно работает в обычной консоли запросов, но при выполнении в СКД выдает неверные агрегированные данные. Особенно ярко это проявляется при попытке реализовать двухэтапную агрегацию, например, посчитать количество уникальных позиций номенклатуры в разрезе партнеров, а затем просуммировать полученные значения по менеджерам или периодам.
Разберем, почему возникает такое расхождение и какими способами можно получить правильные результаты.
Рассмотрим типовой сценарий. В первой временной таблице формируются данные с группировкой по партнерам и рассчитывается количество различных позиций:
ВЫБРАТЬ
РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер КАК Партнер,
РеализацияТоваровУслугТовары.Период КАК Период,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК СуммаФейсов,
РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер КАК Менеджер
ПОМЕСТИТЬ ВТ_Подготовка
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
СГРУППИРОВАТЬ ПО
РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер,
РеализацияТоваровУслугТовары.Период,
РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер
;
Во второй таблице выполняется суммирование поля СуммаФейсов без разреза по партнерам:
ВЫБРАТЬ
ВТ_Подготовка.Период КАК Период,
ВТ_Подготовка.Менеджер КАК Менеджер,
СУММА(ВТ_Подготовка.СуммаФейсов) КАК СуммаФейсов
ПОМЕСТИТЬ ВТ_Итог
ИЗ
ВТ_Подготовка КАК ВТ_Подготовка
СГРУППИРОВАТЬ ПО
ВТ_Подготовка.Период,
ВТ_Подготовка.Менеджер
;
В консоли запросов при наличии двух партнеров с 1 и 2 уникальными позициями итоговый запрос выдаст сумму 3. Однако внутри СКД отчет возвращает значение 2.
Выясним причину такого поведения. Генератор макета СКД перед отправкой запроса в СУБД анализирует текущие настройки отчета (выбранные поля, группировки, отборы). Если поле Партнер не выводится в финальном отчете и не участвует в группировках настроек компоновки, интеллектуальный оптимизатор СКД исключает его из секций ВЫБРАТЬ и СГРУППИРОВАТЬ ПО всех промежуточных запросов. В результате первая таблица группируется напрямую по полям Период и Менеджер, а функция КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...) рассчитывается сразу по всему объему данных, полностью ломая логику промежуточного суммирования.
Проанализируем доступные способы решения этой задачи.
Если в схеме компоновки включено автозаполнение, СКД анализирует только поля итогового набора данных. Чтобы запретить оптимизатору исключать поле из промежуточных временных таблиц, необходимо сделать его обязательным для вычисления.
Партнер через все последующие временные таблицы вплоть до финальной выборки набора данных.Партнер в списке доступных полей набора данных.Свойство Обязательное сообщает генератору макета, что данное поле должно присутствовать во всех промежуточных выборках и группировках СУБД независимо от того, выбрал ли пользователь это поле в настройках структуры отчета.
Более гибким и правильным с точки зрения архитектуры СКД решением является отказ от попыток выполнить двойную агрегацию внутри запроса. Вместо этого запрос оставляет детальные данные, а подсчет сумм по уникальным значениям переносится в механизм ресурсов компоновки данных.
Партнер):
ВЫБРАТЬ
РеализацияТоваровУслугТовары.Период КАК Период,
РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер КАК Менеджер,
РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер КАК Партнер,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК СуммаФейсов
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
СГРУППИРОВАТЬ ПО
РеализацияТоваровУслугТовары.Период,
РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер,
РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер
СуммаФейсов.
Сумма(ВычислитьВыражениеСГруппировкойМассив("СуммаФейсов", "Партнер"))
Период, Менеджер) и Общий итог.При таком подходе СКД сама выполнит расчет количества уникальных позиций на уровне группировки по партнерам, а на вышестоящих уровнях сложит полученные числа, не искажая результат.
Если требуется строго зафиксировать текст запроса в исходном виде без вмешательства оптимизатора СКД, можно отключить механизм автозаполнения полей.
{ВЫБРАТЬ ...} и {ГДЕ ...}.Когда автозаполнение отключено, компоновщик данных выполняет все временные таблицы ровно в том виде, как они написаны в тексте запроса, сохраняя все промежуточные группировки.
В сложных аналитических отчетах надежным способом фиксации промежуточных расчетов является предварительная подготовка данных на встроенном языке перед запуском СКД.
Разберем реализацию в модуле отчета через событие ПриКомпоновкеРезультата:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
// 1. Создаем менеджер временных таблиц и выполняем расчет
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| РеализацияТоваровУслугТовары.Период КАК Период,
| РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер КАК Менеджер,
| РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер КАК Партнер,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК СуммаФейсов
|ПОМЕСТИТЬ ВТ_Подготовка
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
|СГРУППИРОВАТЬ ПО
| РеализацияТоваровУслугТовары.Период,
| РеализацияТоваровУслугТовары.ЗаказКлиента.Соглашение.Менеджер,
| РеализацияТоваровУслугТовары.ЗаказКлиента.Партнер
|;
|
|ВЫБРАТЬ
| ВТ_Подготовка.Период КАК Период,
| ВТ_Подготовка.Менеджер КАК Менеджер,
| СУММА(ВТ_Подготовка.СуммаФейсов) КАК СуммаФейсов
|ПОМЕСТИТЬ ВТ_ИтоговыеДанные
|ИЗ
| ВТ_Подготовка КАК ВТ_Подготовка
|СГРУППИРОВАТЬ ПО
| ВТ_Подготовка.Период,
| ВТ_Подготовка.Менеджер";
Запрос.Выполнить();
// 2. Передаем менеджер временных таблиц в компоновщик макета
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(
СхемаКомпоновкиДанных,
Настройки,
ДанныеРасшифровки,
,
,
Ложь,
МенеджерВТ
);
// 3. Выполняем компоновку и выводим результат
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
МенеджерВТ.Закрыть();
КонецПроцедуры
В этом случае в самом наборе данных СКД достаточно написать простой запрос к уже готовой таблице ВТ_ИтоговыеДанные. Оптимизатор СКД не сможет повлиять на логику предварительного расчета, так как временные таблицы формируются стандартным объектом Запрос.