Как сделать группировку по вычисляемому полю с функцией ВЫЧИСЛИТЬВЫРАЖЕНИЕ в СКД?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке аналитических отчетов в схеме компоновки данных (СКД) часто возникает задача распределить элементы по категориям на основании нарастающего итога, например выполнить ABC-классификацию номенклатуры по объему продаж. Попытка рассчитать класс в вычисляемом поле через функцию ВЫЧИСЛИТЬВЫРАЖЕНИЕ и сразу сгруппировать по нему отчет нередко приводит к аварийному завершению работы платформы 1С или системным ошибкам компоновки.

Разберем, почему возникает такое поведение платформы, как устроен механизм расчета выражений в СКД и какие существуют надежные способы решения этой задачи.

Причина ошибки и ограничение архитектуры СКД

Проанализируем выражение вычисляемого поля, которое рассчитывает класс на основе нарастающего итога:


Выбор 
    Когда ВЫЧИСЛИТЬВЫРАЖЕНИЕ("Сумма(ДоляОтСуммыПродажи)", , ,"Первая", "Текущая") <= &ОбъемКласса_A 
        Тогда "A-класс" 
    Когда ВЫЧИСЛИТЬВЫРАЖЕНИЕ("Сумма(ДоляОтСуммыПродажи)", , ,"Первая", "Текущая") > &ОбъемКласса_A 
         И ВЫЧИСЛИТЬВЫРАЖЕНИЕ("Сумма(ДоляОтСуммыПродажи)", , ,"Первая", "Текущая") <= &ОбъемКласса_B 
        Тогда "B-класс" 
    Иначе "C-класс" 
Конец

Если мы выводим данные в виде плоского списка (детальные записи) или выводим это поле в качестве ресурса внутри группировки Товар, отчет формируется корректно. Однако при попытке настроить структуру иерархии вида:

  1. Группировка 1: Класс (вычисляемое поле)
  2. Группировка 2: Товар

Происходит сбой. Выясним причину этой проблемы:

  1. Порядок компоновки: Процессор компоновки данных сначала формирует структуру строк и дерева группировок, и лишь затем рассчитывает значения ресурсов и контекстных функций для конкретных строк.
  2. Циклическая зависимость (рекурсия): Чтобы определить, в какую группу верхнего уровня поместить товар, системе нужно заранее знать значение поля Класс. Но для расчета значения функции ВЫЧИСЛИТЬВЫРАЖЕНИЕ с нарастающим итогом по строкам детальных записей структура строк уже должна быть окончательно построена и упорядочена.
  3. Ограничения функции КлассификацияABC: Встроенная функция СКД КлассификацияABC("Сумма(СуммаПродажи)", 3, "80, 95") предназначена исключительно для использования в ресурсах отчета. Она присваивает класс строке в контексте родительской группировки и не может служить ключом для группировки верхнего уровня.

Рассмотрим рабочие варианты обхода этого ограничения.

Вариант 1. Расчет классификации и нарастающего итога в запросе СКД

Наиболее производительный и универсальный способ — перенести расчет нарастающего итога и классификации непосредственно в текст запроса набора данных. В этом случае поле Класс приходит в СКД как обычное строковое поле выборки, и по нему можно строить любые группировки без падения платформы.

Разберем по шагам логику построения такого запроса:

  1. Во временной таблице собираем продажи по товарам с подсчетом суммы.
  2. Во второй временной таблице вычисляем общую сумму продаж по всей выборке.
  3. Выполняем левое соединение таблицы продаж самой с собой по условию нестрогого неравенства (нарастающий итог) и делим накопленную сумму на общий итог.
  4. С помощью конструкции ВЫБОР присваиваем категорию каждому товару.

Посмотрим на пример текста запроса:


ВЫБРАТЬ
    ПродажиОбороты.Номенклатура КАК Номенклатура,
    СУММА(ПродажиОбороты.СуммаОборот) КАК СуммаПродажи
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
    ПродажиОбороты.Номенклатура
;

ВЫБРАТЬ
    СУММА(ВТ_Продажи.СуммаПродажи) КАК ОбщийИтог
ПОМЕСТИТЬ ВТ_ОбщийИтог
ИЗ
    ВТ_Продажи КАК ВТ_Продажи
;

ВЫБРАТЬ
    Т1.Номенклатура КАК Номенклатура,
    Т1.СуммаПродажи КАК СуммаПродажи,
    СУММА(Т2.СуммаПродажи) / ВТ_ОбщийИтог.ОбщийИтог * 100 КАК НарастающаяДоля
ПОМЕСТИТЬ ВТ_НарастающийИтог
ИЗ
    ВТ_Продажи КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Продажи КАК Т2
        ПО Т1.СуммаПродажи <= Т2.СуммаПродажи
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ОбщийИтог КАК ВТ_ОбщийИтог
        ПО (ИСТИНА)
СГРУППИРОВАТЬ ПО
    Т1.Номенклатура,
    Т1.СуммаПродажи,
    ВТ_ОбщийИтог.ОбщийИтог
;

ВЫБРАТЬ
    ВТ.Номенклатура КАК Номенклатура,
    ВТ.СуммаПродажи КАК СуммаПродажи,
    ВЫБОР
        КОГДА ВТ.НарастающаяДоля <= &ПроцентКлассаА ТОГДА "A-класс"
        КОГДА ВТ.НарастающаяДоля <= &ПроцентКлассаВ ТОГДА "B-класс"
        ИНАЧЕ "C-класс"
    КОНЕЦ КАК КлассABC
ИЗ
    ВТ_НарастающийИтог КАК ВТ

Теперь в схеме компоновки данных мы можем свободно перетащить поле КлассABC в корень дерева группировок, а поле Номенклатура сделать подчиненной группировкой.

Вариант 2. Программная подготовка данных через «Набор данных - Объект»

Если логика классификации сложная, зависит от динамических параметров отчета или требует сложных алгоритмов постобработки, применим программное формирование источника через ТаблицаЗначений в модуле отчета.

Разберем реализацию в процедуре ПриКомпоновкеРезультата модуля отчета:


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

В самой схеме компоновки данных создается «Набор данных - Объект» с именем ТаблицаДанных, в котором описываются поля Номенклатура, СуммаПродажи и КлассABC. В настройках отчета указывается требуемая структура группировок без риска падения платформы.

Итоги

Попытка использовать вычисляемые поля с нарастающим итогом в качестве верхнеуровневых группировок противоречит модели вычисления данных в СКД. Для построения отчетов с классификацией по нарастающему итогу следует формировать признак категории до момента инициализации дерева группировок — в тексте запроса или через программную передачу внешней таблицы значений в процессор компоновки.

← На главную