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