Как исправить неверный расчет итогов при построении произвольной иерархии в СКД, если у элемента несколько родителей?

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

При разработке отчетов в Системе компоновки данных (СКД) разработчики часто сталкиваются с необходимостью построения произвольной (пользовательской) иерархии. Классический пример из практики — отчет по затратам на производство в конфигурации «1С:Управление производственным предприятием» (УПП), где требуется вывести структуру подчиненности артикулов и полуфабрикатов. Однако стандартные механизмы СКД рассчитаны на строгое дерево, где у каждого подчиненного элемента может быть только один родитель. Если один и тот же артикул затраты входит в разные спецификации или заказы (то есть принадлежит к разным родительским узлам), возникает сетевая структура (граф). В этом случае СКД начинает некорректно суммировать ресурсы, задваивая или даже затраивая итоги на верхних уровнях группировок.

В данной статье мы подробно проанализируем, почему возникает эта проблема, и разберем практические способы ее решения, включая уникализацию группировок и предварительную обработку исходной таблицы значений перед передачей в компоновку.

Выясним причину: почему СКД некорректно рассчитывает итоги?

Рассмотрим ситуацию, которую описывает автор темы. В исходных данных у нас есть плоская таблица, полученная из регистров затрат:

Пример исходных данных:

Обратите внимание: артикул 2Б встречается на разных уровнях и у разных родителей. Он входит напрямую в узел 1А и одновременно в узел 4Д (который сам является подчиненным по отношению к 1А).

При построении обычной иерархии по артикулам СКД видит элемент 2Б как единую сущность. Вместо того чтобы локально рассчитать сумму для каждой ветки отдельно, система складывает все показатели. В результате по ветке 1А мы получаем сумму 11 + 12 + 20 = 43, и по ветке 4Д мы также получаем 20 + 11 + 12 = 43. Итоги верхнего уровня полностью искажаются, так как СКД не может сопоставить конкретное вхождение элемента с нужной веткой иерархии.

Давайте разберем три основных способа решения этой проблемы.

Способ 1. Создание уникальных (составных) ключей группировки

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

Выполним этот процесс по шагам:

Шаг 1. Модификация исходного запроса или таблицы значений.
При формировании данных нам необходимо добавить уникальный ключ для каждой строки. В качестве такого ключа может выступать полный путь (например, «1А/2Б» и «4Д/2Б»), комбинация артикула и номера строки, либо уникальный идентификатор строки. Добавим в наш набор данных два новых поля: УникальныйКлюч и УникальныйКлючРодителя.

Пример кода формирования таблицы значений для СКД:


// Создаем структуру ТЗ с уникальными ключами
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
ТаблицаДанных.Колонки.Добавить("АртикулРодитель", Новый ОписаниеТипов("Строка"));
ТаблицаДанных.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("УникальныйКлюч", Новый ОписаниеТипов("Строка"));
ТаблицаДанных.Колонки.Добавить("УникальныйКлючРодителя", Новый ОписаниеТипов("Строка"));

// Заполняем ТЗ уникальными ключами на основании пути или позиций
Для Каждого СтрокаДанных Из ИсходныйРегистр Цикл
    НоваяСтрока = ТаблицаДанных.Добавить();
    НоваяСтрока.Артикул = СтрокаДанных.Артикул;
    НоваяСтрока.АртикулРодитель = СтрокаДанных.АртикулРодитель;
    НоваяСтрока.Сумма = СтрокаДанных.Сумма;
    
    // Создаем уникальный ключ, связывая текущий элемент с его родителем и уровнем
    НоваяСтрока.УникальныйКлюч = СтрокаДанных.АртикулРодитель + "/" + СтрокаДанных.Артикул;
    НоваяСтрока.УникальныйКлючРодителя = НайтиУникальныйКлючРодителя(СтрокаДанных.АртикулРодитель);
КонецЦикла;

Шаг 2. Настройка связей наборов данных в СКД.
В схеме компоновки данных на вкладке Связи наборов данных мы настраиваем связь не по исходным полям Артикул -> АртикулРодитель, а по нашим новым полям: УникальныйКлюч -> УникальныйКлючРодителя. Благодаря этому СКД начнет воспринимать одну и ту же затрату на разных ветках как абсолютно разные элементы дерева.

