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