При разработке отчетов на Системе компоновки данных (СКД) в 1С программисты регулярно сталкиваются с классической проблемой: завышением или «замножением» сумм на верхних уровнях группировок. Особенно ярко это проявляется в отчетах по взаиморасчетам, когда в одном запросе левым соединением связываются таблицы с разной степенью детализации. Например, сведения о договорах (где указана общая сумма долга) и подчиненные им документы оплаты.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем механизм работы СКД изнутри и рассмотрим пять профессиональных способов её решения — от настройки формул ресурсов до изменения архитектуры запроса.
Рассмотрим стандартную ситуацию. У нас есть договор с клиентом, по которому зафиксирована сумма к оплате (поле ДолженЗаплатитьБезСкидки) в размере 10 000 рублей. По этому договору было совершено три платежа на суммы 2 000, 3 000 и 5 000 рублей соответственно.
Когда мы соединяем эти данные в одном запросе с помощью ЛЕВОЕ СОЕДИНЕНИЕ, СУБД формирует результирующую выборку, в которой на каждый документ оплаты создается отдельная строка. При этом данные шапки договора дублируются:
Когда СКД начинает рассчитывать итоги по группировке Договор или по общим итогам отчета, стандартная агрегатная функция СУММА(ДолженЗаплатитьБезСкидки) честно складывает значения из всех трех строк. В результате мы получаем некорректную сумму долга по договору — 30 000 рублей вместо 10 000.
Ниже представлены проверенные способы решения этой проблемы, расположенные от простых формульных настроек в СКД до структурных изменений на уровне СУБД.
Это самый популярный формульный способ исправить расчет ресурсов без изменения структуры запроса. Функция ВычислитьВыражениеСГруппировкойМассив позволяет вычислить выражение для детальных записей группировки, свернуть их в массив уникальных значений и затем агрегировать на уровне вышестоящих итогов.
Чтобы рассчитать ресурс НеПоступило (который равен разности между долгом и оплатами) без замножения долга по договору, перейдем на вкладку «Ресурсы» в схеме компоновки данных и укажем следующие выражения.
Для ресурса ДолженЗаплатитьБезСкидки напишем:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки)", "Договор"))
Для результирующего ресурса НеПоступило выражение примет следующий вид:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки)", "Договор")) - Сумма(ВсегоПоступлений)
Как это работает: функция находит Максимум (или Среднее) суммы долга по каждому договору, исключая дубликаты строк, собирает эти значения в массив и только потом функция Сумма складывает элементы этого массива для общих итогов по контрагенту или всей организации.
Если вы хотите получить максимально «чистую» архитектуру отчета, избежать написания громоздких формул в ресурсах и повысить производительность, разделите исходные данные на два независимых набора данных прямо в СКД.
Шаг 1. Создаем НаборДанныхДоговоры (тип: Запрос), который выбирает уникальные данные по договорам и сумму их долга:
ВЫБРАТЬ
Договоры.Ссылка КАК Договор,
Договоры.СуммаДолга КАК ДолженЗаплатитьБезСкидки
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
Шаг 2. Создаем второй набор — НаборДанныхОплаты (тип: Запрос), который выбирает только движения по платежам и документы оплаты:
ВЫБРАТЬ
Оплаты.Договор КАК Договор,
Оплаты.Регистратор КАК ДокументОплаты,
Оплаты.СуммаОборотка КАК ВсегоПоступлений
ИЗ
РегистрНакопления.Взаиморасчеты.Обороты КАК Оплаты
Шаг 3. Переходим на вкладку «Связи наборов данных». Добавляем новую связь:
Благодаря такому подходу СКД свяжет данные на уровне вывода отчета. Строки оплат будут подчинены договорам, но само поле ДолженЗаплатитьБезСкидки физически не будет дублироваться в подчиненных записях, и стандартная функция СУММА() отработает абсолютно корректно.
Если в отчете не требуется выводить расшифровку до документов оплаты, а важен только конечный результат по договорам, проблему замножения лучше всего решить на уровне СУБД с помощью временных таблиц в одном запросе.
Рассмотрим структуру такого запроса:
// Шаг 1: Собираем оплаты по договорам во временную таблицу с группировкой
ВЫБРАТЬ
Взаиморасчеты.Договор КАК Договор,
СУММА(Взаиморасчеты.СуммаПриход) КАК ВсегоПоступлений
ПОМЕСТИТЬ ВТ_ОплатыПоДоговорам
ИЗ
РегистрНакопления.Взаиморасчеты КАК Взаиморасчеты
СГРУППИРОВАТЬ ПО
Взаиморасчеты.Договор
;
// Шаг 2: Соединяем уникальные договоры с агрегированными оплатами
ВЫБРАТЬ
Договоры.Ссылка КАК Договор,
Договоры.СуммаДолга КАК ДолженЗаплатитьБезСкидки,
ЕСТЬNULL(ВТ_ОплатыПоДоговорам.ВсегоПоступлений, 0) КАК ВсегоПоступлений,
Договоры.СуммаДолга - ЕСТЬNULL(ВТ_ОплатыПоДоговорам.ВсегоПоступлений, 0) КАК НеПоступило
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ОплатыПоДоговорам КАК ВТ_ОплатыПоДоговорам
ПО Договоры.Ссылка = ВТ_ОплатыПоДоговорам.Договор
Этот способ гарантирует максимальное быстродействие, поскольку сервер базы данных передает в СКД уже свернутые, подготовленные строки без дублей.
СКД позволяет гибко настраивать расчет одного и того же ресурса для разных уровней отчета. Если нам нужно, чтобы долг корректно рассчитывался по договору, но не суммировался на нижних уровнях (например, по документам эквайринга или кассы), настроим расчет ресурса точечно.
На вкладке «Ресурсы» добавим поле НеПоступило дважды с разными настройками:
Максимум(ДолженЗаплатитьБезСкидки) - Сумма(ВсегоПоступлений)Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(ДолженЗаплатитьБезСкидки) - Сумма(ВсегоПоступлений)", "Договор"))Таким образом, на уровне договора СКД просто вычислит разность, а на уровне контрагента или организации — сложит эти разности, полученные по каждому договору.
В сложных аналитических отчетах, когда расчет зависит от нескольких показателей и требует дополнительной фильтрации или применения условий на уровне итогов, одномерного массива может не хватить. В таких случаях на помощь приходит функция ВычислитьВыражениеСГруппировкойТаблицаЗначений.
Эта функция формирует во внутреннем контексте компоновщика полноценную таблицу значений, которую затем можно свернуть. Пример выражения ресурса для расчета сложных остатков:
Сумма(ВычислитьВыражениеСГруппировкойТаблицаЗначений("Максимум(ДолженЗаплатитьБезСкидки) КАК Долг, Сумма(ВсегоПоступлений) КАК Оплаты", "Договор"))
Это дает возможность оперировать многомерными данными прямо внутри выражений ресурсов СКД, не перегружая код модулей объектов.
Мы проанализировали проблему замножения итогов в СКД и разобрали пять способов её решения. Для выбора оптимального пути руководствуйтесь следующими правилами: