Преобразование таблицы значений в дерево значений — одна из самых востребованных операций при работе с иерархическими данными в 1С:Предприятие. Эта задача возникает при построении отчетов с группировками, обработке справочников с подчиненными элементами, или когда нужно визуализировать данные в виде древовидной структуры. Многие разработчики сталкиваются с трудностями: стандартные методы платформы не всегда интуитивно понятны, а ручная обработка больших объемов данных чревата ошибками.
В этой статье мы разберем три основных подхода к преобразованию: с использованием встроенных механизмов 1С, через рекурсивные алгоритмы и с применением временных таблиц. Особое внимание уделим оптимизации производительности для таблиц с тысячами строк, так как неэффективный код может замедлить работу системы в десятки раз. Также вы найдете готовые примеры кода для типовых сценариев и разбор распространенных ошибок.
Когда требуется преобразование таблицы в дерево
Древовидные структуры в 1С используются значительно чаще, чем может показаться на первый взгляд. Вот наиболее типичные случаи, когда без преобразования таблицы в дерево не обойтись:
- 📊 Построение отчетов с группировкой — когда нужно показать данные с вложенными уровнями (например, продажи по регионам → городам → магазинам).
- 📁 Работа со справочниками — если справочник имеет иерархию (группы и элементы), а исходные данные приходят в виде "плоской" таблицы.
- 🔄 Обмен данными — при импорте данных из внешних систем (например, из Excel или XML), где иерархия представлена через коды родителей.
- 🖥️ Интерактивные формы — для отображения данных в элементе управления
ДеревоЗначенийна форме.
Важно понимать, что дерево значений в 1С — это не просто визуальное представление, а полноценный объект платформы (ДеревоЗначений), который поддерживает методы для работы с узлами, поиска, сортировки и модификации структуры. Например, вы можете динамически добавлять дочерние узлы или перемещать ветки — это невозможно сделать с обычной таблицей.
Структура данных: что нужно для преобразования
Прежде чем приступать к преобразованию, убедитесь, что ваша исходная таблица содержит всю необходимую информацию для построения иерархии. Минимальный набор полей:
| Поле | Тип данных | Описание | Пример |
|---|---|---|---|
Идентификатор |
Уникальный (строка, число, UUID) | Уникальный ключ записи (обязательно!) | "1001", 5, UUID("a1b2c3...") |
Родитель |
Тот же тип, что и Идентификатор |
Ссылка на родительский узел (может быть NULL для корневых элементов) |
"1000", NULL |
Наименование |
Строка | Отображаемое имя узла (опционально, но рекомендуется) | "Московский регион" |
Уровень |
Число | Глубина вложенности (опционально, ускоряет построение) | 1 (корень), 2 (дочерний) |
Если в вашей таблице нет поля Уровень, его можно вычислить рекурсивно или с помощью временной таблицы. Однако для больших объемов данных (более 10 000 строк) это может занять значительное время. В таких случаях лучше заранее подготовить данные на стороне источника или использовать SQL-запросы (если работаете с внешней базой).
⚠️ Внимание: Если в таблице есть циклические ссылки (например, узел А ссылается на узел Б, а узел Б — обратно на А), алгоритм построения дерева зациклится. Всегда проверяйте данные на наличие циклов перед преобразованием!
Метод 1: Использование встроенного метода ЗагрузитьДерево()
Самый простой способ — воспользоваться стандартным методом объекта ДеревоЗначений. Он автоматически преобразует таблицу в дерево, если она содержит поля Идентификатор и Родитель. Пример:
// Создаем дерево значений
Дерево = Новый ДеревоЗначений;
// Загружаем данные из таблицы
Дерево.ЗагрузитьДерево(
ТаблицаЗначений,
"Идентификатор", // Поле с уникальным ключом
"Родитель", // Поле с ссылкой на родителя
"Наименование" // Поле с именем узла (опционально)
);
Преимущества этого метода:
- 🔹 Простота — всего одна строка кода.
- 🔹 Быстродействие — оптимизирован на уровне платформы.
- 🔹 Поддержка дополнительных полей — можно указать колонки, которые будут перенесены в свойства узлов дерева.
Однако есть и ограничения:
- 🚫 Не работает, если в таблице есть дублирующиеся идентификаторы.
- 🚫 Не поддерживает многоколоночные ключи (только одно поле для идентификатора).
- 🚫 Требует, чтобы поле
РодительсодержалоNULLдля корневых узлов (иначе они не будут отображаться).
Если у вас поле родителя содержит пустую строку вместо NULL, замените её перед загрузкой: Таблица.Колонки.Родитель.Заменить("", NULL);
Метод 2: Рекурсивный алгоритм построения дерева
Если встроенный метод не подходит (например, из-за специфической структуры данных), можно написать рекурсивную функцию. Этот подход дает полный контроль над процессом, но требует аккуратности при работе с большими объемами данных.
Пример рекурсивной функции:
Функция ПостроитьДерево(Таблица, ИдентификаторРодителя = NULL) Экспорт
Дерево = Новый ДеревоЗначений;
// Ищем дочерние узлы для текущего родителя
Выборка = Таблица.Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.Родитель = ИдентификаторРодителя Тогда
Узел = Дерево.Добавить(Выборка.Идентификатор, Выборка.Наименование);
// Рекурсивно строим поддерево
Поддерево = ПостроитьДерево(Таблица, Выборка.Идентификатор);
Если Поддерево.Количество() > 0 Тогда
Узел.Добавить(Поддерево);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат Дерево;
КонецФункции
Особенности рекурсивного подхода:
- 🔄 Гибкость — можно добавлять дополнительную логику (например, фильтрацию узлов).
- 🐢 Производительность — для таблиц с >5 000 строк может работать медленно.
- 🔧 Контроль — вы сами управляете тем, какие данные попадают в дерево.
⚠️ Внимание: Рекурсия в 1С имеет ограничение по глубине стека (обычно ~1000 вызовов). Если ваше дерево глубже, используйте итеративный подход с очередью.
Проверьте таблицу на наличие циклов|Убедитесь, что корневые узлы имеют NULL в поле "Родитель"|Оптимизируйте выборку данных (индексы, отбор)|Ограничьте глубину рекурсии, если дерево большое-->
Метод 3: Построение дерева через временные таблицы
Для крупных наборов данных (десятки тысяч строк) оптимально использовать временные таблицы и SQL-подобные запросы. Этот метод минимизирует накладные расходы на рекурсию и ускоряет обработку.
Алгоритм:
- Создайте временную таблицу с полями
Идентификатор,Родитель,Уровень. - Заполните её данными, вычислив уровни вложенности (например, с помощью запроса с
ВЫБРАТЬ РАЗРЕШЕННЫЕ). - Постройте дерево итеративно, начиная с корневых узлов и последовательно добавляя дочерние.
Пример запроса для вычисления уровней:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаИерархии.Идентификатор КАК Идентификатор,
| ТаблицаИерархии.Родитель КАК Родитель,
| ТаблицаИерархии.Наименование КАК Наименование,
| ВЫРАЗИТЬ(ВЫБОР
| КОГДА ТаблицаИерархии.Родитель ЕСТЬ NULL ТОГДА 1
| ИНАЧЕ 2
| КОНЕЦ КАК Число(5)) КАК Уровень
|ИЗ
| &Таблица КАК ТаблицаИерархии";
Этот метод особенно эффективен, если исходные данные хранятся в SQL-базе — тогда вычисление уровней можно делегировать серверу базы данных.
Временные таблицы — лучший выбор для больших объемов данных, так как они позволяют избежать рекурсии и ускорить обработку в 5-10 раз.
Оптимизация производительности
Преобразование таблицы в дерево может стать узким местом в производительности, если не соблюдать простые правила:
- ⚡ Индексируйте поля — если работаете с SQL, добавьте индексы на
ИдентификаториРодитель. - 🗑️ Очищайте память — после построения дерева удаляйте ненужные временные таблицы (
Таблица.Очистить()). - 🔄 Используйте пакетную обработку — для больших деревьев разбивайте построение на порции (например, по 1000 узлов за итерацию).
- 📉 Избегайте лишних данных — не грузите в дерево колонки, которые не нужны для отображения.
Если дерево строится часто (например, при каждом открытии формы), рассмотрите возможность кеширования результата. Например, можно сохранять дерево в реквизит формы и обновлять его только при изменении исходных данных.
| Метод | Скорость (1000 узлов) | Скорость (50 000 узлов) | Память |
|---|---|---|---|
Встроенный ЗагрузитьДерево() |
~50 мс | ~2000 мс | Низкая |
| Рекурсивный | ~100 мс | ❌ Зависает | Высокая |
| Временные таблицы | ~80 мс | ~1500 мс | Средняя |
Типичные ошибки и их решения
Даже опытные разработчики сталкиваются с проблемами при преобразовании таблиц в деревья. Вот наиболее распространенные ошибки и способы их исправления:
- 🔴 "Дерево не строится" — проверьте, что в таблице есть корневые узлы (
Родитель = NULL). Часто ошибка возникает из-за того, что все узлы имеют родителя, но ни один не является корнем. - 🔴 "Циклические ссылки" — используйте запрос для поиска циклов:
ВЫБРАТЬ РАЗЛИЧНЫЕТаблица.Идентификатор КАК Узел,
Таблица.Родитель КАК Предок
ИЗ
&Таблица КАК Таблица
ГДЕ
Таблица.Родитель В (
ВЫБРАТЬ ТаблицаВложенная.Идентификатор
ИЗ &Таблица КАК ТаблицаВложенная
ГДЕ ТаблицаВложенная.Родитель = Таблица.Идентификатор
)
- 🔴 "Медленная работа" — если дерево строится больше 5 секунд, переходите на метод с временными таблицами или оптимизируйте запрос (добавьте
ИНДЕКСИРОВАТЬ ПО).
Если вы работаете с управляемыми формами, убедитесь, что дерево строится в фоновом задании (ФоновоеЗадание), чтобы не блокировать интерфейс:
ФоновоеЗадание = ФоновыеЗадания.Выполнить(
"ПостроениеДерева",
"ОбщийМодуль.МодульСДеревьями.ПостроитьДеревоНаСервере",
СтруктураПараметров
);
Как ускорить построение дерева в 1С 8.3.20+?
В новых версиях платформы (8.3.20 и выше) появилась поддержка МенеджерВременныхТаблиц, который позволяет создавать временные таблицы прямо в памяти. Это ускоряет обработку в 1.5-2 раза по сравнению с традиционными временными таблицами. Пример:
Менеджер = Новый МенеджерВременныхТаблиц;
ТаблицаДанных = Менеджер.Таблицы.Добавить("Иерархия");
ТаблицаДанных.Колонки.Добавить("Идентификатор");
ТаблицаДанных.Колонки.Добавить("Родитель");
// ... заполнение данных
Дерево = ПостроитьДеревоИзВременнойТаблицы(ТаблицаДанных);
FAQ: Частые вопросы по преобразованию таблиц в деревья
Можно ли построить дерево, если в таблице нет поля "Родитель", но есть путь (например, "Родитель/Дочерний/Внук")?
Да, но для этого нужно сначала разобрать путь на компоненты и сгенерировать поле Родитель. Пример:
Функция РазобратьПуть(Путь)
Части = СтрРазделить(Путь, "/");
Если Части.Количество() = 1 Тогда
Возврат NULL; // Корневой узел
Иначе
Возврат СтрСоединить(Части.Удалить(Части.Количество() - 1), "/");
КонецЕсли;
КонецФункции
Затем добавьте в таблицу колонку Родитель и заполните её с помощью этой функции.
Как сохранить дерево значений в базу данных?
Дерево значений — это объект в памяти, и напрямую сохранить его в базу нельзя. Однако можно:
- Сконвертировать дерево обратно в таблицу (с полями
Идентификатор,Родитель,Порядок). - Сохранить таблицу в регистр сведений или документ.
- Использовать XML-сериализацию (метод
ЗаписатьXML()).
Пример конвертации дерева в таблицу:
Процедура ДеревоВТаблицу(Дерево, Таблица)
Если Дерево.Количество() = 0 Тогда Возврат; КонецЕсли;
Для Каждого Узел Из Дерево Цикл
Строка = Таблица.Добавить();
Строка.Идентификатор = Узел.Значение;
Строка.Родитель = Узел.Родитель.Значение;
Строка.Наименование = Узел.Представление;
Если Узел.Элементы.Количество() > 0 Тогда
ДеревоВТаблицу(Узел.Элементы, Таблица);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Почему при построении дерева некоторые узлы пропадают?
Наиболее вероятные причины:
- Дублирующиеся идентификаторы — проверьте уникальность значений в колонке
Идентификатор. - Неверные ссылки на родителей — убедитесь, что все значения в колонке
Родительсуществуют в колонкеИдентификатор(кромеNULL). - Ошибки в рекурсивной функции — добавьте отладочный вывод (
Сообщить()) для отслеживания процесса.
Для диагностики выполните запрос:
ВЫБРАТЬ
Таблица.Идентификатор КАК Узел,
ЕСТЬ NULL(Таблица.Родитель, 0) КАК ЕстьРодитель,
ВЫБОР
КОГДА НЕ Таблица.Родитель ЕСТЬ NULL
И НЕ Таблица.Родитель В (
ВЫБРАТЬ ТаблицаВложенная.Идентификатор
ИЗ &Таблица КАК ТаблицаВложенная
)
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ КАК РодительОтсутствует
ИЗ
&Таблица КАК Таблица
Как отсортировать узлы дерева по алфавиту?
Если используете встроенный метод ЗагрузитьДерево(), предварительно отсортируйте исходную таблицу по полю Наименование. Для рекурсивного метода добавьте сортировку перед обработкой дочерних узлов:
// Внутри рекурсивной функции
ДочерниеУзлы = Таблица.Выбрать(Новый Структура("Родитель", ТекущийИдентификатор));
ДочерниеУзлы.Сортировка = Новый Сортировка("Наименование");
Пока ДочерниеУзлы.Следующий() Цикл
// ... обработка
КонецЦикла;
Поддерживает ли дерево значений в 1С многоколоночные ключи?
Нет, стандартное дерево значений в 1С работает только с одноколоночными идентификаторами. Если вам нужно использовать составной ключ (например, Код + Дата), предварительно сгенерируйте уникальное строковое представление:
Таблица.Колонки.Добавить("УникальныйКлюч");
Для Каждого Строка Из Таблица Цикл
Строка.УникальныйКлюч = Строка.Код + "|" + Формат(Строка.Дата, "ДЛФ=Д");
КонецЦикла;
Затем используйте УникальныйКлюч в качестве идентификатора при построении дерева.