При разработке отчетов в Системе компоновки данных (СКД) на платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью расчета сложных, нелинейных итогов. Типичный пример такой задачи — вывод информации о штатном расписании, окладах или ставках сотрудников. Из-за особенностей построения запросов (например, при соединении таблиц «один ко многим», когда к данным о сотруднике присоединяются детальные записи по начислениям, проектам или отработанным дням) строки в выборке могут дублироваться на уровне базы данных.
Если для ресурса КоличествоСтавок применить простую агрегатную функцию Сумма, СКД послушно сложит ставку сотрудника столько раз, сколько детальных строк по нему вывелось в отчет, что приведет к значительному завышению результата. Чтобы решить эту проблему на уровне детальных записей по сотруднику, разработчики часто используют функцию Максимум. На уровне конкретного сотрудника это дает абсолютно верный результат. Однако при расчете общих итогов по подразделению или по организации в целом стандартная функция Максимум выберет лишь наибольшую ставку среди всех сотрудников (например, покажет 1.0 вместо реальной суммы всех ставок сотрудников подразделения, равной 5.5). Выясним причину этого поведения и разберем по шагам, как настроить расчет суммы локальных максимумов с помощью механизма двойного агрегирования.
Для реализации корректного расчета нам понадобится настроить вкладку «Ресурсы» в схеме компоновки данных. Платформа 1С позволяет добавлять одно и то же поле в список ресурсов несколько раз, задавая индивидуальные правила расчета для каждого уровня группировки. Проанализируем, как настроить этот механизм.
Рассмотрим по шагам настройку ресурсов:
КоличествоСтавок в список ресурсов первый раз. Это значение будет рассчитываться для детальной группировки по сотруднику. В поле «Выражение» укажем: Максимум(КоличествоСтавок). В колонке «Рассчитывать по...» выберем группировку Сотрудник.
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(КоличествоСтавок)", "Сотрудник"))
В колонке «Рассчитывать по...» для этой второй строки ресурсов укажем группировки Подразделение и ОбщийИтог (или оставим это поле пустым, чтобы расчет применялся ко всем остальным группировкам по умолчанию, которые не настроены явно в других строках).
Давайте подробно разберем, как именно СКД обрабатывает написанное нами выражение. Функция ВычислитьВыражениеСГруппировкойМассив выполняет обход детальных записей в контексте текущей вышестоящей группировки (например, внутри конкретного подразделения). Для каждого уникального значения группировки Сотрудник внутри этого подразделения она вычисляет локальный максимум по формуле "Максимум(КоличествоСтавок)". Полученные промежуточные значения складываются во временный массив в оперативной памяти.
Затем внешняя функция Сумма производит сложение всех элементов этого полученного массива. Таким образом, мы получаем математически верную сумму ставок уникальных сотрудников, полностью избегая искажения итогов из-за дублирования строк в исходной выборке.
Хотя механизм двойного агрегирования чрезвычайно мощен, при его практическом использовании разработчики часто совершают типичные ошибки. Проанализируем основные подводные камни:
КоличествоСтавок не присутствует в изначальном запросе, а создано на вкладке «Вычисляемые поля», прямая передача его имени в функцию ВычислитьВыражениеСГруппировкойМассив может вернуть пустой результат или вызвать критическую ошибку СКД. Чтобы решить эту проблему, внутри строкового параметра функции необходимо передать полную формулу расчета этого вычисляемого поля. Например: Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(Оклад / ТарифнаяСтавка)", "Сотрудник")).ВычислитьВыражениеСГруппировкойМассив может значительно замедлить построение отчета и увеличить нагрузку на сервер.В крупных информационных базах или при высоких требованиях к быстродействию лучшим решением будет перенос логики агрегирования непосредственно в текст запроса. Посмотрим на пример, как можно оптимизировать получение данных и отказаться от сложных вычислений в ресурсах СКД.
Для этого мы можем сначала сгруппировать данные по сотрудникам во временной таблице, избавившись от дублирования записей, а затем соединить результат с остальными аналитическими таблицами:
ВЫБРАТЬ
СведенияОСтавках.Сотрудник КАК Сотрудник,
МАКСИМУМ(СведенияОСтавках.КоличествоСтавок) КАК КоличествоСтавок
ПОМЕСТИТЬ ВТ_СтавкиСотрудников
ИЗ
РегистрСведений.СведенияОСтавках КАК СведенияОСтавках
СГРУППИРОВАТЬ ПО
СведенияОСтавках.Сотрудник;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_СтавкиСотрудников.Сотрудник КАК Сотрудник,
ВТ_СтавкиСотрудников.КоличествоСтавок КАК КоличествоСтавок,
Продажи.Подразделение КАК Подразделение,
Продажи.СуммаОбороты КАК СуммаОбороты
ИЗ
ВТ_СтавкиСотрудников КАК ВТ_СтавкиСотрудников
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты КАК Продажи
ПО ВТ_СтавкиСотрудников.Сотрудник = Продажи.Сотрудник
При таком подходе поле КоличествоСтавок попадает в СКД уже очищенным от дублей на уровне СУБД. Нам больше не нужно прибегать к сложным функциям компоновщика — достаточно перетащить это поле в ресурсы схемы компоновки данных и назначить ему стандартную функцию Сумма(КоличествоСтавок) для всех уровней группировки. Это значительно ускорит выполнение отчета и сэкономит оперативную память сервера приложений.