К статье
Практический материал

Разработка и Конфигурация · 12 мин

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

Как заполнить дерево значений из таблицы значений в 1С: код загрузки, группировка по итогам, рекурсивное построение иерархии, типичные ошибки и готовые примеры.

Преобразование таблицы значений в дерево значений — одна из типовых задач разработчика 1С. Таблица удобна для хранения и обработки плоских данных, а дерево — для отображения иерархии: групп и элементов справочника, строк документа с подчинёнными строками, разделов отчёта с детализацией. Платформа предоставляет несколько способов такого преобразования, и выбор зависит от того, как устроены исходные данные.

Самый простой сценарий — когда в таблице есть колонка с группировкой (например, «НоменклатурнаяГруппа» или «Родитель»), и требуется свернуть строки в двухуровневое дерево. Для этого у объекта «ТаблицаЗначений» есть встроенный метод Скопировать() в сочетании со свёрткой либо прямой перенос строк с распределением по ветвям дерева. Более сложный случай — многоуровневая иерархия по ссылке на родителя, где нужен рекурсивный обход.

Ниже разобраны рабочие приёмы: группировка через запрос с итогами, ручное построение дерева циклом, рекурсивное заполнение по родителю, а также контроль результата и типичные ошибки, из-за которых дерево получается пустым, дублированным или «битым» по составу колонок.

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

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

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

Маршрут решения

Как выбрать действие

Данные можно получить запросом из базыИспользуйте запрос с ИТОГИ ПО и Выгрузить(ПоГруппировкам) — дерево сформируется автоматически
Таблица уже в памяти, иерархия одноуровневая (группа + строки)Скопируйте колонки в дерево и распределите строки циклом с поиском ветви
В таблице есть колонка Родитель и глубина вложенности неизвестнаСтройте дерево рекурсивной процедурой с поиском строк по родителю
Нужна только визуальная группировка на формеРассмотрите динамический список или свёртку таблицы — дерево может не понадобиться

Подготовка: создаём дерево и переносим структуру колонок

Дерево значений создаётся конструктором Новый ДеревоЗначений. Чтобы строки таблицы можно было переносить без потери данных, у дерева должен быть тот же набор колонок, что у таблицы, плюс при необходимости служебные колонки (признак группы, картинка строки). Состав колонок копируется циклом по коллекции Таблица.Колонки.

Обратите внимание на типы: если колонка таблицы имеет составной тип, в дереве её лучше создавать с тем же описанием типов — иначе при присваивании значений возможно неявное приведение и потеря данных (например, обрезка строки до более короткой длины). Описание типов берётся из свойства ТипЗначения колонки таблицы.

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

Если дерево будет выводиться на форму, заранее добавьте колонку для картинки строки и привяжите её к элементу формы через свойство «ПутьКДаннымКартинкиСтроки» — это избавит от доработок на этапе отображения.

Заполнение дерева из запроса с итогами

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

bsl
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Родитель КАК Группа,
|    Номенклатура.Ссылка КАК Номенклатура,
|    Номенклатура.Код КАК Код
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ИТОГИ ПО
|    Группа";
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

У этого способа есть особенность: в строках итогов детальные поля (кроме полей группировки) содержат NULL или пустые значения, а в детальных строках поле группировки продублировано. Если нужна «чистая» структура, где группа хранит только себя, а товар — только себя, используйте иерархические итоги (ИТОГИ ПО Ссылка ТОЛЬКО ИЕРАРХИЯ для иерархических справочников) либо обрабатывайте ветви после выгрузки.

Ручное заполнение дерева из готовой таблицы значений

Когда таблица уже существует, а иерархия одноуровневая (группа → строки), дерево заполняется циклом: для каждой строки таблицы ищется ветвь первого уровня по значению группирующей колонки; если ветви нет — создаётся; затем в неё добавляется строка с данными.

Порядок действий
  1. 1
    Создайте дерево значений и скопируйте в него колонки таблицы с сохранением типов
  2. 2
    Определите колонку группировки — по ней будут формироваться ветви первого уровня
  3. 3
    В цикле по строкам таблицы ищите ветвь через Строки.Найти(ЗначениеГруппы, ИмяКолонки)
  4. 4
    Если ветвь не найдена — добавьте её и заполните группирующую колонку
  5. 5
    Добавьте в найденную ветвь дочернюю строку и заполните её через ЗаполнитьЗначенияСвойств
  6. 6
    Проверьте итоговое количество строк и уровней дерева
bsl
Для Каждого СтрокаТЗ Из Таблица Цикл
    Ветвь = Дерево.Строки.Найти(СтрокаТЗ.Группа, "Группа");
    Если Ветвь = Неопределено Тогда
        Ветвь = Дерево.Строки.Добавить();
        Ветвь.Группа = СтрокаТЗ.Группа;
    КонецЕсли;
    НоваяСтрока = Ветвь.Строки.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
КонецЦикла;

Метод ЗаполнитьЗначенияСвойств переносит только совпадающие по именам свойства, поэтому состав колонок дерева и таблицы должен совпадать. Если в таблице тысячи строк, поиск ветви методом Найти внутри цикла даст квадратичную сложность; для ускорения заведите соответствие, где ключ — значение группы, а значение — строка дерева.

Рекурсивное построение многоуровневой иерархии по родителю

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

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

// Вызов для верхнего уровня:
ДобавитьВДерево(Дерево.Строки, Таблица, ПустаяСсылкаРодителя);

