Как в СКД рассчитать значение на основе данных предыдущей строки?

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

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

Метод 1: Математическое развертывание рекурсии

Проанализируем простую задачу: нам нужно рассчитать остаток, где текущее значение — это предыдущее значение минус расход текущей строки. Математически формула выглядит так: Остаток_N = Остаток_0 - (Расход_1 + Расход_2 + ... + Расход_N).

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

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

Пример выражения:


Выбор 
    Когда НомерПоПорядкуВГруппировке() = 1 Тогда 
        &НачальныйОстаток - Сумма(Расход) 
    Иначе 
        &НачальныйОстаток - ВычислитьВыражение("Сумма(Расход)", , "Группировка", "Первая", "Текущая") 
Конец

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

Метод 2: Использование внешнего набора данных (ТЗ)

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

Проанализируем стратегию «подготовки данных»: самый надежный способ — это рассчитать всю сложную математику в коде на языке 1С, сформировать ТаблицаЗначений и передать ее в СКД как Внешний набор данных. Рассмотрим, как это реализовать:

Сначала подготовим данные в процедуре формирования отчета:


// В модуле объекта отчета или обработке
Запрос = Новый Запрос("ВЫБРАТЬ ... ИЗ ..."); // Получаем первичные данные
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
ТаблицаДанных.Колонки.Добавить("ПолеВ");
ТаблицаДанных.Колонки.Добавить("ПолеА");

ПредыдущееВ = КонстантаНачало;

Для Каждого Строка Из ТаблицаДанных Цикл
    // Реализуем сложную логику
    Если Строка.Расход < 1000 Тогда
        Строка.ПолеА = ПредыдущееВ;
    Иначе
        Строка.ПолеА = Строка.Расход * 1.2;
    КонецЕсли;

    // Расчет ПолеВ на основе ПолеА и предыдущего значения
    Строка.ПолеВ = ПредыдущееВ - Строка.ПолеА;
    
    // Сохраняем для следующей итерации
    ПредыдущееВ = Строка.ПолеВ;
КонецЦикла;

После этого передаем ТаблицаДанных в процессор компоновки. Этот метод гарантирует 100% точность расчетов, легко отлаживается и не зависит от ограничений языка выражений СКД.

Метод 3: Оконные функции в запросе (для платформы 8.3.20+)

Если ваша платформа обновлена до версии 8.3.20 и выше, мы можем перенести расчеты на уровень СУБД, используя оконные функции. Это позволяет получить значение предыдущей строки (LAG) или накопленную сумму прямо в тексте запроса СКД.

Посмотрим на пример текста запроса:


ВЫБРАТЬ
    Т.Период,
    Т.СуммаОборот,
    СУММА(Т.СуммаОборот) ОБЪЕДИНИТЬ (УПОРЯДОЧИТЬ ПО Т.Период) КАК НакопленныйИтог
ИЗ
    РегистрНакопления.Продажи.Обороты(&Начало, &Конец, День, ) КАК Т

Использование конструкции ОБЪЕДИНИТЬ (в контексте агрегатов это синтаксис 1С для оконных функций) позволяет вычислить итог динамически. Это значительно быстрее, чем использование вычисляемых полей СКД, так как данные приходят в отчет уже в готовом для отображения виде.

Метод 4: Функции общего модуля с передачей массива

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

Рассмотрим алгоритм действий:

  1. Создаем в общем модуле (с флагом «Сервер», «Внешнее соединение») функцию, которая принимает массив данных.
  2. В функции обходим массив циклом, вычисляем рекурсивные значения и возвращаем массив результатов.
  3. В СКД вызываем эту функцию, передавая ей все необходимые значения группировки сразу.

Пример кода в общем модуле:


Функция РассчитатьРекурсию(МассивДанныхБ, СтартоваяКонстанта) Экспорт
    Результаты = Новый Массив;
    ТекущееЗначение = СтартоваяКонстанта;
    
    Для Каждого ЗначениеБ Из МассивДанныхБ Цикл
        ТекущееЗначение = ТекущееЗначение - ЗначениеБ;
        Результаты.Добавить(ТекущееЗначение);
    КонецЦикла;
    
    Возврат Результаты;
КонецФункции

В СКД вычисляемое поле будет выглядеть так:


// В ресурсах или вычисляемых полях для конкретной группировки
МойОбщийМодуль.РассчитатьРекурсию(
    ВычислитьВыражениеСГруппировкойМассив("Сумма(Б)", "Период"), 
    &Константа
)

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

Сравнительный анализ методов

Проанализируем, какой метод выбрать в зависимости от условий задачи:

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

← На главную