При разработке аналитических отчетов часто возникает необходимость рассчитать процент или сумму нарастающим итогом в пределах определенной группировки, а также выполнить АВС-классификацию. Разберем по шагам, как решить эту задачу средствами Системы компоновки данных (СКД), используя встроенные функции языка выражений и программные методы.
В современных версиях платформы 1С для получения процентов и классификации нарастающим итогом писать сложный код в общих модулях не обязательно. Все вычисления можно выполнить встроенным языком выражений СКД на вкладках «Вычисляемые поля» и «Ресурсы».
ВычислитьВыражение, где указываются границы расчета — от первой записи группировки до текущей. Формула ресурса будет выглядеть так:
ВычислитьВыражение("Сумма(СуммаОборот)", , , "Первая", "Текущая")ВычислитьВыражение("Сумма(СуммаОборот)", , , "Первая", "Текущая") / ВычислитьВыражение("Сумма(СуммаОборот)", "ОбщийИтог") * 100ПроцентНарастающий), то класс можно вывести через стандартный условный оператор:
ВЫБОР КОГДА ПроцентНарастающий <= 80 ТОГДА "A" КОГДА ПроцентНарастающий <= 95 ТОГДА "B" ИНАЧЕ "C" КОНЕЦВажные ограничения платформы при расчетах через СКД:
Во-первых, запрещено использование вложенных агрегатных функций (когда одна агрегатная функция берется от результата другой). Чтобы избежать ошибок при расчете сложных долей в ресурсах, промежуточные вычисления следует выносить на уровень «Вычисляемых полей».
Во-вторых, параметры "Первая" и "Текущая" в СКД имеют смысл только в рамках конкретной родительской группировки. При использовании кросс-таблиц (с группировками как по строкам, так и по колонкам) платформе необходимо явно указывать имя группировки, по которой идет нарастающий итог во втором параметре функции ВычислитьВыражение, иначе СКД может рассчитать итог непрерывно по всей таблице.
Иногда логика классификации требует более сложной программной обработки. В таком случае можно написать собственную функцию в общем модуле и вызывать её из выражений СКД. Проанализируем пример реализации функции АВС-классификации, которая передается через параметр типа ДанныеГрупповойОбработкиКомпоновкиДанных.
Добавим в общий модуль следующий код:
Функция ABCКлассификация(Данные) Экспорт
Перем ТаблицаЗначений;
Если Данные = Null Тогда
Возврат Null;
КонецЕсли;
Если ТипЗнч(Данные) <> Тип("ДанныеГрупповойОбработкиКомпоновкиДанных") Тогда
ВызватьИсключение "В функцию ABCКлассификация можно передавать только объект типа ДанныеГрупповойОбработкиКомпоновкиДанных";
КонецЕсли;
Если Не Данные.ВременныеДанныеОбработки.Свойство("ABCКлассификацияТаблицаЗначений", ТаблицаЗначений) Тогда
// выполним расчет классификации один раз при первом вызове
// а потом запомним выполненный расчет и дальше будем использовать его
ТаблицаЗначений = Данные.Данные.Скопировать();
ТаблицаЗначений.Колонки.Добавить("Номер", Новый ОписаниеТипов("Число"));
Номер = 0;
ОбщаяСумма = 0;
Для Каждого СтрокаТаблицыЗначений Из ТаблицаЗначений Цикл
СтрокаТаблицыЗначений.Номер = Номер;
Номер = Номер + 1;
Если СтрокаТаблицыЗначений[0] <> NULL Тогда
ОбщаяСумма = ОбщаяСумма + СтрокаТаблицыЗначений[0];
КонецЕсли;
КонецЦикла;
ТаблицаЗначений.Сортировать(ТаблицаЗначений.Колонки[0].Имя + " Убыв");
ТаблицаЗначений.Индексы.Добавить("Номер");
НакопленнаяСумма = 0;
ИндексКлассаA = Неопределено;
ИндексКлассаB = Неопределено;
Для Каждого СтрокаТаблицыЗначений Из ТаблицаЗначений Цикл
Если СтрокаТаблицыЗначений[0] <> NULL Тогда
НакопленнаяСумма = НакопленнаяСумма + СтрокаТаблицыЗначений[0];
КонецЕсли;
Если ОбщаяСумма = 0 Тогда
Процент = 1;
Иначе
Процент = НакопленнаяСумма / ОбщаяСумма;
КонецЕсли;
Если Процент > 0.75 Тогда
Если ИндексКлассаA = Неопределено Тогда
ИндексКлассаA = ТаблицаЗначений.Индекс(СтрокаТаблицыЗначений);
ИначеЕсли Процент > 0.90 Тогда
Если ИндексКлассаB = Неопределено Тогда
ИндексКлассаB = ТаблицаЗначений.Индекс(СтрокаТаблицыЗначений);
КонецЕсли;
Прервать;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияТаблицаЗначений", ТаблицаЗначений);
Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияИндексКлассаA", ИндексКлассаA);
Данные.ВременныеДанныеОбработки.Вставить("ABCКлассификацияИндексКлассаB", ИндексКлассаB);
КонецЕсли;
Если Данные.ТекущийЭлемент = Неопределено Тогда
// Итог по группировке.
Возврат Null;
Иначе
Строка = ТаблицаЗначений.Найти(Данные.Данные.Индекс(Данные.ТекущийЭлемент), "Номер");
Если Строка = Неопределено Тогда
Возврат Null;
Иначе
Индекс = ТаблицаЗначений.Индекс(Строка);
Если Индекс <= Данные.ВременныеДанныеОбработки.ABCКлассификацияИндексКлассаA Тогда
Возврат 1; // Класс A
ИначеЕсли Индекс <= Данные.ВременныеДанныеОбработки.ABCКлассификацияИндексКлассаB Тогда
Возврат 2; // Класс B
Иначе
Возврат 3; // Класс C
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецФункции
В выражении компоновки (например, в ресурсе или пользовательском поле) для получения класса мы будем использовать следующее выражение:
ABCКлассификация(ГрупповаяОбработка("Сумма(СуммаОборот)"))
При попытке использовать собственную функцию разработчики часто сталкиваются с ошибкой: «Выражение не может быть вычислено». Выясним причину такого поведения. СКД умеет работать с внешними функциями, но для этого должны строго выполняться следующие условия:
Экспорт.ИмяОбщегоМодуля.ABCКлассификация(...). Если модуль глобальный, можно обращаться напрямую.Используя рассмотренные подходы, вы сможете гибко настроить вычисление нарастающих итогов и выполнить АВС-классификацию в любом отчете на базе СКД.