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