Как правильно рассчитать итоги по группировке в СКД и избежать завышения (замножения) сумм?

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

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

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

Выясняем первопричину проблемы: почему СКД «замножает» суммы?

Рассмотрим стандартную ситуацию. У нас есть договор с клиентом, по которому зафиксирована сумма к оплате (поле ДолженЗаплатитьБезСкидки) в размере 10 000 рублей. По этому договору было совершено три платежа на суммы 2 000, 3 000 и 5 000 рублей соответственно.

Когда мы соединяем эти данные в одном запросе с помощью ЛЕВОЕ СОЕДИНЕНИЕ, СУБД формирует результирующую выборку, в которой на каждый документ оплаты создается отдельная строка. При этом данные шапки договора дублируются:

  1. Строка 1: Договор №1 — Оплата №1 (2 000 руб.) — Должен оплатить: 10 000 руб.
  2. Строка 2: Договор №1 — Оплата №2 (3 000 pyб.) — Должен оплатить: 10 000 руб.
  3. Строка 3: Договор №1 — Оплата №3 (5 000 руб.) — Должен оплатить: 10 000 руб.

Когда СКД начинает рассчитывать итоги по группировке Договор или по общим итогам отчета, стандартная агрегатная функция СУММА(ДолженЗаплатитьБезСкидки) честно складывает значения из всех трех строк. В результате мы получаем некорректную сумму долга по договору — 30 000 рублей вместо 10 000.

Ниже представлены проверенные способы решения этой проблемы, расположенные от простых формульных настроек в СКД до структурных изменений на уровне СУБД.

Способ 1. Использование функции СКД «ВычислитьВыражениеСГруппировкойМассив»

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

Чтобы рассчитать ресурс НеПоступило (который равен разности между долгом и оплатами) без замножения долга по договору, перейдем на вкладку «Ресурсы» в схеме компоновки данных и укажем следующие выражения.

Для ресурса ДолженЗаплатитьБезСкидки напишем:


Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки)", "Договор"))

Для результирующего ресурса НеПоступило выражение примет следующий вид:


Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки)", "Договор")) - Сумма(ВсегоПоступлений)

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

Способ 2. Разделение логики на два набора данных и их связь (Рекомендованный метод)

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

Шаг 1. Создаем НаборДанныхДоговоры (тип: Запрос), который выбирает уникальные данные по договорам и сумму их долга:


ВЫБРАТЬ
    Договоры.Ссылка КАК Договор,
    Договоры.СуммаДолга КАК ДолженЗаплатитьБезСкидки
ИЗ
    Справочник.ДоговорыКонтрагентов КАК Договоры

Шаг 2. Создаем второй набор — НаборДанныхОплаты (тип: Запрос), который выбирает только движения по платежам и документы оплаты:


ВЫБРАТЬ
    Оплаты.Договор КАК Договор,
    Оплаты.Регистратор КАК ДокументОплаты,
    Оплаты.СуммаОборотка КАК ВсегоПоступлений
ИЗ
    РегистрНакопления.Взаиморасчеты.Обороты КАК Оплаты

Шаг 3. Переходим на вкладку «Связи наборов данных». Добавляем новую связь:

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

Способ 3. Предварительное агрегирование данных в запросе

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

Рассмотрим структуру такого запроса:


// Шаг 1: Собираем оплаты по договорам во временную таблицу с группировкой
ВЫБРАТЬ
    Взаиморасчеты.Договор КАК Договор,
    СУММА(Взаиморасчеты.СуммаПриход) КАК ВсегоПоступлений
ПОМЕСТИТЬ ВТ_ОплатыПоДоговорам
ИЗ
    РегистрНакопления.Взаиморасчеты КАК Взаиморасчеты
СГРУППИРОВАТЬ ПО
    Взаиморасчеты.Договор
;

// Шаг 2: Соединяем уникальные договоры с агрегированными оплатами
ВЫБРАТЬ
    Договоры.Ссылка КАК Договор,
    Договоры.СуммаДолга КАК ДолженЗаплатитьБезСкидки,
    ЕСТЬNULL(ВТ_ОплатыПоДоговорам.ВсегоПоступлений, 0) КАК ВсегоПоступлений,
    Договоры.СуммаДолга - ЕСТЬNULL(ВТ_ОплатыПоДоговорам.ВсегоПоступлений, 0) КАК НеПоступило
ИЗ
    Справочник.ДоговорыКонтрагентов КАК Договоры
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОплатыПоДоговорам КАК ВТ_ОплатыПоДоговорам
    ПО Договоры.Ссылка = ВТ_ОплатыПоДоговорам.Договор

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

Способ 4. Тонкая настройка расчета ресурсов по группировкам

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

На вкладке «Ресурсы» добавим поле НеПоступило дважды с разными настройками:

  1. Первая строка:
    • Выражение: Максимум(ДолженЗаплатитьБезСкидки) - Сумма(ВсегоПоступлений)
    • Рассчитывать по...: Договор (указываем конкретную группировку)
  2. Вторая строка (для вышестоящих уровней и общих итогов):
    • Выражение: Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки) - Сумма(ВсегоПоступлений)", "Договор"))
    • Рассчитывать по...: Оставляем поле пустым (это значит, что формула применится ко всем остальным группировкам, включая Общий итог)

Таким образом, на уровне договора СКД просто вычислит разность, а на уровне контрагента или организации — сложит эти разности, полученные по каждому договору.

Способ 5. Использование функции «ВычислитьВыражениеСГруппировкойТаблицаЗначений»

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

Эта функция формирует во внутреннем контексте компоновщика полноценную таблицу значений, которую затем можно свернуть. Пример выражения ресурса для расчета сложных остатков:


Сумма(ВычислитьВыражениеСГруппировкойТаблицаЗначений("Максимум(ДолженЗаплатитьБезСкидки) КАК Долг, Сумма(ВсегоПоступлений) КАК Оплаты", "Договор"))

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

Резюме и рекомендации

Мы проанализировали проблему замножения итогов в СКД и разобрали пять способов её решения. Для выбора оптимального пути руководствуйтесь следующими правилами:

← На главную