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