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