Как правильно рассчитать нарастающий итог в СКД при наличии нескольких группировок?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Расчет нарастающего итога в Системе компоновки данных (СКД) в 1С — одна из классических задач, с которой рано или поздно сталкивается каждый разработчик. На первый взгляд, встроенная функция ВычислитьВыражение предоставляет для этого все необходимые инструменты. Однако при попытке применить ее в отчетах со сложной иерархией, вложенными или параллельными (соседними) группировками разработчики часто сталкиваются с неожиданным поведением системы: итог либо рассчитывается локально внутри каждой группы, либо сбрасывается, либо выводится общей суммой для всех строк.

В этой статье мы подробно проанализируем, почему возникают сложности при расчете нарастающего итога по нескольким группировкам, выясним разницу между вычисляемыми полями и ресурсами, а также разберем по шагам практические способы решения этой задачи, включая обходные пути (костыли) и альтернативные методы расчета на уровне запроса.

Выясняем причину: почему функция ВычислитьВыражение ведет себя не так, как ожидается?

Рассмотрим стандартный синтаксис функции, которая используется для расчета нарастающего итога в СКД:

ВычислитьВыражение(Выражение, Группировка, ТипРасчета, Начало, Конец)

Для классического нарастающего итога по текущей группировке выражение выглядит следующим образом:

ВычислитьВыражение("Сумма(Остаток)", , , "Первая", "Текущая")

Пока мы работаем в рамках одной линейной группировки, эта формула отрабатывает безупречно. СКД последовательно обходит строки текущего уровня, складывая значение поля Остаток от первой строки до текущей. Но как только мы пытаемся указать конкретную группировку во втором параметре (например, "ГрупШапка" или предопределенное имя "ОбщийИтог"), поведение кардинально меняется.

Проанализируем, почему выражение ВычислитьВыражение("Сумма(Остаток)", "ОбщийИтог", , "Первая", "Текущая") выводит всю итоговую сумму во всех строках отчета:

  1. Смена контекста расчета. Указывая во втором параметре конкретное имя группировки верхнего уровня (или "ОбщийИтог"), мы заставляем СКД рассчитывать выражение в контексте записей этого верхнего уровня.
  2. Единственность строки в контексте родителя. Для детальной (дочерней) строки на уровне общего итога или родительской группы существует всего одна агрегированная строка. СКД переходит на этот уровень, видит там одну-единственную свернутую строку общего итога. Соответственно, для нее "Первая" запись совпадает с "Текущей".
  3. Результат. Вместо последовательного сложения строк дочерней группы СКД мгновенно возвращает уже готовый, полностью рассчитанный итог по всей родительской группе для каждой строки.

Ситуация усложняется, когда в отчете присутствуют две параллельные (соседние) группировки, созданные, к примеру, для применения разных макетов оформления. Поскольку СКД обрабатывает структуру данных как иерархическое дерево, ветки параллельных группировок рассчитываются изолированно. Передать сквозной нарастающий итог из конца одной дочерней ветки в начало соседней стандартным линейным обходом невозможно.

Разбираем фундаментальное различие: Вычисляемое поле или Ресурс?

Часто ступор у разработчиков возникает из-за непонимания архитектурных слоев СКД, а именно — разницы между вкладками Вычисляемые поля и Ресурсы. Выясним, в чем принципиальная разница их использования при расчете итогов:

Правильная методика настройки:

  1. Переходим на вкладку Вычисляемые поля, добавляем новую строку, задаем ей имя (например, НарастающийОстаток), но поле "Выражение" оставляем абсолютно пустым.
  2. Переходим на вкладку Ресурсы.
  3. Добавляем наше поле НарастающийОстаток в список ресурсов.
  4. В колонке "Выражение" пишем формулу расчета: ВычислитьВыражение("Сумма(Остаток)", , , "Первая", "Текущая").
  5. В колонке "Рассчитывать по" четко указываем те группировки, в которых нам требуется получить этот нарастающий итог.

Способ 1. Обходное решение (костыль) в ресурсах СКД

Если структура отчета требует наличия двух дочерних параллельных группировок внутри одной родительской (например, когда для каждой дочерней группы назначен свой уникальный макет оформления), стандартный расчет нарастающего итога прерывается при переходе от первой группы ко второй. Чтобы обойти это ограничение, мы можем применить математический трюк в описании ресурсов.

Допустим, у нас есть начальный остаток и обороты (остаток), и нам нужно вывести конечный баланс. Мы можем настроить формулу ресурса отдельно для каждого уровня группировки:

Для первой группировки в ресурсах прописываем выражение:

Максимум(НачальныйОстаток)

Для второй группировки (где должен продолжиться нарастающий итог с учетом накопленных данных) задаем выражение ресурса, которое прибавляет к начальному значению нарастающую сумму текущего периода:


Максимум(НачальныйОстаток) + ВычислитьВыражение("Сумма(Остаток)", , , "Первая", "Текущая")

Этот подход позволяет "подхватить" базовое значение на старте второй группировки и корректно продолжить нарастающий итог без разрывов в расчете.

