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