Разберем одну из классических и наиболее коварных проблем, с которой сталкиваются разработчики при проектировании сложных отчетов в Системе компоновки данных (СКД) 1С. Суть проблемы заключается в следующем: когда мы накладываем отбор на конкретную группировку (например, по определенному бизнес-событию или аналитике), строки в отчете визуально скрываются, но при этом общие итоги и ресурсы вышестоящих группировок продолжают рассчитываться по всем исходным данным, включая скрытые. Получается неприятная ситуация: отчет выглядит отфильтрованным, но итоговые суммы "не бьются" с детальными записями.
В этой статье мы подробно проанализируем, почему так происходит, и рассмотрим три надежных способа решения этой проблемы: средствами функций ресурсов СКД, через вычисляемые поля и путем оптимизации исходного запроса.
Для начала разберем внутренний механизм работы компоновщика макета СКД. Процесс компоновки и вывода данных происходит в строго определенном порядке:
Поскольку расчет ресурсов (Шаг 3) происходит до фильтрации на уровне конкретных группировок (Шаг 4), скрытые локальным отбором строки все равно остаются в составе рассчитанных итогов. Чтобы обойти это поведение платформы, нам нужно явно указать СКД, как именно собирать итоги.
Это наиболее элегантный и гибкий способ решения проблемы непосредственно на уровне настроек схемы компоновки данных. Мы можем переопределить формулу расчета ресурса для верхних уровней группировок и общих итогов, заставив СКД агрегировать только те данные, которые соответствуют условию нашего отбора.
Предположим, у нас есть группировка Поле5 (например, Бизнес-событие или статья), внутри которой лежат документы детальных записей. Нам нужно суммировать Поле3 (Расход) только по тем группам Поле5, где хотя бы раз в детальных записях был зафиксирован доход (то есть Поле2 > 0).
Перейдем на вкладку "Ресурсы" нашей схемы компоновки данных и настроим расчет для ресурса следующим образом:
Поле5 оставляем стандартное выражение расчета:
Сумма(Поле3)
Поле5, Детальные записи
Поле1) и для Общего итога мы пропишем специальную формулу:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Сумма(Поле3)", "Поле5", , "Максимум(Поле2) > 0"))
Поле1, ОбщийИтог
Разберем детально, как работает эта формула:
Функция ВычислитьВыражениеСГруппировкойМассив на лету создает массив значений. Первым параметром мы передаем выражение, которое нужно рассчитать (сумму расходов). Вторым параметром указываем группировку, в разрезе которой нужно выполнить вычисление (наше Поле5). Четвертый параметр — это условие фильтрации. Мы говорим системе: "Выбери только те группы Поле5, для которых максимальное значение дохода Поле2 больше нуля". Полученный массив отфильтрованных сумм передается во внешнюю функцию Сумма(), которая складывает элементы массива и возвращает корректный общий итог.
Если вам кажется сложным использование тяжелых функций расчета ресурсов, или если логика отбора завязана на более простые условия, можно пойти по пути создания вспомогательного вычисляемого поля.
Проанализируем ситуацию: мы хотим обнулять показатели расхода там, где не было дохода, еще на этапе подготовки данных к расчету итогов.
Разберем этот вариант по шагам:
ПризнакНаличияДохода.Поле5:
ВычислитьВыражение("Максимум(Поле2)", "Поле5")
Поле3 мы напишем условие с использованием нашего нового вычисляемого поля:
Сумма(Выбор Когда ПризнакНаличияДохода > 0 Тогда Поле3 Иначе 0 Конец)
Поле1, ОбщийИтог
При таком подходе СКД сама выполнит логическое ветвление при расчете итогов. Для тех строк группировок, которые не содержат дохода, в ресурс пойдет значение 0, и общий итог сойдется с тем, что видит пользователь на экране.
Несмотря на то, что СКД предоставляет богатый инструментарий для расчетов "на лету", сложные вычисления функций ресурсов выполняются на сервере приложений 1С в оперативной памяти. Если ваш отчет обрабатывает десятки и сотни тысяч строк, использование функций вроде ВычислитьВыражениеСГруппировкойМассив может привести к значительному снижению производительности и повышенному потреблению памяти.
Наиболее профессиональным и правильным с точки зрения архитектуры решением является подготовка данных на уровне СУБД (в самом запросе схемы компоновки данных). Разберем, как это реализовать.
Мы можем заранее определить в запросе, какие группировки содержат доход, и пометить все связанные с ними строки специальным флагом. Посмотрим на пример построения такого запроса с использованием временных таблиц:
// ВЫБИРАЕМ ВСЕ ИСХОДНЫЕ ДАННЫЕ ВО ВРЕМЕННУЮ ТАБЛИЦУ
ВЫБРАТЬ
ИсходныеДанные.Поле1 КАК Поле1,
ИсходныеДанные.Поле2 КАК Поле2, // Доход
ИсходныеДанные.Поле3 КАК Поле3, // Расход
ИсходныеДанные.Поле5 КАК Поле5, // БС
ИсходныеДанные.Поле6 КАК Поле6 // Документ
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
&ТаблицаИсточника КАК ИсходныеДанные
;
////////////////////////////////////////////////////////////////////////////////
// ОПРЕДЕЛЯЕМ ГРУППЫ (БС), ГДЕ БЫЛ ДОХОД
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВТ_ИсходныеДанные.Поле5 КАК Поле5,
ИСТИНА КАК БС_ИмеетДоход
ПОМЕСТИТЬ ВТ_ГруппыСДоходом
ИЗ
ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
ГДЕ
ВТ_ИсходныеДанные.Поле2 > 0
;
////////////////////////////////////////////////////////////////////////////////
// СОЕДИНЯЕМ ИСХОДНЫЕ ДАННЫЕ С ПОЛУЧЕННЫМ ПРИЗНАКОМ
ВЫБРАТЬ
Данные.Поле1 КАК Поле1,
Данные.Поле2 КАК Поле2,
Данные.Поле3 КАК Поле3,
Данные.Поле5 КАК Поле5,
Данные.Поле6 КАК Поле6,
ЕСТЬNULL(Группы.БС_ИмеетДоход, ЛОЖЬ) КАК Поле4 // Наш признак-флаг
ИЗ
ВТ_ИсходныеДанные КАК Данные
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ГруппыСДоходом КАК Группы
ПО Данные.Поле5 = Группы.Поле5
Преимущества данного подхода:
Поле4 (БС имеет доход). Вы сможете использовать его в качестве стандартного общего отбора отчета.Сумма(Поле3)) рассчитаются абсолютно верно без дополнительных настроек на вкладке "Ресурсы".Поле4 в состав быстрых отборов на форму отчета, вы дадите пользователю возможность самостоятельно решать: видеть только доходные группы, только недоходные или вывести всё вместе одним кликом.