При работе со стандартными запросами в платформе «1С:Предприятие» разработчики регулярно сталкиваются с необходимостью агрегирования данных. Для большинства стандартных задач предусмотрены встроенные агрегатные функции, такие как СУММА, МИНИМУМ, МАКСИМУМ, СРЕДНЕЕ и КОЛИЧЕСТВО. Однако стандартной агрегатной функции для расчета произведения значений строк (аналога условного "ПРОИЗВЕДЕНИЕ") в языке запросов нет.
Такая задача часто возникает при расчете сложных финансовых коэффициентов, дисконтировании, расчете вероятностей совместных событий, вычислении многомерных физических величин или обработке специфических аналитических показателей в регистрах сведений. В этой статье мы подробно разберем несколько эффективных способов решения этой задачи: от современного математического метода в запросе до классической постобработки данных и механизмов СКД.
Долгое время реализация произведения непосредственно внутри тела запроса была невозможна без использования прямого SQL. Однако начиная с версии платформы 8.3.20.1549, разработчики добавили в стандартный язык запросов 1С полноценную поддержку математических функций, таких как Log() (натуральный логарифм) и Exp() (экспонента).
Благодаря этому обновлению мы можем применить классическое математическое тождество, которое позволяет свести операцию умножения к сложению. Вспомним свойство логарифма произведения:
log(a * b) = log(a) + log(b)
Следовательно, произведение элементов последовательности можно представить как экспоненту от суммы их натуральных логарифмов:
a * b = exp(log(a) + log(b))
Проанализируем базовый пример сборки такого запроса для вычисления произведения ресурса Показатель из регистра сведений:
ВЫБРАТЬ
EXP(СУММА(LOG(РегистрПоказатели.Показатель))) КАК Произведение
ИЗ
РегистрСведений.Показатели КАК РегистрПоказатели
Несмотря на простоту и изящество формулы, при реальном внедрении этого кода в рабочую конфигурацию мы неизбежно столкнемся с математическими ограничениями логарифмической функции. Рассмотрим эти подводные камни по шагам и найдем способы их обхода.
Log() и Exp() работают с типом данных двойной точности с плавающей точкой. В результате вычислений может возникнуть погрешность округления (например, вместо точного числа 8 запрос вернет значение 7.9999999999). Чтобы гарантировать точность, результат вычисления экспоненты обязательно нужно оборачивать в функцию округления ОКР().Разберем готовый, безопасный и универсальный запрос, учитывающий все перечисленные математические особенности:
ВЫБРАТЬ
// Вычисляем количество отрицательных значений для определения знака
СУММА(ВЫБОР КОГДА ИсходныеДанные.Показатель < 0 ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) КАК КолОтрицательных,
// Проверяем, есть ли среди строк хотя бы один ноль
МИНИМУМ(ВЫБОР КОГДА ИсходныеДанные.Показатель = 0 ТОГДА 0 ИНАЧЕ 1 КОНЕЦ) КАК ЕстьНоль,
// Суммируем логарифмы модулей значений
СУММА(LOG(ВЫБОР
КОГДА ИсходныеДанные.Показатель < 0 ТОГДА -ИсходныеДанные.Показатель
ИНАЧЕ ИсходныеДанные.Показатель
КОНЕЦ)) КАК СуммаЛогарифмов
ПОМЕСТИТЬ ВТ_Подготовка
ИЗ
РегистрСведений.Показатели КАК ИсходныеДанные
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВЫБОР
// Если в выборке был хотя бы один ноль, произведение равно нулю
КОГДА ВТ_Подготовка.ЕстьНоль = 0 ТОГДА 0
ИНАЧЕ ОКР(
EXP(ВТ_Подготовка.СуммаЛогарифмов) *
ВЫБОР
// Если количество отрицательных чисел нечетное, итоговый знак минус, иначе плюс
КОГДА ВТ_Подготовка.КолОтрицательных % 2 = 1 ТОГДА -1
ИНАЧЕ 1
КОНЕЦ,
4
)
КОНЕЦ КАК ИтоговоеПроизведение
ИЗ
ВТ_Подготовка КАК ВТ_Подготовка
Этот сложный, но абсолютно надежный запрос позволяет получить точное произведение полей непосредственно на уровне СУБД, минимизируя передачу данных на сторону сервера приложений 1С.
Если вы работаете на версиях платформы ниже 8.3.20 или хотите полностью исключить влияние погрешностей вычислений функций с плавающей точкой, наиболее надежным и простым решением будет классический обход выборки во встроенном языке.
В этом случае запрос используется исключительно для отбора и группировки нужных данных, а само перемножение выполняется в цикле. Этот метод гарантирует стопроцентную точность, так как 1С работает с десятичными числами фиксированной точности высокой разрядности.
Рассмотрим пример реализации этого подхода:
// Инициализируем запрос для получения сырых данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РегистрПоказатели.Показатель КАК Значение
|ИЗ
| РегистрСведений.Показатели КАК РегистрПоказатели
|ГДЕ
| РегистрПоказатели.Период МЕЖДУ &ДатаНачала И &ДатаКонца";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекущаяДата()));
Запрос.УстановитьПараметр("ДатаКонца", КонецДня(ТекущаяДата()));
Выборка = Запрос.Выполнить().Выбрать();
// Начальное значение произведения должно быть равно единице
ИтоговоеПроизведение = 1;
ЕстьСтроки = Ложь;
Пока Выборка.Следующий() Цикл
ЕстьСтроки = Истина;
// Производим последовательное перемножение
ИтоговоеПроизведение = ИтоговоеПроизведение * Выборка.Значение;
// Оптимизация: если в процессе перемножения мы получили 0,
// дальнейший обход выборки бессмысленен
Если ИтоговоеПроизведение = 0 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
// Корректируем результат, если выборка оказалась пустой
Если Не ЕстьСтроки Тогда
ИтоговоеПроизведение = 0;
КонецЕсли;
Сообщить("Произведение всех полей ресурса составляет: " + ИтоговоеПроизведение);
Преимущества метода: простота чтения кода, отсутствие сложных математических преобразований, корректная работа на абсолютно любых версиях платформы 1С:Предприятие (включая 8.2 и 8.3 старых релизов) и полное сохранение разрядности чисел без искажений.
Система компоновки данных предоставляет разработчику гибкие инструменты агрегирования через "Выражения ресурсов". Если перед вами стоит задача вывести произведение записей в отчете, мы можем решить её двумя способами внутри СКД.
Вариант А: Использование функций компоновки (для новых платформ)
В выражении ресурса на вкладке «Ресурсы» СКД мы можем написать аналогичное логарифмическое выражение:
Exp(Сумма(Log(Показатель)))
Сама система скомпонует итоговый запрос к СУБД с использованием встроенных функций логарифмирования.
Вариант Б: Связка СКД и экспортной функции общего модуля (универсальный вариант)
Этот вариант подходит для любой версии платформы и позволяет вычислять произведение программным путем при расчете итогов в группировках СКД. Разберем по шагам его настройку:
МатематикаСервер). В свойствах модуля обязательно взведем флаги Сервер и Вызов сервера.
Функция ВычислитьПроизведениеМассива(МассивЗначений) Экспорт
Если МассивЗначений = Неопределено Или МассивЗначений.Количество() = 0 Тогда
Возврат 0;
КонецЕсли;
Результат = 1;
Для Каждого ТекущееЗначение Из МассивЗначений Цикл
// Пропускаем NULL-значения, которые могут прийти из левых соединений запроса
Если ТекущееЗначение = Неопределено Или ТекущееЗначение = NULL Тогда
Продолжить;
КонецЕсли;
Результат = Результат * ТекущееЗначение;
КонецЦикла;
Возврат Результат;
КонецФункции
Показатель в список ресурсов.ТаблицаЗначений() или Массив():
МатематикаСервер.ВычислитьПроизведениеМассива(Массив(Показатель))
При формировании отчета СКД автоматически соберет все детальные записи по каждой группировке в массив и передаст их в нашу функцию, которая быстро и безошибочно вернет итоговое произведение для строк отчета любой сложности.
Мы проанализировали три принципиально разных подхода к расчету произведения числовых полей. Подведем итог и определим, когда какой метод целесообразно использовать: