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

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

Что такое дерево значений в 1С и как с ним работать

Дерево значений в 1С: что это за объект, чем отличается от таблицы значений, как создать колонки, добавить строки, обойти ветки и вывести на форму.

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

Объект создаётся программно через конструктор Новый ДеревоЗначений, наполняется колонками и строками, после чего его можно обходить рекурсивно, сортировать по ветвям, искать значения и выводить на форму в элемент «Дерево» или в табличный документ. Дерево значений не сохраняется в базе данных само по себе — это временная структура в памяти, которую заполняют из запроса, справочника или другого источника и используют в рамках одного вызова кода.

Материал рассчитан на начинающих разработчиков 1С и продвинутых пользователей, которые настраивают расширения или внешние обработки. Разобраны создание дерева, добавление веток, обход, поиск, сортировка, вывод на форму и типичные ошибки, из-за которых код работает медленно или падает с исключением.

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

Таблица значений — плоская структура: набор колонок и строк одного уровня. Дерево значений добавляет к этому понятие вложенности: у каждой строки есть коллекция подчинённых строк, доступная через свойство Строки, и ссылка на родителя через свойство Родитель. У строк верхнего уровня родитель не заполнен, уровень строки возвращает метод Уровень() (0 — корневые строки).

Колонки у дерева общие для всех уровней: нельзя задать разный набор полей для родителей и потомков. Если на разных уровнях нужны разные данные, неиспользуемые колонки просто остаются пустыми. Типы колонок задаются явно через объект ОписаниеТипов либо определяются автоматически при первом присваивании значения, если колонка создана без типа.

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

Как создать дерево значений и описать колонки

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

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

Дерево.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)));

Дерево.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

Дерево.Колонки.Добавить("Ссылка"); // составной тип, определится при присваивании

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

Ветка.Наименование = "Продажи";

Ветка.Сумма = 1000;

Подстрока = Ветка.Строки.Добавить();

Подстрока.Наименование = "Розничные продажи";

Подстрока.Сумма = 600;

Обратите внимание: строки добавляются не в само дерево напрямую, а в коллекцию Строки — у корня дерева или у конкретной строки-родителя. Именно через эту вложенность и строится иерархия. Метод Добавить() возвращает созданную строку, что позволяет сразу заполнить её поля и добавить потомков.

Как заполнить дерево из запроса

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

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

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

"ВЫБРАТЬ

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

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

|ИЗ

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

|ИТОГИ ПО

| Ссылка ИЕРАРХИЯ";

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

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

Как обойти дерево значений

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

Процедура ОбойтиДерево(Строки, Уровень = 0)

Для Каждого Строка Из Строки Цикл

// обработка строки

ОбойтиДерево(Строка.Строки, Уровень + 1);

КонецЦикла;

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

Для поиска конкретных значений рекурсия не обязательна: метод Строки.Найти(Значение, "ИмяКолонки", Истина) с параметром «Истина» ищет по всем уровням вложенности. Метод НайтиСтроки работает только в пределах одной коллекции, поэтому для поиска по всему дереву его вызывают рекурсивно или используют Найти с признаком поиска в подчинённых.

Порядок обхода дерева
  1. 1
    Создайте экспортную процедуру, принимающую коллекцию строк
  2. 2
    В цикле Для Каждого обработайте текущую строку
  3. 3
    Вызовите процедуру рекурсивно для Строка.Строки
  4. 4
    Передайте уровень параметром, если нужна глубина или отступы
  5. 5
    Проверьте результат на тестовом дереве с 3+ уровнями вложенности

Как вывести дерево значений на форму

На управляемой форме дерево показывают через реквизит формы типа «ДеревоЗначений» и элемент формы «Таблица» с отображением «Дерево». Дерево значений — несериализуемый тип, поэтому при передаче между сервером и клиентом его преобразуют: либо реквизит объявлен заранее и заполняется в серверной процедуре, либо используют ЗначениеВДанныеФормы для программно созданного реквизита.

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

Сортировка, итоги и типичные операции

Метод Строки.Сортировать("Сумма Убыв", Истина) сортирует строки; второй параметр «Истина» включает сортировку по всем подчинённым уровням. Без него отсортируется только текущая коллекция, и внутри веток порядок останется исходным — частая причина «неправильной» с виду сортировки.

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

ЗадачаМетодОсобенность
Найти строку по значениюСтроки.Найти(значение, колонка, Истина)Ищет по всем уровням при третьем параметре Истина
Отсортировать веткиСтроки.Сортировать("Колонка", Истина)Без Истина сортируется только текущий уровень
Скопировать веткуСтроки.Добавить() + СкопироватьПодчиненныеСтрокиИспользуется метод Скопировать() у коллекции строк
Удалить строкуСтроки.Удалить(строка)Удаляются и все подчинённые строки ветки

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

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

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

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

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

Когда дерево значений не подходит

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

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

Как выбрать структуру данных

Данные иерархичны и нужно показать ветки на формеИспользуйте дерево значений с реквизитом формы типа ДеревоЗначений
Данные плоские, нужна только фильтрация и итогиИспользуйте таблицу значений
Нужен мгновенный поиск по уникальному ключуИспользуйте соответствие, а дерево стройте только для отображения
Иерархию нужно хранить постоянноИспользуйте иерархический справочник, а дерево — только как временное представление
Итог

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

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

Частые вопросы о дереве значений

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

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

Можно ли сохранить дерево значений в базе данных?

Напрямую — нет, это временный объект в памяти. Для постоянного хранения иерархии используют иерархический справочник или план видов характеристик, а дерево строят из них при необходимости. Само дерево можно сериализовать в хранилище значения, но это редкий и неудобный приём.

Как найти строку на любом уровне дерева?

Вызовите метод Найти у коллекции строк корня с третьим параметром Истина: Дерево.Строки.Найти(ИскомоеЗначение, "ИмяКолонки", Истина). Поиск пройдёт по всем уровням вложенности. Без третьего параметра ищутся только строки верхнего уровня.

Почему сортировка дерева работает неправильно?

Чаще всего не указан второй параметр метода Сортировать. Вызов Строки.Сортировать("Колонка") сортирует только текущий уровень, внутри веток порядок не меняется. Добавьте Истина вторым аргументом, чтобы сортировка применилась ко всем подчинённым уровням.

Как вывести дерево значений на управляемую форму?

Добавьте реквизит формы типа ДеревоЗначений с нужными колонками и элемент «Таблица» с отображением «Дерево». В серверной процедуре заполните локальную переменную-дерево и скопируйте её в реквизит методом ЗначениеВРеквизитФормы. Передавать дерево между клиентом и сервером напрямую нельзя.

Есть ли ограничение на глубину дерева значений?

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