Как преобразовать Таблицу значений в Дерево значений с сохранением иерархии и уровней?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки на платформе 1С часто возникает задача визуализации данных в виде иерархии (поможет отчет по остаткам номенклатуры в виде дерева). Если исходные данные представлены в плоском виде (например, результат запроса или загруженная ТаблицаЗначений), их необходимо преобразовать в объект ДеревоЗначений. В этой статье мы подробно разберем несколько способов решения этой задачи: от простого копирования строк до сложной рекурсивной группировки по нескольким уровням.

Понимание структуры Дерева значений

Прежде чем приступать к коду, вспомним, чем ДеревоЗначений отличается от ТаблицыЗначений. В таблице все строки находятся на одном уровне. В дереве же каждая строка имеет коллекцию Строки, которая может содержать подчиненные строки. Таким образом, обращение к строкам первого уровня происходит через ДеревоЗначений.Строки, а к подчиненным — через ТекущаяСтрока.Строки. Основная сложность при заполнении заключается именно в правильном определении родительского узла для каждой записи.

Способ 1. Рекурсивное построение дерева по группировкам

Рассмотрим наиболее гибкий алгоритм, который позволяет строить дерево на основе произвольного списка колонок-группировок. Этот метод полезен, когда у вас есть плоская таблица (например, "Категория — Подкатегория — Товар") и вы хотите превратить её в иерархию — для этого подойдёт обработка преобразования плоского списка товаров в дерево.

Разберем реализацию функции, которая принимает таблицу и строку с именами колонок для группировки:


Функция ТаблицаЗначенийВДеревоЗначений(ТЗ, ПоляГруппировок)
    // Разделяем строку группировок в массив
    Группировки = СтрРазделить(ПоляГруппировок, ",", Ложь);
    
    ДеревоЗначений = Новый ДеревоЗначений;
    
    // Копируем структуру колонок из исходной таблицы
    Для Каждого Колонка Из ТЗ.Колонки Цикл
        ДеревоЗначений.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
    КонецЦикла;
    
    // Запускаем рекурсивное построение
    СоздатьСтрокиДереваРекурсивно(ТЗ, ДеревоЗначений.Строки, Группировки);
    
    Возврат ДеревоЗначений;
КонецФункции

Теперь проанализируем саму процедуру рекурсии. Она на каждом шаге берет одну группировку, сворачивает таблицу по ней и создает узлы дерева:


Процедура СоздатьСтрокиДереваРекурсивно(ТаблицаЗначений, СтрокиДерева, Группировки, ИндексГруппировки = 0)
    // Проверяем, не дошли ли мы до конца списка группировок
    Если ИндексГруппировки = Группировки.Количество() Тогда
        // Добавляем детальные записи (листья дерева)
        Для Каждого Стр Из ТаблицаЗначений Цикл
            НоваяСтрока = СтрокиДерева.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтрока, Стр);
        КонецЦикла;
        Возврат;
    КонецЕсли;

    ИмяГруппировки = Группировки[ИндексГруппировки];
    
    // Получаем уникальные значения для текущего уровня
    ЗначенияГруппировки = ТаблицаЗначений.Скопировать(, ИмяГруппировки);
    ЗначенияГруппировки.Свернуть(ИмяГруппировки);

    Для Каждого СтрГруппа Из ЗначенияГруппировки Цикл
        Значение = СтрГруппа[ИмяГруппировки];
        НоваяСтрокаДерева = СтрокиДерева.Добавить();
        
        // Заполняем значение текущей группировки
        НоваяСтрокаДерева[ИмяГруппировки] = Значение;
        
        // Отбираем строки таблицы, относящиеся к этой группе
        ПараметрыОтбора = Новый Структура(ИмяГруппировки, Значение);
        СтрокиГруппы = ТаблицаЗначений.Скопировать(ПараметрыОтбора);
        
        // Рекурсивный вызов для следующего уровня
        СоздатьСтрокиДереваРекурсивно(СтрокиГруппы, НоваяСтрокаДерева.Строки, Группировки, ИндексГруппировки + 1);
    КонецЦикла;