Шаг 3. Оформление представления для пользователя.
Если вывести отчет в таком виде, пользователи увидят технические ключи вида «1А/2Б», что неприемлемо. Чтобы вернуть отчету исходный понятный вид, выполним следующее:

  1. В настройках СКД для поля группировки УникальныйКлюч перейдем в свойства поля.
  2. Найдем параметр Поле представления.
  3. Укажем в качестве поля представления наш исходный, красивый Артикул.

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

Способ 2. Предварительная подготовка данных (предрасчет сумм в ТЗ)

Если структура отчета слишком сложная или связи наборов данных в СКД работают медленно из-за большого объема информации, правильным архитектурным решением будет отказаться от расчета итогов силами компоновки. Мы можем полностью рассчитать суммы для каждого узла иерархии в коде 1С перед передачей таблицы в СКД.

Проанализируем алгоритм этого решения:

  1. Мы обходим наше дерево снизу вверх (начиная с конечных элементов — «листьев»).
  2. Для каждого родительского узла мы суммируем только те показатели, которые подчинены ему непосредственно на данном уровне.
  3. Очищаем или корректируем суммы у промежуточных узлов, если они дублируются.
  4. В СКД мы передаем уже готовую таблицу в качестве «Набора данных - Объект».

Пример программного расчета и подготовки таблицы значений:


// Пример рекурсивного расчета сумм по веткам
Процедура РассчитатьСуммыУзлов(ДеревоДанных)
    Для Каждого СтрокаУровня Из ДеревоДанных.Строки Цикл
        // Если у узла есть подчиненные элементы, сначала рассчитываем их
        Если СтрокаУровня.Строки.Количество() > 0 Тогда
            РассчитатьСуммыУзлов(СтрокаУровня);
            
            // Суммируем только непосредственных потомков для текущей ветки
            НоваяСумма = 0;
            Для Каждого ПодчиненнаяСтрока Из СтрокаУровня.Строки Цикл
                НоваяСумма = НоваяСумма + ПодчиненнаяСтрока.Сумма;
            КонецЦикла;
            
            СтрокаУровня.Сумма = НоваяСумма;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

После того как мы передали предрасчитанную таблицу в СКД, нам необходимо правильно настроить вкладку Ресурсы. Вместо стандартного выражения Сумма(Сумма) для иерархических группировок мы указываем функцию Максимум(Сумма) или Среднее(Сумма). Поскольку все суммы на каждом уровне уже посчитаны нами вручную и зафиксированы в строках ТЗ, СКД не будет складывать их повторно, а просто отобразит корректное готовое значение для каждого узла сети.

Способ 3. Ограничение расчета ресурсов с помощью функций СКД

Если мы не хотим менять структуру данных и переписывать код подготовки ТЗ, мы можем попробовать решить проблему на уровне формул ресурсов СКД.

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

В выражении ресурса для проблемного поля мы можем прописать формулу:

Сумма(ВычислитьВыражениеСГруппировкойМассив("Сумма", "Артикул"))

Или, если требуется свернуть дублирующиеся значения внутри конкретной ветки:

Сумма(Свернуть(ВычислитьВыражениеСГруппировкойМассив("Сумма", "Артикул")))

Важное примечание: Этот способ эффективен только в том случае, если в иерархии нет циклов (зацикливаний, когда элемент является родителем самого себя через несколько уровней), а задвоение сумм происходит исключительно из-за одинаковых наименований артикулов на разных уровнях подчинения. В сложных сетевых структурах данный метод может давать погрешности, поэтому приоритетными остаются первый и второй способы.

Резюме

Мы проанализировали проблему неверного суммирования в СКД при построении произвольной иерархии для сетевых структур. Для получения гарантированно точного результата рекомендуется использовать Способ 1 (создание уникальных ключей с подменой представления), так как он полностью сохраняет интерактивность отчета и позволяет использовать стандартные возможности СКД по расчету итогов. Если же отчет строится по сложным правилам распределения затрат, оптимальным выбором будет Способ 2 (предварительный расчет сумм в ТЗ), который переносит тяжелые математические вычисления на сторону сервера 1С и гарантирует предсказуемость результата.

← На главную