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