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