При разработке сложных финансовых отчетов, таких как расчет доходности ценных бумаг или динамика индексов, программисты 1С часто сталкиваются с необходимостью вычислить произведение нарастающим итогом. В отличие от стандартной суммы нарастающим итогом, которая легко реализуется встроенными средствами СКД, прямого агрегата "Произведение" в системе компоновки данных не предусмотрено. В этой статье мы подробно разберем, как обойти это ограничение, проанализируем возможные ошибки и выберем оптимальный метод реализации.
Этот способ является классическим и наиболее гибким для версий платформы ниже 8.3.20. Идея заключается в том, чтобы собрать все значения числового поля в массив от первой строки до текущей и передать этот массив в общую функцию для перемножения. Рассмотрим этот процесс по шагам.
Во-первых, нам необходимо создать общий модуль (например, с именем РасчетыСКД), установить у него флаги Сервер и Внешнее соединение, а также разрешить вызов функций из СКД. В модуле создадим экспортную функцию:
Функция ПеремножитьМассивЗначений(МассивЧисел) Экспорт
Результат = 1;
Для Каждого Значение Из МассивЧисел Цикл
Если ТипЗнч(Значение) = Тип("Число") Тогда
Результат = Результат * Значение;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Во-вторых, настроим схему компоновки данных. Предположим, у нас есть поле ФакторРоста. Для получения произведения нарастающим итогом в Вычисляемых полях или сразу в Ресурсах мы будем использовать мощную функцию СКД ВычислитьВыражение.
Разберем синтаксис выражения для ресурса:
РасчетыСКД.ПеремножитьМассивЗначений(ВычислитьВыражение("Массив(ФакторРоста)", , , "Первая", "Текущая"))
Проанализируем, что здесь происходит:
Массив(ФакторРоста) агрегирует значения поля в коллекцию."Первая" и "Текущая" указывают системе, что для каждой строки отчета нужно брать данные начиная с самой первой строки группировки и заканчивая текущей позицией.ПеремножитьМассивЗначений, которая возвращает итоговый коэффициент.При использовании данного метода важно понимать механизмы управления памятью. Как было отмечено в обсуждении, отчет может "умереть" или работать крайне медленно на больших выборках. Выясним причину: при использовании границ "Первая" и "Текущая" для 1000-й строки система создает массив из 1000 элементов, для 1001-й — из 1001 элемента и так далее. Это приводит к квадратичному росту потребления памяти.
Если в вашем отчете десятки тысяч строк, рекомендуется использовать Метод 3 (Объектный набор данных), чтобы избежать перегрузки ресурсов сервера.
Начиная с версии 8.3.20, в языке запросов 1С появились математические функции LOG() (натуральный логарифм) и EXP() (экспонента). Это позволяет нам использовать свойство логарифмов: сумма логарифмов чисел равна логарифму их произведения.
Математически это выглядит так: Число1 * Число2 = Exp(Log(Число1) + Log(Число2)).
Рассмотрим, как реализовать это прямо в тексте запроса СКД для получения накопленного итога:
ВЫБРАТЬ
Т.Дата,
Т.ФакторРоста,
EXP(СУММА(LOG(Т2.ФакторРоста))) КАК НакопленноеПроизведение
ИЗ
ТаблицаДанных КАК Т
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаДанных КАК Т2
ПО Т2.Дата <= Т.Дата
СГРУППИРОВАТЬ ПО
Т.Дата,
Т.ФакторРоста
Важное ограничение: Логарифм определен только для положительных чисел. Если в ваших данных встречаются нули или отрицательные значения (например, отрицательная доходность, выраженная не коэффициентом 0.9, а числом -0.1), этот метод выдаст ошибку. Перед использованием LOG() убедитесь, что данные проходят предварительную валидацию.
Если вы хотите добиться максимальной производительности и стабильности, особенно в сложных финансовых отчетах, разберем вариант с предварительным расчетом в коде на языке 1С. Вместо того чтобы заставлять СКД вычислять тяжелые массивы в каждой строке, мы подготовим таблицу значений заранее.
Рассмотрим алгоритм реализации:
ТаблицаЗначений.ПроизведениеНарастающимИтогом.
ТекущееПроизведение = 1;
Для Каждого СтрокаДанных Из ТЗДанные Цикл
ТекущееПроизведение = ТекущееПроизведение * СтрокаДанных.ФакторРоста;
СтрокаДанных.ПроизведениеНарастающимИтогом = ТекущееПроизведение;
КонецЦикла;
После этого мы передаем готовую таблицу в СКД через ВнешниеНаборыДанных. Это гарантирует, что отчет откроется мгновенно, так как СКД останется только вывести уже посчитанные цифры без выполнения сложных рекурсивных вычислений.
Часто возникает вопрос: как заставить работать ВычислитьВыражение, если в отчете нет иерархии группировок, а есть только детальные записи? В этом случае в настройках ресурса необходимо правильно указать контекст вычисления. Рассмотрим пример выражения для детальных записей:
ВычислитьВыражение("ВашОбщийМодуль.ФункцияРасчета(Массив(Поле))", , , "Первая", "Текущая")
Если вы используете конструкцию с группировками (например, по дням), полезно применять ВычислитьВыражениеСГруппировкойМассив. Это позволяет оперировать значениями, которые уже были агрегированы на уровне нижестоящих группировок, что значительно расширяет аналитические возможности вашего отчета.
Подведем итог: выбор метода зависит от версии вашей платформы и объема данных. Для небольших отчетов идеально подходит Метод 1 с экспортной функцией. Для современных систем и строгих математических расчетов лучше использовать Метод 2. В высоконагруженных системах с огромным количеством проводок правильным выбором будет Метод 3.