КонецПроцедуры

Важное замечание: Использование метода Скопировать() внутри цикла на больших объемах данных (десятки тысяч строк) может привести к замедлению работы, так как платформа каждый раз создает новую таблицу в памяти. В таких случаях рекомендуется изучить производительность способов обхода таблицы значений по группировке, предварительно отсортировать таблицу и обходить её одним циклом.

Способ 2. Плоское заполнение (один уровень)

Если задача стоит максимально просто — перенести данные из таблицы в дерево без создания сложной вложенности (например, подготовить объект для дальнейшей ручной обработки), мы можем использовать упрощенный цикл. Посмотрим на этот пример:


// Создаем структуру дерева на основе таблицы
ДеревоМечтаний = Новый ДеревоЗначений;
Для каждого Колонка Из ТЗ.Колонки Цикл
    ДеревоМечтаний.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);
КонецЦикла;

// Переносим данные
Для каждого СтрокаТЗ Из ТЗ Цикл
    НоваяСтрокаДерева = ДеревоМечтаний.Строки.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрокаДерева, СтрокаТЗ);
КонецЦикла;

Здесь крайне важно использовать метод ЗаполнитьЗначенияСвойств(). Это стандартная функция 1С, которая автоматически сопоставляет имена колонок источника и приемника. Она работает значительно быстрее, чем ручное присваивание каждого поля через точку (НоваяСтрока.Товар = СтрокаТЗ.Товар).

Способ 3. Построение дерева через запрос (рекомендуемый, включая выполнение запросов через COM-подключение)

Проанализируем ситуацию, когда данные еще не получены в таблицу, и требуется быстрый анализ данных. Самый эффективный способ сформировать дерево — переложить эту задачу на СУБД с помощью механизма итогов, используя Консоль запросов для управляемых форм 8.3 (удобно через инструментарий разработчика с консолью запросов и СКД). Выясним, как это реализовать:

  1. В тексте запроса используйте конструкцию ИТОГИ... ПО.... Например: ВЫБРАТЬ Товар, Категория, Склад ИЗ ... ИТОГИ ПО Категория, Склад.
  2. При получении результата используйте метод РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам).

Этот метод не только самый быстрый, но и самый надежный, так как платформа сама заботится о типизации и иерархических связях. Вы сразу получаете готовый объект ДеревоЗначений.

Оптимизация и полезные советы

При работе с иерархическими структурами следует учитывать несколько технических нюансов:

1. Типизация колонок. При создании колонок дерева вручную всегда копируйте типы из таблицы-источника. Вместо создания нового описания типов используйте КолонкаТЗ.ТипЗначения. Это позволит избежать ошибок при заполнении данных, особенно если в таблице используются составные типы или специфические квалификаторы (длина строки, точность числа).

2. Поиск в дереве. Помните, что метод Найти() или НайтиСтроки() в дереве значений ищет только на том уровне, к которому вы обращаетесь. Для поиска по всему дереву (включая все вложенные уровни) необходимо писать собственную рекурсивную функцию обхода.

3. Отображение на форме. Если вы создали дерево в программном коде на сервере и хотите показать его пользователю, не забудьте связать его с реквизитом формы (для разработки интерфейсов без программирования есть конструктор интерфейсов с иерархическими списками). Для этого используются методы:

4. Использование БСП. В современных конфигурациях (УТ 11, ERP, БП 3.0) в общем модуле ОбщегоНазначения часто уже есть готовые функции для развертывания таблицы в дерево. Прежде чем писать свой велосипед, проверьте наличие метода РазвернутьТаблицуЗначенийВДеревоЗначений().

Подводя итог, можно сказать, что выбор метода преобразования зависит от объема данных и сложности требуемой структуры. Для простых задач подойдет ЗаполнитьЗначенияСвойств() в цикле, для сложных иерархий — рекурсия, а для максимальной производительности — использование итогов в запросах.

← На главную