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

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

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

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

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

Типовой сценарий выглядит так: данные получены запросом или загружены из файла в виде плоской таблицы, где у каждой строки есть колонка «Родитель» (ссылка, код или другой идентификатор). Требуется показать эти данные в форме как иерархический список или выгрузить в отчёт с группировками. Ниже разобраны рабочие приёмы с готовым кодом.

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

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

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

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

Условия, без которых преобразование не сработает

Прежде чем писать код, убедитесь, что исходные данные подходят для построения иерархии. Нарушение этих условий — самая частая причина «пустого» или «сломанного» дерева.

  • В таблице есть колонка-идентификатор строки (например, «Ссылка» или «Код») и колонка со ссылкой на родителя (например, «Родитель»).
  • Значения родителя ссылаются на существующие строки той же таблицы, иначе появятся «потерянные» строки, которые не попадут в дерево.
  • Нет циклических ссылок: строка не может быть родителем самой себя прямо или через цепочку. Цикл приведёт к бесконечной рекурсии.
  • Типы колонок идентификатора и родителя совместимы для сравнения — иначе поиск родителя будет молча возвращать «не найдено».

Способ 1: рекурсивное построение по полю «Родитель»

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

Функция ТаблицуВДерево(ТаблицаЗначений) Экспорт

Дерево = Новый ДеревоЗначений;

Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл

Дерево.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения);

КонецЦикла;

ТаблицаЗначений.Индексы.Добавить("Родитель");

ДобавитьСтрокиРекурсивно(Дерево.Строки, ТаблицаЗначений,

Неопределено);

Возврат Дерево;

КонецФункции

Процедура ДобавитьСтрокиРекурсивно(СтрокиДерева, ТаблицаЗначений, Родитель)

Отбор = Новый Структура("Родитель", Родитель);

НайденныеСтроки = ТаблицаЗначений.НайтиСтроки(Отбор);

Для Каждого СтрокаТаблицы Из НайденныеСтроки Цикл

НоваяСтрока = СтрокиДерева.Добавить();

ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТаблицы);

ДобавитьСтрокиРекурсивно(НоваяСтрока.Строки, ТаблицаЗначений,

СтрокаТаблицы.Ссылка);

КонецЦикла;

КонецПроцедуры

Обратите внимание на два момента. Во-первых, для корневых строк значение родителя должно совпадать с тем, что передаётся на первом вызове (в примере — Неопределено; если у вас пустые ссылки, передавайте пустую ссылку соответствующего типа). Во-вторых, метод ЗаполнитьЗначенияСвойств копирует значения по совпадающим именам колонок, поэтому имена колонок дерева должны совпадать с именами таблицы.

1c
ТаблицаЗначений.Индексы.Добавить("Родитель"); // ускоряет НайтиСтроки в десятки раз на больших таблицах

Способ 2: запрос с итогами по иерархии

Если данные ещё не выгружены в таблицу, а лежат в базе, дерево часто проще получить напрямую запросом. Конструкция ИТОГИ ПО с указанием ТОЛЬКО ИЕРАРХИЯ или обычных группировок позволяет обойти результат с типом обхода ПоГруппировкамСИерархией и построить дерево без ручной рекурсии.

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Номенклатура.Ссылка КАК Номенклатура,

| Номенклатура.ЭтоГруппа КАК ЭтоГруппа

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ИТОГИ ПО

| Номенклатура ТОЛЬКО ИЕРАРХИЯ";

РезультатЗапроса = Запрос.Выполнить();

Дерево = Новый ДеревоЗначений;

Дерево.Колонки.Добавить("Номенклатура");

Дерево.Колонки.Добавить("ЭтоГруппа");

Выборка = РезультатЗапроса.Выбрать(

ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

ЗаполнитьДеревоИзВыборки(Выборка, Дерево.Строки);

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

Способ 3: группировка плоской таблицы по уровням

Иногда иерархии в данных нет, но требуется сгруппировать строки по значениям колонок — например, превратить таблицу «Склад, Товар, Количество» в дерево «Склад → Товары». Здесь помогает свёртка и последовательное построение уровней.

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

Сравнение способов

Рекурсия по полю «Родитель»

Универсален для таблиц любого происхождения, включая загруженные из файлов. Требует контроля циклов и корректных типов колонок.

Запрос с ИТОГИ ПО

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

Группировка по колонкам

Простой код для 2–3 уровней по значениям реквизитов. Не работает для произвольной глубины иерархии.

Как проверить результат и избежать типичных ошибок

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

Для отладки выведите дерево в табличное поле на форме или пройдите по нему рекурсивно с подсчётом. Если дерево отображается плоским списком — проверьте, что дочерние строки добавлялись именно в НоваяСтрока.Строки, а не в корневую коллекцию дерева.

Типичные ошибки
01
Добавление всех строк в корень дерева вместо вложенных коллекций — иерархия теряется, дерево выглядит как таблица
02
Сравнение ссылок разных типов или ссылки со строкой — НайтиСтроки не находит родителя, строки пропадают
03
Отсутствие индекса по колонке «Родитель» — на таблицах в десятки тысяч строк построение занимает минуты вместо секунд
04
Игнорирование циклических ссылок — рекурсия уходит в бесконечный цикл и сеанс аварийно завершается
Контрольные точки

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

Количество строк
Норма

Сумма строк дерева на всех уровнях равна числу строк таблицы

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

Строк меньше — есть потерянные записи без найденного родителя

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

У групп показаны значки разворачивания, вложенность видна

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

Все строки на одном уровне — заполнение шло в корень

Время построения
Норма

Тысячи строк обрабатываются за секунды

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

Заметные паузы — не добавлен индекс или лишние полные обходы

Ограничения и практические замечания

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

Ещё одно ограничение касается типов колонок: если в колонке «Родитель» смешаны пустые ссылки разных типов, сравнение с Неопределено не сработает. Надёжнее привести пустые значения к единому виду ещё на этапе заполнения таблицы или проверять родителя через ЗначениеЗаполнено().

Итог

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

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

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

Есть ли в 1С готовый метод «ТаблицаВДерево»?

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

Что делать, если у корневых строк родитель — пустая ссылка, а не Неопределено?

Передайте в первый вызов рекурсии именно пустую ссылку нужного типа, например Справочники.Номенклатура.ПустаяСсылка(). Тип и значение отбора должны точно совпадать со значениями в колонке «Родитель», иначе корневые строки не будут найдены.

Как построить дерево из таблицы, загруженной из Excel?

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

Почему НайтиСтроки не находит дочерние строки, хотя родитель есть?

Чаще всего причина в несовпадении типов: например, в колонке идентификатора строка, а в колонке родителя — число, или ссылки разных видов справочников. Проверьте типы через ТипЗнч() и при необходимости приведите значения к одному типу.

Можно ли отсортировать дерево по нескольким колонкам?

Да, метод Дерево.Строки.Сортировать("Колонка1 Возр, Колонка2 Убыв", Истина) принимает список колонок с направлением, а второй параметр включает сортировку всех вложенных уровней.

Что быстрее: рекурсия по таблице или запрос с итогами?

Для данных из базы запрос с ИТОГИ ПО обычно быстрее, так как иерархию строит СУБД. Рекурсия по таблице выигрывает, когда данные уже в памяти и повторное обращение к базе не нужно.