При разработке отчетов на базе Системы компоновки данных (СКД) программисты часто сталкиваются с ситуацией, когда итоговые значения числовых полей становятся некорректными. Типичный пример: у нас есть заказ с определенным весом, и этот заказ разбивается на комплектующие согласно рецептуре. Поскольку одному заказу соответствует несколько строк комплектующих, при левом соединении таблиц значение веса заказа дублируется для каждой строки состава. В результате общие итоги по полю КоличествоКг суммируют эти дубли, выдавая результат, в несколько раз превышающий реальный.
В этой статье мы подробно разберем, почему возникает эта проблема, и рассмотрим несколько профессиональных способов её решения, от настройки структуры наборов данных до использования продвинутых функций языка выражений СКД.
Проанализируем ситуацию на уровне SQL-запроса. Когда мы соединяем таблицу заказов с таблицей рецептур, СУБД формирует плоскую таблицу. Если для одной «Сборки» весом 3 кг в рецептуре указано два узла, в выборке появятся две строки, и в каждой из них в колонке КоличествоКг будет стоять цифра 3. СКД, не обладая встроенной «магией» распознавания смысла данных, просто суммирует все значения в колонке, которые попали в результат запроса. В итоге мы получаем 6 кг вместо положенных 3 кг.
Рассмотрим по шагам, как можно заставить систему считать итоги правильно.
Это наиболее правильный архитектурный подход с точки зрения логики СКД. Вместо того чтобы пытаться «починить» испорченную выборку в одном запросе, мы разделим данные на два независимых потока.
ВЫБРАТЬ
Документы.Номенклатура КАК Номенклатура,
Документы.КоличествоКг КАК КоличествоКг,
Документы.Ссылка КАК СсылкаНаЗаказ
ИЗ
Документ.Заказ КАК Документы
ВЫБРАТЬ
Рецептуры.Номенклатура КАК Номенклатура,
Рецептуры.Комплектующее КАК Комплектующее,
Рецептуры.Доля КАК ДоляСодержания
ИЗ
РегистрСведений.Рецептуры КАК Рецептуры
Номенклатура. Источником связи будет «НаборДанныхЗаказы», приемником — «НаборДанныхРецептуры».При таком подходе поле КоличествоКг берется только из родительского набора. СКД сначала рассчитает итоги по первому набору, а затем для каждой строки «подтянет» соответствующие записи из второго. Дублирования физически не произойдет, и сумма всегда будет верной независимо от количества строк в рецептуре.
Если по каким-то причинам разделение на наборы данных неудобно (например, запрос слишком сложный или данные сильно взаимосвязаны), мы можем воспользоваться мощным инструментом языка выражений СКД. Рассмотрим вариант решения через агрегатные функции, работающие с массивами группировок.
Перейдем на закладку «Ресурсы» и для поля КоличествоКг вместо стандартного Сумма(КоличествоКг) пропишем следующее выражение:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(КоличествоКг)", "Номенклатура"))
Разберем, как это работает:
ВычислитьВыражениеСГруппировкойМассив заставляет систему сначала сгруппировать данные по полю Номенклатура внутри текущего контекста расчета.Максимум(КоличествоКг). Так как в рамках одной номенклатуры (одного заказа) вес везде одинаковый, максимум просто вернет нам исходное число (3 кг) один раз, отсекая все дубли комплектующих.Сумма() складывает элементы этого массива, получая корректный общий итог.Этот метод считается универсальным, так как он корректно работает даже при наличии многоуровневых группировок в отчете, где простые математические ухищрения (вроде деления на количество строк) обычно «ломаются».
Иногда требуется, чтобы в детальных записях значение отображалось одним способом, а в итогах — другим. СКД позволяет гибко настраивать расчет ресурса для каждой группировки отдельно.
Рассмотрим пример настройки на закладке «Ресурсы»:
КоличествоКг.Номенклатура). В выражении укажем: Максимум(КоличествоКг).Это позволит избежать ситуации, когда формула, предназначенная для нижнего уровня, портит общую картину всего отчета. Проанализируем ситуацию: если мы используем Среднее(КоличествоКг), то на уровне номенклатуры итог будет верным, но в общем итоге по отчету система выдаст «среднее по всем заказам», что в корне неверно. Поэтому использование ВычислитьВыражениеСГруппировкойМассив в общем итоге является наиболее безопасным вариантом.
Часто программисты пытаются решить проблему «в лоб» через математические формулы в запросе или ресурсах. Посмотрим на пример такого подхода:
Сумма(КоличествоКг) / Количество(КомплектующееНоменклатура)
Хотя на первый взгляд это кажется логичным (мы делим раздутую сумму на количество повторов), этот метод крайне нестабилен:
NULL.Для корректного расчета сумм в условиях дублирования записей в СКД рекомендуется: