При разработке отчетов на Системе Компоновки Данных (СКД) часто возникает задача, где результат текущей строки напрямую зависит от вычисленного значения в предыдущей строке. Стандартный механизм вычисляемых полей СКД имеет существенное ограничение: поле не может ссылаться само на себя. Если вы попробуете написать в выражении поля Значение что-то вроде ВычислитьВыражение("Значение", , , "Предыдущая"), система выдаст ошибку, так как поле еще не рассчитано. Разберем детально, как обойти это ограничение и реализовать сложную логику расчетов.
Проанализируем простую задачу: нам нужно рассчитать остаток, где текущее значение — это предыдущее значение минус расход текущей строки. Математически формула выглядит так: Остаток_N = Остаток_0 - (Расход_1 + Расход_2 + ... + Расход_N).
Вместо того чтобы пытаться обратиться к предыдущей строке вычисляемого поля, мы можем использовать агрегатную функцию Сумма с указанием области расчета. Разберем по шагам, как это настроить в вычисляемых полях:
ОстатокРасчетный.НомерПоПорядкуВГруппировке().ВычислитьВыражение для получения накопленного итога.Пример выражения:
Выбор
Когда НомерПоПорядкуВГруппировке() = 1 Тогда
&НачальныйОстаток - Сумма(Расход)
Иначе
&НачальныйОстаток - ВычислитьВыражение("Сумма(Расход)", , "Группировка", "Первая", "Текущая")
Конец
В данном примере мы превращаем рекурсивную зависимость в линейную сумму всех предыдущих значений. Это самый производительный метод, так как он работает внутри механизмов компоновки без обращения к внешнему коду.
Рассмотрим более сложную ситуацию, описанную в сообщениях форума: когда логика становится взаимозависимой. Например, значение в колонке А зависит от рассчитанного значения в колонке В, а В в свою очередь зависит от предыдущего В и текущего А. В таких случаях СКД бессильна в плане вычисляемых полей.
Проанализируем стратегию «подготовки данных»: самый надежный способ — это рассчитать всю сложную математику в коде на языке 1С, сформировать ТаблицаЗначений и передать ее в СКД как Внешний набор данных. Рассмотрим, как это реализовать:
Сначала подготовим данные в процедуре формирования отчета:
// В модуле объекта отчета или обработке
Запрос = Новый Запрос("ВЫБРАТЬ ... ИЗ ..."); // Получаем первичные данные
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ТаблицаДанных.Колонки.Добавить("ПолеВ");
ТаблицаДанных.Колонки.Добавить("ПолеА");
ПредыдущееВ = КонстантаНачало;
Для Каждого Строка Из ТаблицаДанных Цикл
// Реализуем сложную логику
Если Строка.Расход < 1000 Тогда
Строка.ПолеА = ПредыдущееВ;
Иначе
Строка.ПолеА = Строка.Расход * 1.2;
КонецЕсли;
// Расчет ПолеВ на основе ПолеА и предыдущего значения
Строка.ПолеВ = ПредыдущееВ - Строка.ПолеА;
// Сохраняем для следующей итерации
ПредыдущееВ = Строка.ПолеВ;
КонецЦикла;
После этого передаем ТаблицаДанных в процессор компоновки. Этот метод гарантирует 100% точность расчетов, легко отлаживается и не зависит от ограничений языка выражений СКД.
Если ваша платформа обновлена до версии 8.3.20 и выше, мы можем перенести расчеты на уровень СУБД, используя оконные функции. Это позволяет получить значение предыдущей строки (LAG) или накопленную сумму прямо в тексте запроса СКД.
Посмотрим на пример текста запроса:
ВЫБРАТЬ
Т.Период,
Т.СуммаОборот,
СУММА(Т.СуммаОборот) ОБЪЕДИНИТЬ (УПОРЯДОЧИТЬ ПО Т.Период) КАК НакопленныйИтог
ИЗ
РегистрНакопления.Продажи.Обороты(&Начало, &Конец, День, ) КАК Т
Использование конструкции ОБЪЕДИНИТЬ (в контексте агрегатов это синтаксис 1С для оконных функций) позволяет вычислить итог динамически. Это значительно быстрее, чем использование вычисляемых полей СКД, так как данные приходят в отчет уже в готовом для отображения виде.
Выясним, как ускорить расчет, если необходимо остаться в рамках вычисляемых полей, но математическое развертывание невозможно. Обычный вызов функции общего модуля для каждой строки — это медленно. Оптимальный вариант — использование функции ВычислитьВыражениеСГруппировкойМассив.
Рассмотрим алгоритм действий:
Пример кода в общем модуле:
Функция РассчитатьРекурсию(МассивДанныхБ, СтартоваяКонстанта) Экспорт
Результаты = Новый Массив;
ТекущееЗначение = СтартоваяКонстанта;
Для Каждого ЗначениеБ Из МассивДанныхБ Цикл
ТекущееЗначение = ТекущееЗначение - ЗначениеБ;
Результаты.Добавить(ТекущееЗначение);
КонецЦикла;
Возврат Результаты;
КонецФункции
В СКД вычисляемое поле будет выглядеть так:
// В ресурсах или вычисляемых полях для конкретной группировки
МойОбщийМодуль.РассчитатьРекурсию(
ВычислитьВыражениеСГруппировкойМассив("Сумма(Б)", "Период"),
&Константа
)
Важный нюанс: при таком подходе функция вернет массив. Чтобы вывести конкретное значение для текущей строки, вам потребуется дополнительная логика по извлечению элемента массива по индексу (номеру строки).
Проанализируем, какой метод выбрать в зависимости от условий задачи:
Таким образом, несмотря на то что СКД напрямую не поддерживает обращение вычисляемого поля к самому себе, у разработчика есть богатый арсенал инструментов для реализации рекурсивных алгоритмов. Выбор метода зависит от сложности задачи и версии используемой платформы.