Критичный момент — корректное значение «корневого» родителя. В иерархических справочниках у элементов верхнего уровня родитель — пустая ссылка соответствующего типа. Если передать Неопределено вместо пустой ссылки, сравнение в отборе не сработает, и дерево останется пустым. Перед запуском проверьте, чем заполнена колонка «Родитель» у корневых строк.

Второй риск — зацикливание: если в данных есть ошибка и элемент ссылается сам на себя или образуется цикл по цепочке родителей, рекурсия уйдёт в бесконечный обход и завершится переполнением стека. Для защиты ведите соответствие уже обработанных ссылок и пропускайте повторы.

Проверка результата и вывод дерева на форму

После заполнения убедитесь, что дерево содержит ожидаемые данные. Базовые проверки: количество строк верхнего уровня, общее число строк с учётом вложенных, отсутствие потерянных строк таблицы. Сумму «утерянного» проще всего контролировать по счётчику: количество детальных строк дерева должно совпадать с количеством строк исходной таблицы (при одноуровневой схеме).

Для отображения на управляемой форме дерево значений добавляется как реквизит формы типа «ДеревоЗначений», а программное заполнение выполняется через преобразование: ЗначениеВРеквизитФормы(Дерево, "ИмяРеквизита"). Обратное чтение — РеквизитФормыВЗначение. Забытое преобразование — частая причина пустой таблицы на форме при заполненном дереве в коде.

Контрольные точки

Как проверить результат

Количество детальных строк дерева
Норма

Равно числу строк исходной таблицы

Тревожный признак

Меньше — часть строк потеряна при отборе или поиске ветвей

Строки верхнего уровня
Норма

Содержат только значения групп или корневые элементы

Тревожный признак

Верхний уровень продублирован детальными строками

Отображение на форме
Норма

Дерево разворачивается, ветви раскрываются

Тревожный признак

Пусто — не выполнено ЗначениеВРеквизитФормы

Типы данных в колонках
Норма

Совпадают с типами таблицы

Тревожный признак

Строки обрезаны, ссылки заменены на строки

Типичные ошибки и ограничения

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

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

Типичные ошибки
01
Поиск корня по Неопределено вместо пустой ссылки — рекурсия не находит ни одной строки, дерево пустое
02
Разные типы колонки группировки в таблице и дереве — Найти не срабатывает, группы дублируются
03
Отсутствие ЗначениеВРеквизитФормы при выводе на форму — дерево заполнено, но на форме пусто
04
Поиск ветви методом Найти внутри цикла на большой таблице — сильное замедление, нужен индекс или соответствие
05
Рекурсия без защиты от циклических ссылок — переполнение стека на повреждённых данных
06
Копирование колонок без описания типов — обрезка строк и потеря ссылочных значений

По производительности: для таблиц в десятки тысяч строк добавьте индекс по колонке группировки (Таблица.Индексы.Добавить("Группа")) перед вызовом НайтиСтроки в рекурсии — это заметно ускоряет отбор. А если итоговая цель — только свернуть данные по группе без иерархии, возможно, достаточно метода таблицы Свернуть(), и дерево не понадобится вовсе.

Итог

Что важно запомнить

Дерево из запроса строится через ИТОГИ ПО и Выгрузить(ПоГруппировкам); из готовой таблицы — копированием колонок и распределением строк по ветвям циклом или рекурсией по родителю. Контролируйте типы колонок, значение корневого родителя и защищайтесь от циклических ссылок. На форму дерево выводится через ЗначениеВРеквизитФормы.

Частые вопросы

Можно ли заполнить дерево значений методом Загрузить, как таблицу?

Нет. У дерева значений нет метода Загрузить, принимающего таблицу значений, — структуры несовместимы. Перенос выполняется построчно: сначала копируются колонки, затем строки распределяются по ветвям дерева вручную или через выгрузку запроса с итогами.

Как заполнить дерево, если иерархия должна быть больше двух уровней, но колонки «Родитель» нет?

Используйте запрос с несколькими полями в предложении ИТОГИ ПО — каждое поле даст свой уровень вложенности. Например, ИТОГИ ПО Склад, НоменклатурнаяГруппа создаст трёхуровневое дерево: склады, группы, детальные строки.

Почему после Выгрузить(ПоГруппировкам) в строках групп пустые детальные поля?

Строки итогов содержат только значения полей группировки и агрегатные итоги; остальные поля в них не заполняются — это нормальное поведение платформы. Если в ветвях нужны дополнительные данные, дозаполните их обходом дерева после выгрузки.

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

Добавьте индекс по колонке, используемой в НайтиСтроки и Найти, а поиск ветвей в цикле замените соответствием «значение группы → строка дерева». Это убирает повторные полные переборы коллекции и даёт основной выигрыш на больших объёмах.

Чем дерево значений отличается от таблицы значений на форме?

Дерево значений хранит иерархические строки с вложенными уровнями и отображается как разворачивающийся список; таблица значений — плоский набор строк. На управляемой форме оба типа добавляются как реквизиты и заполняются через ЗначениеВРеквизитФормы, но элемент формы для дерева показывает уровни вложенности.

Как обойти все строки дерева, включая вложенные?

Используйте рекурсивную процедуру: для каждой строки коллекции Строки выполните обработку и вызовите ту же процедуру для её вложенной коллекции Строки. Альтернатива — метод НайтиСтроки с параметром Истина (поиск по всем уровням), если нужно отобрать строки по условию без явной рекурсии.