Разработка и Конфигурация · 12 мин
Как заполнить дерево значений из таблицы значений в 1С
Как заполнить дерево значений из таблицы значений в 1С: код загрузки, группировка по итогам, рекурсивное построение иерархии, типичные ошибки и готовые примеры.
Преобразование таблицы значений в дерево значений — одна из типовых задач разработчика 1С. Таблица удобна для хранения и обработки плоских данных, а дерево — для отображения иерархии: групп и элементов справочника, строк документа с подчинёнными строками, разделов отчёта с детализацией. Платформа предоставляет несколько способов такого преобразования, и выбор зависит от того, как устроены исходные данные.
Самый простой сценарий — когда в таблице есть колонка с группировкой (например, «НоменклатурнаяГруппа» или «Родитель»), и требуется свернуть строки в двухуровневое дерево. Для этого у объекта «ТаблицаЗначений» есть встроенный метод Скопировать() в сочетании со свёрткой либо прямой перенос строк с распределением по ветвям дерева. Более сложный случай — многоуровневая иерархия по ссылке на родителя, где нужен рекурсивный обход.
Ниже разобраны рабочие приёмы: группировка через запрос с итогами, ручное построение дерева циклом, рекурсивное заполнение по родителю, а также контроль результата и типичные ошибки, из-за которых дерево получается пустым, дублированным или «битым» по составу колонок.
Какой способ выбрать: запрос с итогами, цикл или рекурсия
Существует три практических подхода. Первый — получить дерево напрямую из запроса, минуя промежуточную таблицу: запрос с конструкцией ИТОГИ ПО выгружается методом Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам), и результатом сразу является дерево значений. Это оптимальный вариант, когда источник данных — база, а не готовая таблица в памяти.
Второй подход — ручное заполнение: создаётся пустое дерево значений, в него копируются колонки таблицы, затем строки распределяются по ветвям. Способ подходит, когда таблица уже сформирована (например, загружена из файла или получена из внешнего источника) и запросом её не получить. Третий подход — рекурсивное построение по ссылке на родителя, когда глубина иерархии заранее неизвестна.
Как выбрать действие
Подготовка: создаём дерево и переносим структуру колонок
Дерево значений создаётся конструктором Новый ДеревоЗначений. Чтобы строки таблицы можно было переносить без потери данных, у дерева должен быть тот же набор колонок, что у таблицы, плюс при необходимости служебные колонки (признак группы, картинка строки). Состав колонок копируется циклом по коллекции Таблица.Колонки.
Обратите внимание на типы: если колонка таблицы имеет составной тип, в дереве её лучше создавать с тем же описанием типов — иначе при присваивании значений возможно неявное приведение и потеря данных (например, обрезка строки до более короткой длины). Описание типов берётся из свойства ТипЗначения колонки таблицы.
Дерево = Новый ДеревоЗначений;
Для Каждого Колонка Из Таблица.Колонки Цикл
Дерево.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения, Колонка.Заголовок);
КонецЦикла;Если дерево будет выводиться на форму, заранее добавьте колонку для картинки строки и привяжите её к элементу формы через свойство «ПутьКДаннымКартинкиСтроки» — это избавит от доработок на этапе отображения.
Заполнение дерева из запроса с итогами
Самый короткий путь — поручить построение иерархии платформе. Запрос с предложением ИТОГИ ПО возвращает результат, который методом Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам) превращается в готовое дерево значений. Уровней итогов может быть несколько — каждый даст свой уровень ветвей.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Родитель КАК Группа,
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ИТОГИ ПО
| Группа";
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);У этого способа есть особенность: в строках итогов детальные поля (кроме полей группировки) содержат NULL или пустые значения, а в детальных строках поле группировки продублировано. Если нужна «чистая» структура, где группа хранит только себя, а товар — только себя, используйте иерархические итоги (ИТОГИ ПО Ссылка ТОЛЬКО ИЕРАРХИЯ для иерархических справочников) либо обрабатывайте ветви после выгрузки.
Ручное заполнение дерева из готовой таблицы значений
Когда таблица уже существует, а иерархия одноуровневая (группа → строки), дерево заполняется циклом: для каждой строки таблицы ищется ветвь первого уровня по значению группирующей колонки; если ветви нет — создаётся; затем в неё добавляется строка с данными.
- 1Создайте дерево значений и скопируйте в него колонки таблицы с сохранением типов
- 2Определите колонку группировки — по ней будут формироваться ветви первого уровня
- 3В цикле по строкам таблицы ищите ветвь через Строки.Найти(ЗначениеГруппы, ИмяКолонки)
- 4Если ветвь не найдена — добавьте её и заполните группирующую колонку
- 5Добавьте в найденную ветвь дочернюю строку и заполните её через ЗаполнитьЗначенияСвойств
- 6Проверьте итоговое количество строк и уровней дерева
Для Каждого СтрокаТЗ Из Таблица Цикл
Ветвь = Дерево.Строки.Найти(СтрокаТЗ.Группа, "Группа");
Если Ветвь = Неопределено Тогда
Ветвь = Дерево.Строки.Добавить();
Ветвь.Группа = СтрокаТЗ.Группа;
КонецЕсли;
НоваяСтрока = Ветвь.Строки.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
КонецЦикла;Метод ЗаполнитьЗначенияСвойств переносит только совпадающие по именам свойства, поэтому состав колонок дерева и таблицы должен совпадать. Если в таблице тысячи строк, поиск ветви методом Найти внутри цикла даст квадратичную сложность; для ускорения заведите соответствие, где ключ — значение группы, а значение — строка дерева.
Рекурсивное построение многоуровневой иерархии по родителю
Если в таблице есть ссылка на родителя (например, выгрузка иерархического справочника), и глубина вложенности произвольная, дерево строится рекурсивно. Идея: процедура получает текущего родителя и коллекцию строк, находит в таблице все строки с этим родителем, добавляет их в коллекцию и для каждой вызывает саму себя.
Процедура ДобавитьВДерево(СтрокиДерева, Таблица, Родитель)
Отбор = Новый Структура("Родитель", Родитель);
Найденные = Таблица.НайтиСтроки(Отбор);
Для Каждого СтрокаТЗ Из Найденные Цикл
НоваяСтрока = СтрокиДерева.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТЗ);
ДобавитьВДерево(НоваяСтрока.Строки, Таблица, СтрокаТЗ.Ссылка);
КонецЦикла;
КонецПроцедуры
// Вызов для верхнего уровня:
ДобавитьВДерево(Дерево.Строки, Таблица, ПустаяСсылкаРодителя);Критичный момент — корректное значение «корневого» родителя. В иерархических справочниках у элементов верхнего уровня родитель — пустая ссылка соответствующего типа. Если передать Неопределено вместо пустой ссылки, сравнение в отборе не сработает, и дерево останется пустым. Перед запуском проверьте, чем заполнена колонка «Родитель» у корневых строк.
Второй риск — зацикливание: если в данных есть ошибка и элемент ссылается сам на себя или образуется цикл по цепочке родителей, рекурсия уйдёт в бесконечный обход и завершится переполнением стека. Для защиты ведите соответствие уже обработанных ссылок и пропускайте повторы.
Проверка результата и вывод дерева на форму
После заполнения убедитесь, что дерево содержит ожидаемые данные. Базовые проверки: количество строк верхнего уровня, общее число строк с учётом вложенных, отсутствие потерянных строк таблицы. Сумму «утерянного» проще всего контролировать по счётчику: количество детальных строк дерева должно совпадать с количеством строк исходной таблицы (при одноуровневой схеме).
Для отображения на управляемой форме дерево значений добавляется как реквизит формы типа «ДеревоЗначений», а программное заполнение выполняется через преобразование: ЗначениеВРеквизитФормы(Дерево, "ИмяРеквизита"). Обратное чтение — РеквизитФормыВЗначение. Забытое преобразование — частая причина пустой таблицы на форме при заполненном дереве в коде.
Как проверить результат
Равно числу строк исходной таблицы
Меньше — часть строк потеряна при отборе или поиске ветвей
Содержат только значения групп или корневые элементы
Верхний уровень продублирован детальными строками
Дерево разворачивается, ветви раскрываются
Пусто — не выполнено ЗначениеВРеквизитФормы
Совпадают с типами таблицы
Строки обрезаны, ссылки заменены на строки
Типичные ошибки и ограничения
Наиболее частая ошибка — попытка присвоить таблицу значений переменной, объявленной как дерево, или наоборот. Это разные типы с несовместимой структурой: у дерева есть коллекция Строки с иерархией, у таблицы — плоская коллекция строк. Прямого присваивания между ними нет, перенос выполняется только построчно.
Вторая группа ошибок связана с поиском: метод Найти сравнивает значения по типу и значению, поэтому ссылка справочника не будет равна строке с её представлением, а число — строке с числом. Если группирующая колонка в таблице и в дереве имеет разные типы, поиск ветвей молча не сработает, и каждая строка создаст свою ветвь — получится дублирование групп.
По производительности: для таблиц в десятки тысяч строк добавьте индекс по колонке группировки (Таблица.Индексы.Добавить("Группа")) перед вызовом НайтиСтроки в рекурсии — это заметно ускоряет отбор. А если итоговая цель — только свернуть данные по группе без иерархии, возможно, достаточно метода таблицы Свернуть(), и дерево не понадобится вовсе.
Что важно запомнить
Дерево из запроса строится через ИТОГИ ПО и Выгрузить(ПоГруппировкам); из готовой таблицы — копированием колонок и распределением строк по ветвям циклом или рекурсией по родителю. Контролируйте типы колонок, значение корневого родителя и защищайтесь от циклических ссылок. На форму дерево выводится через ЗначениеВРеквизитФормы.
Частые вопросы
Можно ли заполнить дерево значений методом Загрузить, как таблицу?
Нет. У дерева значений нет метода Загрузить, принимающего таблицу значений, — структуры несовместимы. Перенос выполняется построчно: сначала копируются колонки, затем строки распределяются по ветвям дерева вручную или через выгрузку запроса с итогами.
Как заполнить дерево, если иерархия должна быть больше двух уровней, но колонки «Родитель» нет?
Используйте запрос с несколькими полями в предложении ИТОГИ ПО — каждое поле даст свой уровень вложенности. Например, ИТОГИ ПО Склад, НоменклатурнаяГруппа создаст трёхуровневое дерево: склады, группы, детальные строки.
Почему после Выгрузить(ПоГруппировкам) в строках групп пустые детальные поля?
Строки итогов содержат только значения полей группировки и агрегатные итоги; остальные поля в них не заполняются — это нормальное поведение платформы. Если в ветвях нужны дополнительные данные, дозаполните их обходом дерева после выгрузки.
Как ускорить заполнение дерева из большой таблицы значений?
Добавьте индекс по колонке, используемой в НайтиСтроки и Найти, а поиск ветвей в цикле замените соответствием «значение группы → строка дерева». Это убирает повторные полные переборы коллекции и даёт основной выигрыш на больших объёмах.
Чем дерево значений отличается от таблицы значений на форме?
Дерево значений хранит иерархические строки с вложенными уровнями и отображается как разворачивающийся список; таблица значений — плоский набор строк. На управляемой форме оба типа добавляются как реквизиты и заполняются через ЗначениеВРеквизитФормы, но элемент формы для дерева показывает уровни вложенности.
Как обойти все строки дерева, включая вложенные?
Используйте рекурсивную процедуру: для каждой строки коллекции Строки выполните обработку и вызовите ту же процедуру для её вложенной коллекции Строки. Альтернатива — метод НайтиСтроки с параметром Истина (поиск по всем уровням), если нужно отобрать строки по условию без явной рекурсии.