При построении отчетов в системе компоновки данных (СКД) разработчики часто сталкиваются с необходимостью выполнять математические операции между динамическими колонками кросс-таблицы. Классический пример — расчет относительных показателей продаж, например, когда требуется разделить сумму продаж июня на сумму продаж марта или сопоставить текущий месяц с предыдущим. Выполнение подобных расчетов стандартными средствами агрегации вызывает трудности, поскольку контекст расчета каждой ячейки ограничен текущей группировкой.
В этой статье мы подробно разберем, почему стандартные вычисляемые поля не подходят для решения этой задачи, как правильно настроить формулы в ресурсах с помощью функции ВычислитьВыражение, а также рассмотрим альтернативные способы реализации — от доработки запроса до динамического сопоставления с помощью параметров.
---
Частой ошибкой начинающих разработчиков является попытка прописать формулу деления на вкладке «Вычисляемые поля». Настроив там выражение вида СуммаИюнь / СуммаМарт, в результате в отчете они видят лишь нули или пустые значения.
Выясним причину такого поведения системы. Вычисляемые поля в СКД рассчитываются для каждой детальной записи (строки исходного запроса) отдельно, еще до того, как платформа выполнит группировку данных и расчет итогов. Поскольку одна конкретная строка из базы данных не может одновременно относиться и к марту, и к июню, условие отбора по месяцу внутри одной строки вернет ложь. В итоге при делении система пытается поделить ноль на ноль или получает NULL.
Важное правило разработки в СКД: любые математические операции над агрегированными показателями (суммами группировок, средними значениями и т. д.) должны настраиваться строго на вкладке «Ресурсы». Только на этом этапе СКД оперирует уже сгруппированными и просуммированными данными.
---
Самым гибким и правильным способом является использование встроенного языка выражений СКД на вкладке «Ресурсы». Для доступа к данным других колонок (группировок) используется функция ВычислитьВыражение.
Рассмотрим пример, когда нам нужно разделить сумму июня на сумму марта. Для этого на вкладке «Ресурсы» для нашего рассчитываемого поля (например, «ИндексДинамики») мы прописываем следующее выражение:
ВычислитьВыражение("Сумма(Выбор когда Месяц = ""июнь"" Тогда Количество Иначе 0 Конец)", "Месяц")
/
ВычислитьВыражение("Сумма(Выбор когда Месяц = ""март"" Тогда Количество Иначе 0 Конец)", "Месяц")
Проанализируем, как работает эта конструкция:
ВычислитьВыражение принимает первым параметром строку с выражением, которое нужно рассчитать (в нашем случае это сумма количества с условием выбора конкретного месяца).Обратите внимание: если формула рассчитывается внутри конкретной ячейки колонки (например, мы выводим этот ресурс прямо в колонках месяцев), контекст расчета может ограничивать видимость других месяцев. Чтобы формула корректно отрабатывала в любой ячейке строки, третьим параметром функции ВычислитьВыражение рекомендуется передавать область расчета "ОбщийИтог" или указывать родительскую группировку строк (например, «Организация»):
ВычислитьВыражение("Сумма(Выбор когда Месяц = ""июнь"" Тогда Количество Иначе 0 Конец)", "Месяц", "ОбщийИтог")
---
Если ваша задача шире и вам нужно делить не конкретно «июнь на март», а динамически сопоставлять каждый текущий месяц с предыдущим (например, февраль с январем, март с февралем и т.д.), жесткое кодирование имен месяцев не подойдет. Пользователь может построить отчет за другой год или за несколько лет сразу.
Для решения этой задачи в функции ВычислитьВыражение используются параметры смещения («Предыдущая», «Текущая»). Рассмотрим готовое выражение для ресурса, которое делит сумму текущего периода на предыдущий с защитой от деления на ноль:
Сумма(Количество)
/
ВыбратьКогда(
ВычислитьВыражение("Сумма(Количество)", "ПериодМесяц", , "Предыдущая", "Предыдущая") = 0,
1,
ВычислитьВыражение("Сумма(Количество)", "ПериодМесяц", , "Предыдущая", "Предыдущая")
)
Разберем параметры функции в этой формуле:
"Сумма(Количество)" — показатель, который мы хотим получить из предыдущей колонки."ПериодМесяц" — имя группировки колонок, по которой происходит смещение."Предыдущая", "Предыдущая") указывают системе, что нужно взять данные ровно на один шаг назад относительно текущей рассчитываемой колонки.ВыбратьКогда(Условие, Значение1, Значение2) страхует отчет от критической ошибки деления на ноль в случае, если за предыдущий месяц не было продаж.---
Что делать, если в колонках кросс-таблицы выводятся не месяцы, а элементы справочника (например, Номенклатурные группы, Склады или Проекты), и пользователю нужно сравнить два произвольных элемента?
Жесткое кодирование названий элементов в формуле ресурса — плохая практика, так как при переименовании элементов в базе данных отчет перестанет работать. В такой ситуации правильным решением будет использование параметров СКД.
Выполним настройку по шагам:
&ЭлементСравнения1 и &ЭлементСравнения2 с типом ссылки на ваш справочник (например, СправочникСсылка.НоменклатурныеГруппы). Снимите для них флаг «Ограничение доступности», чтобы пользователь мог выбирать их на форме отчета.
Сумма(Выбор Когда НоменклатурнаяГруппа = &ЭлементСравнения1 Тогда Количество Иначе 0 Конец)
/
ВыбратьКогда(
Сумма(Выбор Когда НоменклатурнаяГруппа = &ЭлементСравнения2 Тогда Количество Иначе 0 Конец) = 0,
1,
Сумма(Выбор Когда НоменклатурнаяГруппа = &ЭлементСравнения2 Тогда Количество Иначе 0 Конец)
)
Этот подход делает отчет полностью динамическим и интерактивным. Пользователь сам определяет, какие именно склады или группы товаров он хочет сопоставить в данный момент.
---
Если структура колонок в вашем отчете жестко регламентирована и не будет меняться динамически (например, всегда выводится ровно 12 месяцев строго текущего года), то расчет можно перенести на уровень SQL-запроса. Это упростит схему компоновки данных и ускорит формирование отчета.
Посмотрим на пример построения такого запроса:
ВЫБРАТЬ
Продажи.Организация КАК Организация,
СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 1 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ) КАК СуммаЯнварь,
СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 2 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ) КАК СуммаФевраль,
СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 3 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ) КАК СуммаМарт,
СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 6 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ) КАК СуммаИюнь,
// Прямой расчет коэффициента на уровне СУБД
ВЫБОР
КОГДА СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 3 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ) = 0
ТОГДА 0
ИНАЧЕ СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 6 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ)
/ СУММА(ВЫБОР КОГДА ПРОДОЛЖИТЕЛЬНОСТЬ.ПериодМесяц = 3 ТОГДА Продажи.СуммаОборот ИНАЧЕ 0 КОНЕЦ)
КОНЕЦ КАК ОтношениеИюньКМарту
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Организация
Преимущества метода: высокая скорость работы СУБД, отсутствие сложных формул в настройках СКД, простота отладки запроса в консоли.
Недостатки метода: полная потеря гибкости. Если пользователю потребуется вывести данные в разрезе дней, недель или добавить сравнение по годам, структуру запроса придется полностью переписывать.
---
Если ваша задача деления одного месяца на другой сводится к расчету долей (например, какую долю занимает конкретный месяц в годовом итоге или какой процент от продаж базового месяца составляют текущие продажи), в СКД предусмотрен мощный встроенный механизм, не требующий написания кода.
Выполним простую настройку:
+ слева от него.% в группе — доля текущего значения от общего итога родительской группировки.% в строке или серии — отношение значения ячейки к общему итогу по строке кросс-таблицы (идеально подходит для анализа сезонности по месяцам).% общего итога — процент от абсолютного итога всего отчета.% в строке или серии в «Выбранные поля» нашей таблицы. СКД самостоятельно рассчитает все соотношения без риска получить деление на ноль.---
Выбор конкретного метода зависит от архитектуры вашего отчета:
ВычислитьВыражение.