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