Дерево значений 1С: создание, заполнение, обход и методы

Дата: 21.09.2026 08:57:33
Дерево значений
Универсальные коллекции
Рекурсия
Запросы
Управляемые формы
Комментарий:
0
Платформа:
8.3
Конфигурация:
Любая
Формы:
Любые

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

С деревом сталкиваются в нескольких типовых задачах:

  • отчёт или обработка показывает данные с группировками: контрагент → документы, статья → подстатьи;
  • нужно воспроизвести иерархию справочника или многоуровневую спецификацию изделия;
  • результат запроса с разделом ИТОГИ выгружают сразу в иерархию;
  • на форме пользователь отмечает флажками группы и элементы.

Объект ДеревоЗначений доступен на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте такого типа нет: там с деревом работают через реквизит формы, об этом — в конце статьи.

Дерево значений 1С 8.3 и таблица значений: в чём разница

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

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

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

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

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

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

Дерево.Колонки.Добавить("Код",
	Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20)), "Код");
Дерево.Колонки.Добавить("Наименование",
	Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)), "Статья");
Дерево.Колонки.Добавить("Сумма",
	Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "Сумма");
Дерево.Колонки.Добавить("Объект"); // без типа: примет значение любого типа

// Проверка, что колонка есть, перед обращением к ней
ЕстьКолонкаСумма = Дерево.Колонки.Найти("Сумма") <> Неопределено;

Если группам и элементам нужны разные поля, заводят общий набор колонок, а лишние оставляют пустыми.

Тип указывать не обязательно, но лучше указать. Типизированная колонка приводит значение при присваивании: в числовую колонку не попадёт строка, а в новой строке там будет 0, а не Неопределено. От этого зависят Итог() и сортировка. Имя колонки подчиняется тем же правилам, что имя переменной: буквы, цифры и подчёркивание, без пробелов, первой не может быть цифра.

Как добавить строки: корневые и подчинённые

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

Доходы = Дерево.Строки.Добавить();
Доходы.Код = "1";
Доходы.Наименование = "Доходы";

Строка = Доходы.Строки.Добавить();
Строка.Код = "1.1";
Строка.Наименование = "Выручка от продаж";
Строка.Сумма = 150000;

Строка = Доходы.Строки.Добавить();
Строка.Код = "1.2";
Строка.Наименование = "Прочие доходы";
Строка.Сумма = 12000;

// Сумма группы = сумма её строк (только ближайший уровень)
Доходы.Сумма = Доходы.Строки.Итог("Сумма");

// Новая строка первой в верхнем уровне
Расходы = Дерево.Строки.Вставить(0);
Расходы.Наименование = "Расходы";

К значению колонки обращаются через точку (Строка.Сумма), по имени в квадратных скобках (Строка["Сумма"]) или по индексу колонки (Строка[2]). Скобки нужны, когда имя колонки хранится в переменной. Кроме колонок, у строки есть служебные свойства и методы:

  • Строка.Строки — коллекция подчинённых строк;
  • Строка.Родитель — вышестоящая строка, у строк верхнего уровня — Неопределено;
  • Строка.Уровень() — номер уровня, у верхнего — 0;
  • Строка.Владелец() — само дерево.

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

Частая практическая задача: данные приходят плоским списком, где у записи есть свой код и код родителя, — из внешней системы, файла или таблицы значений. Каждую запись нужно положить под её родителя, при этом родитель может встретиться в списке позже потомка. Для этого удобно держать соответствие «код → строка дерева» и создавать цепочку родителей рекурсивно. На входе — массив структур с ключами Код, КодРодителя, Наименование, Сумма; функция НовоеДеревоСтатей() создаёт пустое дерево с колонками, как в первом примере (полный модуль — в конце статьи).

Функция ДеревоИзСписка(Список) Экспорт

	Дерево = НовоеДеревоСтатей();

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

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

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

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

Функция СтрокаДляКода(Дерево, Код, ДанныеПоКоду, СтрокиПоКоду)

	Строка = СтрокиПоКоду.Получить(Код);
	Если Строка = Null Тогда
		ВызватьИсключение "Цикл в иерархии: элемент с кодом " + Код + " оказался предком самого себя";
	ИначеЕсли Строка <> Неопределено Тогда
		Возврат Строка;
	КонецЕсли;

	СтрокиПоКоду.Вставить(Код, Null); // метка: строка сейчас создаётся

	Запись = ДанныеПоКоду.Получить(Код);
	СтрокаРодителя = Неопределено;
	Если ЗначениеЗаполнено(Запись.КодРодителя)
		И ДанныеПоКоду.Получить(Запись.КодРодителя) <> Неопределено Тогда
		СтрокаРодителя = СтрокаДляКода(Дерево, Запись.КодРодителя, ДанныеПоКоду, СтрокиПоКоду);
	КонецЕсли;

	Если СтрокаРодителя = Неопределено Тогда
		Строка = Дерево.Строки.Добавить();
	Иначе
		Строка = СтрокаРодителя.Строки.Добавить();
	КонецЕсли;
	ЗаполнитьЗначенияСвойств(Строка, Запись);

	СтрокиПоКоду.Вставить(Код, Строка);
	Возврат Строка;

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