Способ 2. Расчет нарастающего итога на уровне запроса

Если логика распределения данных по группировкам слишком сложна, а формулы в ресурсах СКД становятся громоздкими и начинают тормозить отчет на больших базах данных, самым надежным решением будет перенос расчета нарастающего итога непосредственно в схему запроса. В этом случае СКД получит уже готовое линейное поле, которое останется просто вывести в отчет.

Рассмотрим, как реализовать расчет нарастающего итога в запросе с помощью левого соединения таблицы самой с собой по условию последовательности дат (или других упорядоченных полей). Посмотрим на пример шаблона такого запроса:


ВЫБРАТЬ
    ПродажиОбороты1.Период КАК Период,
    ПродажиОбороты1.Номенклатура КАК Номенклатура,
    ПродажиОбороты1.СуммаОборот КАК СуммаОборот,
    СУММА(ЕСТЬNULL(ПродажиОбороты2.СуммаОборот, 0)) КАК НарастающийИтог
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты1
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК ПродажиОбороты2
        ПО ПродажиОбороты1.Номенклатура = ПродажиОбороты2.Номенклатура
        И ПродажиОбороты1.Период >= ПродажиОбороты2.Период
СГРУППИРОВАТЬ ПО
    ПродажиОбороты1.Период,
    ПродажиОбороты1.Номенклатура,
    ПродажиОбороты1.СуммаОборот

Проанализируем этот запрос по шагам:

  1. Мы берем таблицу оборотов ПродажиОбороты1 и соединяем ее с точно такой же таблицей ПродажиОбороты2.
  2. Условие связи накладывается по аналитике (Номенклатура) и по временному признаку: ПродажиОбороты1.Период >= ПродажиОбороты2.Период. Это означает, что для каждой строки из первой таблицы будут подобраны все строки из второй таблицы, которые были зафиксированы ранее или в этот же день.
  3. С помощью конструкции СУММА(ЕСТЬNULL(ПродажиОбороты2.СуммаОборот, 0)) мы складываем все эти более ранние обороты, получая чистый нарастающий итог.
  4. В СКД мы выносим полученное поле НарастающийИтог в ресурсы и рассчитываем его с использованием агрегатной функции Максимум(НарастающийИтог).

Важное примечание: Использование левого соединения таблиц по условию "больше или равно" (неравенство) может приводить к снижению производительности базы данных при обработке сотен тысяч строк. Если вы работаете с большими объемами информации, рекомендуется предварительно индексировать временные таблицы по связующим полям или ограничивать выборку жесткими фильтрами.

Способ 3. Объединение группировок и гибкая настройка макетов

Зачастую разработчики создают несколько дочерних параллельных группировок только ради того, чтобы применить к ним разные макеты оформления (как описывает автор в Сообщении 5). Однако такое разделение ломает сквозной расчет нарастающего итога в СКД.

Рассмотрим альтернативный вариант решения этой проблемы:

  1. Объединение структур. Схлопываем параллельные группировки обратно в одну общую линейную группировку на вкладке «Настройки». Это моментально решает проблему нарастающего итога, так как расчет функции ВычислитьВыражение("Сумма(Остаток)", , , "Первая", "Текущая") снова начинает идти по единой непрерывной цепочке записей.
  2. Реализация оформления через Условное оформление. Если нужно визуально выделить разные типы строк (например, разные номенклатурные группы или склады), мы можем воспользоваться механизмом Условного оформления СКД. Мы задаем правила выделения цветом, шрифтом или границами на основании значений реквизитов группировки.
  3. Использование параметров макета. В СКД можно создать индивидуальный макет оформления для одной и той же группировки, добавив в схему параметры макета. При этом внешний вид ячеек будет динамически меняться в зависимости от условий, прописанных в выражении макета. Это позволяет сохранить целостность данных для расчета итогов и одновременно получить требуемый внешний вид отчета.

Способ 4. Использование функции ВычислитьВыражениеСГруппировкойМассив

Для самых неординарных задач, когда нарастающий итог нужно передать со сложных нижних уровней на верхние, стандартная агрегации суммы может не подойти. В таких случаях программисты используют продвинутые функции СКД, например, ВычислитьВыражениеСГруппировкойМассив. Данная функция собирает результаты вычислений по указанной группировке в массив, который затем можно обработать другими функциями (например, сложить или найти максимум).

Посмотрим на пример синтаксиса использования этой функции в ресурсах:

Сумма(ВычислитьВыражениеСГруппировкойМассив("Сумма(Остаток)", "ИмяГруппировки"))

Этот метод позволяет "вытянуть" итоги нижестоящих уровней и правильно сложить их на верхних уровнях, даже если стандартное суммирование ресурсов работает некорректно из-за сложной иерархии структуры отчета.

Мы подробно разобрали особенности расчета нарастающих итогов в Системе компоновки данных 1С. Понимание разницы между контекстами вычисления, грамотное разделение вычисляемых полей и ресурсов, а также умение вовремя перенести тяжелые расчеты в плоскость SQL-запроса помогут вам создавать быстрые, точные и профессиональные отчеты любой сложности.

← На главную