Как преобразовать таблицу значений в дерево значений в 1С?

Программист 1С v8.3 (Обычные формы) 1С:ERP Управление предприятием Промышленность, строительство и АПК
← На главную

В практике разработчика 1С часто возникает задача преобразования «плоского» списка данных из таблицы значений в иерархическую структуру — дерево значений. Это может потребоваться при загрузке данных из внешних файлов (Excel, JSON, CSV), выводе сложных составов изделий в ERP или при визуализации данных, где иерархия заложена в виде кодов или уровней — для этого подойдёт обработка построения иерархических печатных форм. Разберем подробно, как решить эту задачу различными способами, от классической рекурсии до использования встроенных механизмов платформы.

Метод 1: Рекурсивный обход по ключу связи (Родителю)

Это классический и наиболее универсальный способ. Он применяется, когда в каждой строке таблицы значений (ТЗ) есть уникальный идентификатор (например, ID) и идентификатор родителя (ParentID). Проанализируем алгоритм: мы ищем строки верхнего уровня (где родитель пуст), добавляем их в корень дерева, а затем для каждой добавленной строки вызываем ту же функцию, чтобы найти её «детей».

Рассмотрим пример реализации такой функции:


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

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

Важный нюанс: использование метода НайтиСтроки внутри рекурсии на больших объемах данных может привести к замедлению работы. Проанализируем ситуацию: если в таблице 10 000 строк, система будет многократно сканировать её всю. Для оптимизации можно предварительно отсортировать таблицу или использовать индексацию, если это позволяют механизмы платформы.

Метод 2: Формирование дерева по «Номеру уровня» (ERP-подход)

Иногда данные приходят в линейном виде, где нет явной ссылки на родителя, но указан номер уровня (1, 2, 3...). Например, так часто строится дерево состава изделия в 1С:ERP. В этом случае рекурсия не совсем удобна, так как родителем текущей строки является последняя созданная строка предыдущего уровня.

Разберем, как реализовать заполнение такого дерева через цикл и массив текущих уровней:


// Предположим, в ТЗ есть колонка "Уровень" (число)
Процедура СформироватьДеревоПоУровням(ТЗ, Дерево)
    
    // Массив для хранения ссылок на текущие родительские строки по уровням
    // Индекс массива соответствует номеру уровня
    РодителиПоУровням = Новый Соответствие; 
    
    Для Каждого СтрокаТЗ Из ТЗ Цикл
        ТекущийУровень = СтрокаТЗ.Уровень;
        
        Если ТекущийУровень = 1 Тогда
            // Строки первого уровня добавляем в корень
            НоваяСтрока = Дерево.Строки.Добавить();
        Иначе
            // Ищем родителя на уровень выше
            РодительскаяСтрока = РодителиПоУровням.Получить(ТекущийУровень - 1);
            Если РодительскаяСтрока <> Неопределено Тогда
                НоваяСтрока = РодительскаяСтрока.Строки.Добавить();
            Иначе
                // Если родитель не найден (нарушена логика данных), добавляем в корень
                НоваяСтрока = Дерево.Строки.Добавить();
            КонецЕсли;
        КонецЕсли;
        
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
        // Запоминаем текущую строку как потенциального родителя для следующего уровня
        РодителиПоУровням.Вставить(ТекущийУровень, НоваяСтрока);
    КонецЦикла;
    
КонецПроцедуры

Этот метод работает за один проход по таблице значений (линейная сложность), что делает его крайне эффективным для больших структур.

Метод 3: Использование Системы Компоновки Данных (СКД)

Многие разработчики забывают, что мощный движок 1С — СКД — умеет строить иерархию автоматически — для этого есть консоль кода, запросов и СКД для разработчика. Если ваша таблица содержит ссылки на объекты, имеющие иерархию в метаданных, или если вы можете задать связь «сама на себя», то СКД сделает всё за вас.

Посмотрим на последовательность действий:

  1. Создаем схему компоновки данных программно или в макете.
  2. Добавляем набор данных — объект (нашу таблицу значений).
  3. В связях наборов данных указываем связь набора с самим собой: Источник связи — «Идентификатор», Приемник связи — «Родитель», Условие связи — связь по иерархии.
  4. Выгружаем результат в ДеревоЗначений.

Этот способ удобен тем, что платформа берет на себя все вычисления и оптимизацию обхода дерева.

Метод 4: Запрос к временной таблице

Если данные уже находятся на сервере, мы можем поместить таблицу значений во временную таблицу и выполнить запрос с предложением ИТОГИ ПО ... ИЕРАРХИЯ. Выясним причину, почему это удобно: результат запроса можно выгрузить в дерево одной командой РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам).

Пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Данные ИЗ &ТЗ КАК ТЗ;
    |ВЫБРАТЬ * ИЗ ВТ_Данные
    |ИТОГИ ПО Ссылка ИЕРАРХИЯ";
Запрос.УстановитьПараметр("ТЗ", МояТаблица);

Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамИерархия);

Важно: этот метод работает корректно только если иерархия строится по ссылочным типам (например, справочник Номенклатура), у которых в метаданных определен владелец или родитель.

Сводные советы по оптимизации

Проанализируем основные моменты, которые помогут сделать работу с деревьями более стабильной:

Таким образом, выбор метода зависит от структуры вашей исходной таблицы. Если есть «Родитель» — используйте рекурсию или запросы. Если есть только «Уровень» — используйте линейный обход с фиксацией текущего родителя в массиве или соответствии.

← На главную