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