Что здесь важно:

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

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

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

// Сервер: в тонком клиенте и веб-клиенте объект Запрос недоступен.
Функция ПродажиПоКонтрагентам(НачалоПериода, КонецПериода) Экспорт

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

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

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

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

Для иерархического справочника добавьте в итоги слово ИЕРАРХИЯ (ПО Номенклатура ИЕРАРХИЯ) и выгружайте с параметром ОбходРезультатаЗапроса.ПоГруппировкамСИерархией: группы справочника станут отдельными уровнями дерева.

Обход дерева значений: цикл и рекурсия

Для Каждого Строка Из Дерево.Строки перебирает только верхний уровень. Чтобы пройти все строки, процедуру вызывают рекурсивно для коллекции Строки каждой строки. Пример — пересчёт сумм групп снизу вверх: функция возвращает сумму коллекции, а строка-группа получает сумму своих подчинённых.

Функция ПосчитатьИтоги(КоллекцияСтрок) Экспорт

	Всего = 0;
	Для Каждого Строка Из КоллекцияСтрок Цикл
		Если Строка.Строки.Количество() > 0 Тогда
			Строка.Сумма = ПосчитатьИтоги(Строка.Строки);
		КонецЕсли;
		Всего = Всего + Строка.Сумма;
	КонецЦикла;

	Возврат Всего;

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

// Вызов: ВсегоПоДереву = ПосчитатьИтоги(Дерево.Строки);

Групповые строки перезаписываются, листовые остаются как были. Второй типовой обход — развернуть дерево в таблицу значений с колонкой уровня:

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

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

	ДобавитьСтрокиВТаблицу(Дерево.Строки, Таблица);

	Возврат Таблица;

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

Процедура ДобавитьСтрокиВТаблицу(КоллекцияСтрок, Таблица)

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

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

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

Стек = Новый Массив;
Стек.Добавить(Дерево.Строки);
ВсегоСтрок = 0;

Пока Стек.Количество() > 0 Цикл
	Коллекция = Стек[Стек.ВГраница()];
	Стек.Удалить(Стек.ВГраница());
	Для Каждого Строка Из Коллекция Цикл
		ВсегоСтрок = ВсегоСтрок + 1;
		Если Строка.Строки.Количество() > 0 Тогда
			Стек.Добавить(Строка.Строки);
		КонецЕсли;
	КонецЦикла;
КонецЦикла;

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

Методы коллекции строк: поиск, сортировка, итоги

Основные методы вызываются не у самого дерева, а у коллекции строк — Дерево.Строки или Строка.Строки. По умолчанию они работают только на своём уровне.

МетодЧто делает
Добавить(), Вставить(Индекс)новая строка в конце или на заданной позиции
Найти(Значение, Колонки, ВключатьПодчиненные)первая строка с таким значением или Неопределено
НайтиСтроки(Отбор, ВключатьПодчиненные)массив строк, подходящих под структуру отбора
Сортировать(Колонки, ВключатьПодчиненные, ОбъектСравнения)сортировка уровня или всех уровней
Итог(Колонка, ВключатьПодчиненные)сумма числовых значений колонки
Количество(), Индекс(Строка)число строк на уровне, позиция строки
Удалить(Строка), Очистить()удаление строки вместе с подчинёнными, очистка уровня
Сдвинуть(Строка, Смещение)перемещение строки внутри уровня
ВыгрузитьКолонку(Колонка)массив значений колонки по строкам уровня
// Поиск по всем уровням: третий параметр Истина
Строка = Дерево.Строки.Найти("1.2", "Код", Истина);
Если Строка <> Неопределено Тогда
	УровеньНайденной = Строка.Уровень();
КонецЕсли;

// Все строки с нулевой суммой на любом уровне
НулевыеСтроки = Дерево.Строки.НайтиСтроки(Новый Структура("Сумма", 0), Истина);

// Каждый уровень: по убыванию суммы, затем по наименованию
Дерево.Строки.Сортировать("Сумма Убыв, Наименование", Истина);

// Итог верхнего уровня и итог по всем строкам всех уровней
ИтогВерхнегоУровня = Дерево.Строки.Итог("Сумма");
ИтогВсехСтрок = Дерево.Строки.Итог("Сумма", Истина);

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

Удалять строки внутри Для Каждого нельзя: коллекция сдвигается, и часть строк пропускается. Обходите уровень с конца по индексу:

Процедура УдалитьНулевыеСтроки(КоллекцияСтрок) Экспорт

	Индекс = КоллекцияСтрок.Количество() - 1;
	Пока Индекс >= 0 Цикл
		Строка = КоллекцияСтрок[Индекс];
		УдалитьНулевыеСтроки(Строка.Строки);
		Если Строка.Строки.Количество() = 0 И Строка.Сумма = 0 Тогда
			КоллекцияСтрок.Удалить(Строка);
		КонецЕсли;
		Индекс = Индекс - 1;
	КонецЦикла;

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

