При разработке отчетов в системе компоновки данных (СКД) разработчики часто сталкиваются с ситуацией, когда данные из разных источников имеют разную степень детализации (гранулярность). Типичный пример: продажи или кассовые чеки ведутся с детализацией до кассира, а безналичная выручка фиксируется суммарно за день (или привязана к Z-отчету/терминалу без разделения по сотрудникам).
В результате при левом соединении таблиц в запросе суточная сумма безналичных оплат дублируется в строке каждого кассира, работавшего в этот день. Например, если 31 декабря работали два кассира, у каждого в строке отображается по 1000 рублей. При стандартной агрегации Сумма(Безналичные) система сложит эти показатели и выдаст в итоге дня 2000 рублей вместо фактической 1000 рублей, а также исказит общий итог за весь период.
Разберем, почему возникает такое поведение платформы и какими способами можно получить корректные итоги как внутри дня, так и по отчету в целом.
Проанализируем механику работы СКД. При обычном формировании отчета платформа применяет агрегатную функцию к детальным записям результирующей таблицы запроса. Если в выборке за одну дату сформировалось несколько строк из-за группировки по кассирам, то поле с суточной безналичной суммой дублируется физически в каждой строке выборки. Стандартное выражение ресурса суммирует каждую запись, что приводит к завышению показателей.
Простая замена агрегата на Максимум(Безналичные) решает вопрос только на уровне группировки по конкретной дате, но ломает общий итог за период: вместо сложения показателей разных дней СКД выведет максимальное значение за весь период среди всех дат.
Рассмотрим несколько рабочих подходов для решения этой задачи.
Наиболее гибкий способ, не требующий кардинального переписывания запросов, заключается в тонкой настройке вкладки Ресурсы схемы компоновки данных. Платформа 1С:Предприятие 8 позволяет указывать различные формулы расчета одного и того же ресурса для разных уровней группировок.
Разберем по шагам, как настроить расчет ресурса:
Безналичные для расчета по группировке Дата (и по детальным записям/кассирам). В колонке Выражение укажем:
Максимум(Безналичные)Безналичные, но колонку Рассчитывать по... оставим пустой (это определит расчет для общего итога отчета). В колонке Выражение пропишем специальную конструкцию с агрегацией по массиву:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(Безналичные)", "Дата"))
Посмотрим на логику работы этой формулы: функция ВычислитьВыражениеСГруппировкойМассив сначала вычисляет максимальное значение поля внутри каждой уникальной даты, складывает полученные результаты в промежуточный массив, а внешняя функция Сумма складывает элементы этого массива для формирования общего итога отчета. В итоге за каждую дату берется ровно одно значение, и все даты корректно плюсуются между собой.
Важное ограничение: данный способ жестко завязан на имя группировки. Если пользователь в режиме предприятия изменит структуру отчета (например, удалит группировку по полю Дата), расчет общего итога может выдать ошибку или неверный результат.
Второй надежный подход — разделить получение данных на два отдельных набора данных (набора данных — запрос) внутри СКД, как предлагалось в обсуждении:
Дата и Кассир.Дата (без поля Кассир).Дата (при необходимости также связываем организацию, кассу ККМ или подразделение).В этом случае поле Безналичные присутствует только на уровне группировки по дате и не привязывается к строкам кассиров. В ресурсах можно использовать стандартную функцию Сумма(Безналичные), и дублирования не возникнет, так как СКД сама распределит агрегацию на соответствующий уровень иерархии.
Если требуется оставить один набор данных и использовать стандартные настройки ресурсов, задвоение можно устранить непосредственно в запросе. Для этого сумму безналичных оплат относят только к одной первой строке кассира за день, а для остальных строк кассиров за этот же день выводят 0.
Рассмотрим пример реализации с помощью временных таблиц и нумерации строк:
ВЫБРАТЬ
Продажи.Дата КАК Дата,
Продажи.Кассир КАК Кассир,
Продажи.Наличные КАК Наличные,
ВЫБОР
КОГДА Продажи.Кассир = ПервыеКассиры.Кассир
ТОГДА БезналичныеПоДням.СуммаБезналичные
ИНАЧЕ 0
КОНЕЦ КАК Безналичные
ИЗ
ВТ_ПродажиКассиров КАК Продажи
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_БезналичныеПоДням КАК БезналичныеПоДням
ПО Продажи.Дата = БезналичныеПоДням.Дата
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Дата,
МИНИМУМ(Кассир) КАК Кассир
ИЗ
ВТ_ПродажиКассиров
СГРУППИРОВАТЬ ПО
Дата
) КАК ПервыеКассиры
ПО Продажи.Дата = ПервыеКассиры.Дата
При таком подходе обычная Сумма(Безналичные) в ресурсах СКД корректно отработает на всех уровнях отчета: в детальных записях безналичная сумма будет показана один раз у первого кассира дня, внутри группировки дня сумма сойдется без дублей, а в общем итоге сложатся все дни.