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