Сначала чистятся вложенные уровни, потом проверяется сама строка, поэтому группа, у которой не осталось подчинённых, тоже удаляется.

Дерево значений на управляемой форме

На форме создают реквизит типа ДеревоЗначений, задают ему колонки и перетаскивают в элементы — получается таблица формы с отображением «Дерево». В модуле формы и на клиенте, и в серверных процедурах реквизит — не дерево значений, а ДанныеФормыДерево: вместо коллекции Строки у него метод ПолучитьЭлементы(), вместо свойства Родитель — метод ПолучитьРодителя(). Поиска по значениям, итогов и сортировки там нет; есть только НайтиПоИдентификатору(). Поэтому на сервере реквизит преобразуют в настоящее дерево через РеквизитФормыВЗначение(), обрабатывают и возвращают через ЗначениеВРеквизитФормы().

// Модуль формы. У реквизита ДеревоСтатей (тип ДеревоЗначений)
// в редакторе формы созданы колонки Наименование и Сумма.

&НаСервере
Процедура ЗаполнитьДеревоНаСервере()

	Дерево = РеквизитФормыВЗначение("ДеревоСтатей");
	Дерево.Строки.Очистить();

	Доходы = Дерево.Строки.Добавить();
	Доходы.Наименование = "Доходы";
	Строка = Доходы.Строки.Добавить();
	Строка.Наименование = "Выручка от продаж";
	Строка.Сумма = 150000;
	Доходы.Сумма = Доходы.Строки.Итог("Сумма");

	ЗначениеВРеквизитФормы(Дерево, "ДеревоСтатей");

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

&НаКлиенте
Процедура Заполнить(Команда)

	ЗаполнитьДеревоНаСервере();
	Для Каждого Элемент Из ДеревоСтатей.ПолучитьЭлементы() Цикл
		Элементы.ДеревоСтатей.Развернуть(Элемент.ПолучитьИдентификатор(), Истина);
	КонецЦикла;

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

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

Типичные ошибки

  • «Тип не определен (ДеревоЗначений)». Новый ДеревоЗначений написан в процедуре с директивой &НаКлиенте. Перенесите код в серверную процедуру.
  • «Поле объекта не обнаружено (Сума)». Опечатка в имени колонки или колонку забыли добавить. На форме так бывает, когда колонка есть в коде, но не создана в реквизите. Та же ошибка с (Строки) в модуле формы значит, что к ДанныеФормыДерево обратились как к дереву значений — нужен ПолучитьЭлементы() или РеквизитФормыВЗначение().
  • Найти() возвращает Неопределено, хотя строка есть. Без третьего параметра поиск идёт только по текущему уровню. Так же ведут себя НайтиСтроки(), Сортировать() и Итог().
  • Обработаны не все строки. Для Каждого по Дерево.Строки не заходит во вложенные уровни — нужна рекурсия. Если строки при этом удаляются, часть пропускается: обходите с конца.
  • Итог завышен. Итог(..., Истина) по дереву, где группы уже содержат суммы подчинённых.
  • Итог() «не видит» значения. Колонка создана без типа, и в неё записали числа строками. Итог() учитывает только числовые значения.
  • Колонки «Родитель» и «Строки». Эти имена заняты свойствами самой строки дерева. Называйте колонки иначе, например РодительСсылка.
  • «Переполнение стека встроенного языка» при построении дерева. В исходных данных элемент оказался собственным предком, и рекурсия зациклилась. В варианте с циклом Пока по родителям построение просто не заканчивается. Проверяйте циклы, как в функции СтрокаДляКода().

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

Как узнать уровень строки и её родителя?

Строка.Уровень() возвращает номер уровня, у верхнего — 0. Строка.Родитель возвращает вышестоящую строку, у верхнего уровня — Неопределено. Путь до корня получают циклом: пока Родитель заполнен, поднимаетесь вверх.

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

Готового метода нет. Дерево в таблицу разворачивают рекурсией (функция ДеревоВТаблицу() выше), таблицу в дерево собирают через соответствие «код → строка», как в ДеревоИзСписка(). Если нужна группировка по значениям колонок, таблицу передают параметром, сначала помещают во временную таблицу, а затем вторым запросом пакета выбирают данные с ИТОГИ:

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

Колонки таблицы значений в таком запросе должны быть типизированы.

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

Нет. Источником в конструкции ИЗ &Параметр может быть только таблица значений, и выбрать её можно только во временную таблицу через ПОМЕСТИТЬ. Разверните дерево в таблицу, передайте её параметром, поместите во временную таблицу и дальше работайте с ней в следующих запросах пакета.

Как скопировать дерево?

Дерево.Скопировать() возвращает независимую копию со всеми колонками и строками. Если нужна одна ветка, создайте новое дерево с теми же колонками и перенесите строки рекурсией через ЗаполнитьЗначенияСвойств().

Когда проще заказать

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

По теме

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.


1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.