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

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

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

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

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

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

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

Создание дерева и описание колонок

Колонки дерева описываются так же, как у таблицы значений: через коллекцию Колонки с указанием имени и типа. Тип лучше задавать явно через ОписаниеТипов — это ускоряет работу и позволяет платформе корректно отображать данные на форме.

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

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

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

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

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

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

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

СтрокаГруппа.Номенклатура = ГруппаСсылка;

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

СтрокаЭлемент.Номенклатура = ЭлементСсылка;

СтрокаЭлемент.Количество = 5;

Заполнение дерева запросом с итогами по иерархии

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

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

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

"ВЫБРАТЬ

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

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

|ИЗ

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

|ИТОГИ ПО

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

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

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

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

Частая задача — сгруппировать плоские данные по нескольким полям (склад → товар → партия). Здесь удобен запрос с итогами по обычным группировкам без ключевого слова ИЕРАРХИЯ: каждая группировка станет уровнем дерева.

Способы заполнения дерева

Запрос с ИТОГИ ПО ИЕРАРХИЯ

Подходит для иерархических справочников. Один запрос, минимум кода. Ограничение: структура дерева совпадает с иерархией справочника.

Запрос с итогами по группировкам

Подходит для произвольной многоуровневой группировки (склад, товар, партия). Гибко, но порядок уровней фиксируется в тексте запроса.

Ручное заполнение циклом

Полный контроль над логикой построения. Медленно на больших объёмах, требует хранения соответствия «родитель → строка дерева».

Обход дерева: рекурсия и итоги

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

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

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

// обработка текущей строки

Сообщить(Строка(Уровень) + ": " + Строка.Номенклатура);

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

КонецЦикла;

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

ОбойтиДерево(Дерево.Строки);

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

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

Порядок построения и обхода дерева
  1. 1
    Создайте объект Новый ДеревоЗначений и опишите колонки с типами
  2. 2
    Заполните дерево запросом с итогами или вручную через Строки.Добавить()
  3. 3
    Напишите рекурсивную процедуру обхода с параметром коллекции строк
  4. 4
    Вызовите её от Дерево.Строки и проверьте, что обработаны все уровни
  5. 5
    Проверьте итоги: количество обработанных строк должно совпасть с ожидаемым

Поиск, сортировка и удаление строк

Метод Найти() у дерева по умолчанию ищет только в корневых строках. Чтобы искать по всей иерархии, нужно передать третий параметр Истина — тогда поиск станет рекурсивным:

НайденнаяСтрока = Дерево.Строки.Найти(ИскомаяСсылка, "Номенклатура", Истина);

Если НайденнаяСтрока <> Неопределено Тогда

НайденнаяСтрока.Количество = 10;

КонецЕсли;

У строки дерева доступны свойства Родитель и Уровень() — они позволяют подняться вверх по иерархии или определить глубину текущей строки. Это удобно при выводе с отступами и при проверке, не является ли строка корневой (Родитель = Неопределено).

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

Типичные ошибки
01
Поиск без третьего параметра Истина — находятся только корневые строки, вложенные «теряются»
02
Перебор только Дерево.Строки без рекурсии — обрабатывается первый уровень, данные веток пропускаются
03
Удаление строки с подчинёнными без проверки — вся ветка удаляется вместе с родителем
04
Сортировка без флага по иерархии — упорядочиваются только корневые строки
05
Добавление строк к дереву вместо добавления к строке-родителю — вся структура оказывается плоской

Вывод дерева на управляемую форму

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

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

Дерево = РеквизитФормыВЗначение("ДеревоНоменклатуры");

Дерево.Строки.Очистить();

// ... заполнение дерева

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

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

Для разворачивания веток программно используется метод Развернуть() элемента формы с идентификатором строки. Идентификатор берётся из метода ПолучитьИдентификатор() строки дерева значений формы — это отдельный объект ДанныеФормыДерево, и путать его с обычным деревом значений нельзя.

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

Преобразование дерева в таблицу значений и обратно

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

Обратное преобразование — из таблицы в дерево — строится через соответствие: ключом служит идентификатор строки (ссылка), значением — добавленная строка дерева. Сначала добавляются все строки, затем по колонке «Родитель» каждая строка переносится к своему родителю методом Строки.Добавить() с последующим заполнением, либо используется перемещение через коллекции строк.

При обмене данными между базами дерево значений сериализуется через ЗначениеВСтрокуВнутр() или промежуточное хранилище значения — прямая передача объекта между сеансами невозможна.

Контрольные точки

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

Количество уровней дерева
Норма

Вложенность совпадает с глубиной иерархии источника

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

Все строки оказались в корне — значит, строки добавлялись к дереву, а не к родителю

Полнота данных после обхода
Норма

Счётчик обработанных строк равен числу строк источника

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

Обработано меньше — рекурсия не заходит во вложенные коллекции

Поиск по дереву
Норма

Найти() с параметром Истина находит строку на любом уровне

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

Поиск возвращает Неопределено при существующем значении — не передан флаг рекурсивного поиска

Ответы на частые вопросы

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

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

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

Напрямую нет — дерево значений существует только в оперативной памяти. Для хранения его помещают в хранилище значения (ЗначениеВСтрокуВнутр или сериализация в ХранилищеЗначения) и записывают в реквизит объекта. При чтении дерево восстанавливается из хранилища.

Как узнать общее количество строк дерева со всеми уровнями?

Готового метода нет: Количество() у коллекции Строки вернёт только строки текущего уровня. Нужно пройти рекурсивным обходом со счётчиком либо при заполнении дерева сразу вести подсчёт добавленных строк.

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

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

Как развернуть все ветки дерева на форме программно?

Через цикл по строкам дерева формы с вызовом Элементы.ИмяДерева.Развернуть(Строка.ПолучитьИдентификатор(), Истина) — второй параметр разворачивает и подчинённые уровни. Вызов выполняется на клиенте, поэтому идентификаторы строк получают заранее.

Что быстрее: запрос с итогами по иерархии или ручное построение дерева?

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

Итог

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

Дерево значений — объект для иерархических данных в памяти. Колонки типизируйте явно, заполняйте запросом с итогами по иерархии, обходите рекурсией, при поиске и сортировке не забывайте флаги работы с подчинёнными строками. Для вывода на управляемую форму используйте РеквизитФормыВЗначение и ЗначениеВРеквизитФормы.