При разработке сложных отчетов в Системе компоновки данных (СКД) программисты часто сталкиваются с задачей: необходимо вывести значение реквизита (например, "Плановая сумма" или "Допустимый остаток") для группировки, но при этом не суммировать его по детальным записям. Проблема обостряется, когда в отчете используется иерархическая группировка. В таких случаях стандартные итоги начинают вести себя непредсказуемо: либо показывают неверные суммы, либо просто дублируют значения верхнего уровня.
В этой статье мы подробно проанализируем, почему так происходит, и разберем несколько проверенных способов решения проблемы, включая метод "дублирующего поля" и использование связей между наборами данных.
Рассмотрим типовую ситуацию. Мы строим отчет по взаиморасчетам, где детальными записями являются документы-регистраторы, а группировкой — ДоговорКонтрагента. У договора есть реквизит ПлановаяСумма. Если мы просто укажем в ресурсах Сумма(ПлановаяСумма), то СКД сложит это значение столько раз, сколько документов-регистраторов попало в отчет по этому договору. Это классическая ошибка дублирования итогов.
Для решения этой проблемы часто применяют функцию ВЫЧИСЛИТЬВыражениеСГруппировкойМассив. Казалось бы, логика проста: собрать массив минимальных значений по договору и просуммировать их на уровне выше. Однако, как только мы включаем иерархию по полю ДоговорКонтрагента, механизм "ломается".
Выясним причину: в СКД при расчете иерархических уровней контекст вычисления "схлопывается". Поле, по которому строится иерархия, на родительских узлах принимает значение самого родителя. В итоге функция ВЫЧИСЛИТЬВыражениеСГруппировкойМассив не может корректно проитерировать подчиненные элементы, так как для системы они становятся неотличимы от контекста текущей группы. Все значения подменяются значением родителя, и итоговая сумма превращается в минимум или максимум одного значения.
Проанализируем наиболее эффективное решение, которое позволяет "обмануть" механизм определения контекста СКД. Суть метода заключается в создании в запросе дополнительного поля, которое будет полностью дублировать ссылку на объект, но иметь другое имя.
ВЫБРАТЬ
Продажи.ДоговорКонтрагента КАК ДоговорКонтрагента,
Продажи.ДоговорКонтрагента КАК ДоговорДляРасчета,
Продажи.СуммаОборот КАК СуммаОборот,
Продажи.ДоговорКонтрагента.ПлановаяСумма КАК ПлановаяСумма
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ДоговорКонтрагента (с включенной иерархией).
Сумма(ВычислитьВыражениеСГруппировкойМассив("Минимум(ПлановаяСумма)", "ДоговорДляРасчета"))
Почему это работает? Поскольку иерархия строится по полю ДоговорКонтрагента, а агрегация в ресурсе обращается к полю ДоговорДляРасчета, СКД не подменяет значения в массиве при расчете иерархических уровней. Система видит "другое" поле и корректно собирает массив из подчиненных элементов.
Рассмотрим более архитектурно "чистый" подход. Вместо того чтобы пытаться выудить данные из одного плоского запроса, мы разделим данные на два потока. Это исключит саму возможность дублирования данных на уровне детальных записей.
Выполним следующие шаги:
ДоговорыКонтрагентов и выбираем поле Ссылка и ПлановаяСумма.
ВЫБРАТЬ
Договоры.Ссылка КАК ДоговорКонтрагента,
Договоры.ПлановаяСумма КАК ПлановаяСумма
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
ГДЕ
Договоры.Ссылка В (&ДоговорыДляСвязи)
ДоговорКонтрагента. Обязательно укажем параметр &ДоговорыДляСвязи и установим флаг "Список параметров".В этом случае ресурс ПлановаяСумма будет привязан к объекту "Договор", а не к строкам оборотов. Теперь стандартная функция Сумма(ПлановаяСумма) в ресурсах отработает корректно даже в иерархии, так как СКД понимает кратность данных из разных наборов.
Если мы не хотим менять структуру запроса, можно попробовать использовать расширенные параметры функции ВычислитьВыражение. Посмотрим на пример выражения, которое может помочь обойти ограничения иерархических отборов:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Минимум(ПлановаяСумма)", "ДоговорКонтрагента", , "Группировка"))
Здесь четвертый параметр "Группировка" (или его аналоги в зависимости от версии платформы) заставляет СКД вычислять выражение именно в контексте текущей строки группировки, что иногда позволяет избежать "зацикливания" на родителе в иерархии. Однако стоит помнить, что согласно документации, ВычислитьВыражение может не учитывать иерархические отборы, поэтому данный метод требует тщательного тестирования на конкретной версии платформы.
Разберем несколько критических моментов, о которых стоит помнить:
ДоговорКонтрагента Иерархия, и прописать там выражение из способа №1.NULL. Рекомендуется использовать функцию ЕСТЬNULL(ПлановаяСумма, 0) внутри агрегатов, чтобы не "сломать" математическое суммирование массива.ВычислитьВыражение(...), а уже его использовать в ресурсах. Это позволяет отладить получение значения на уровне одной строки, прежде чем пытаться его суммировать.Подводя итог, можно сказать, что проблема итогов в иерархии СКД — это следствие оптимизации контекстов компоновки. Использование дублирующих полей остается самым простым и быстрым способом "пробросить" нужные значения сквозь уровни иерархии и получить корректные, не завышенные суммы в отчете.