Разработка и Конфигурация · 10 мин
1С: как обойти дерево значений и иерархические данные
Как обойти дерево значений в 1С: рекурсивный обход, поиск строк, обработка иерархических справочников. Готовые процедуры, примеры кода и типичные ошибки.
Обход дерева — одна из базовых задач при разработке на 1С: она возникает при работе с деревом значений на форме, иерархическими справочниками вроде «Номенклатура» или «Подразделения», а также при разборе данных из XML и JSON. Во всех этих случаях логика одинакова: нужно пройти по строкам верхнего уровня, а для каждой строки — по её подчинённым строкам, и так до самого нижнего уровня.
В 1С нет встроенного метода «обойти дерево», поэтому обход реализуется вручную — чаще всего рекурсивной процедурой. Рекурсия — это вызов процедурой самой себя для каждой вложенной ветки. Такой подход компактен и работает с деревом любой глубины, но требует аккуратности: ошибка в условии выхода приведёт к зацикливанию.
Ниже разобраны готовые процедуры обхода дерева значений, обхода иерархического справочника, поиск строки по условию, а также типичные ошибки и способы проверить результат. Код приведён на встроенном языке 1С и подходит для управляемых и обычных форм с минимальными правками.
Как устроено дерево значений в 1С
Дерево значений — это объект с колонками и коллекцией строк, где каждая строка сама содержит коллекцию подчинённых строк. Обратиться к вложенным строкам можно через свойство Строки (в новых версиях платформы также доступен алиас ПолучитьЭлементы() у строк дерева на форме). Глубина вложенности не ограничена жёстко, поэтому универсальный обход обязан работать на любом уровне.
Дерево значений на форме и объект типа «ДеревоЗначений» в коде обходятся одинаково. Разница только в том, как вы получаете корневую коллекцию: для реквизита формы это Дерево.ПолучитьЭлементы(), а для программно созданного объекта — Дерево.Строки. Дальше логика идентична.
Рекурсивный обход дерева значений: базовый шаблон
Базовая процедура принимает коллекцию строк и перебирает её в цикле. Для каждой строки выполняется нужное действие (например, вывод в сообщение или запись в таблицу), а затем процедура вызывает сама себя для подчинённых строк этой строки.
Процедура ОбойтиДерево(Строки, Уровень = 0)
Для Каждого Строка Из Строки Цикл
// Здесь обработка текущей строки
Сообщить(СтрПовторить(" ", Уровень) + Строка(Строка.Наименование));
// Рекурсивный вызов для подчинённых строк
ОбойтиДерево(Строка.Строки, Уровень + 1);
КонецЦикла;
КонецПроцедуры
Запускается обход передачей корневой коллекции: ОбойтиДерево(Дерево.Строки);. Параметр «Уровень» не обязателен, но полезен — он позволяет делать отступы при выводе или ограничивать глубину обхода.
- 1Создайте процедуру, принимающую коллекцию строк как параметр
- 2Внутри цикла выполните обработку текущей строки
- 3Вызовите процедуру рекурсивно для Строка.Строки
- 4Запустите обход, передав корневую коллекцию дерева
- 5Проверьте результат на дереве с двумя-тремя уровнями вложенности
Обход дерева на управляемой форме
Если дерево хранится в реквизите формы типа «ДеревоЗначений», обращение к строкам идёт через ПолучитьЭлементы(). У каждой строки-элемента также есть метод ПолучитьЭлементы() для вложенных данных. Код рекурсии при этом почти не меняется:
Процедура ОбойтиДеревоФормы(Элементы)
Для Каждого Элемент Из Элементы Цикл
Сообщить(Элемент.Наименование);
ОбойтиДеревоФормы(Элемент.ПолучитьЭлементы());
КонецЦикла;
КонецПроцедуры
// Вызов:
ОбойтиДеревоФормы(ДеревоНаФорме.ПолучитьЭлементы());
Учитывайте контекст выполнения: если процедура обхода расположена в модуле формы с директивой &НаСервере, а дерево большое, обход может занять ощутимое время. Для массивной обработки лучше выгрузить дерево в объект через РеквизитФормыВЗначение(), обработать на сервере и вернуть результат через ЗначениеВРеквизитФормы().
Поиск строки в дереве по условию
Частая задача — найти строку по значению колонки, например по ссылке на номенклатуру. Есть два подхода. Первый — метод Строки.Найти(Значение, "ИмяКолонки", Истина): четвёртый параметр «Истина» включает поиск по всем уровням дерева, то есть рекурсия уже встроена в платформу.
Второй подход — собственная рекурсивная функция, которая возвращает найденную строку. Она нужна, когда условие сложное (сравнение по нескольким колонкам, нестрогое совпадение) или когда нужно найти все подходящие строки, а не первую попавшуюся.
Функция НайтиСтрокуВДереве(Строки, ИскомоеЗначение)
Для Каждого Строка Из Строки Цикл
Если Строка.Ссылка = ИскомоеЗначение Тогда
Возврат Строка;
КонецЕсли;
Найденная = НайтиСтрокуВДереве(Строка.Строки, ИскомоеЗначение);
Если Найденная <> Неопределено Тогда
Возврат Найденная;
КонецЕсли;
КонецЦикла;
Возврат Неопределено;
КонецФункции
Обратите внимание на проверку результата вложенного вызова: без неё функция вернёт Неопределено, даже если строка найдена на глубоком уровне. Это одна из самых частых ошибок при написании рекурсивного поиска.
Обход иерархического справочника
Иерархические справочники вроде «Номенклатуры» или «Подразделений» — это тоже дерево, но хранится оно в базе данных, а не в памяти. Обходить его рекурсивными запросами «родитель за родителем» неэффективно: каждый уровень даст отдельное обращение к базе. Правильнее получить всю иерархию одним запросом с итогами.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ИТОГИ ПО
| Ссылка ИЕРАРХИЯ";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
Выборка с обходом «ПоГруппировкамСИерархией» сама выстраивает строки в иерархическом порядке: сначала группа, затем её элементы и подгруппы. Дальше можно перенести результат в дерево значений или обработать построчно. Альтернатива — обход по родителю через Справочники.Номенклатура.ВыбратьИерархически(), который перебирает элементы верхнего уровня, а затем позволяет выбрать дочерние.
Сравнение способов обхода
Подходит для данных уже в памяти или на форме. Гибко, но вся нагрузка на клиентский или серверный код
Оптимален для справочников: один запрос к базе вместо множества. Требует аккуратной обработки выборки
Быстрый поиск одной строки по значению колонки. Не подходит для сложных условий и множественного поиска
Типичные ошибки и ограничения
Главная ошибка — отсутствие корректного выхода из рекурсии. В дереве значений зацикливание маловероятно (структура не может ссылаться сама на себя), но при обходе произвольных связанных данных, например графа ссылок из документов, возможны циклические ссылки. В таких случаях ведите соответствие уже посещённых элементов и пропускайте повторы.
Вторая группа ошибок связана с производительностью. Рекурсивный обход справочника через обращения к базе на каждом уровне при глубокой иерархии и тысячах элементов работает медленно. Также стоит помнить про ограничение глубины стека вызовов: при патологически глубокой вложенности (сотни уровней) рекурсия может завершиться ошибкой переполнения стека, хотя в реальных учётных данных это встречается редко.
Как проверить результат обхода
Самый простой способ проверки — вывести обойдённые элементы с отступами по уровням, как в первом примере, и сравнить структуру с отображением дерева на форме. Все ветки должны присутствовать, порядок следования — соответствовать исходному.
Для программной проверки посчитайте количество обработанных строк и сравните с ожидаемым. У дерева значений нет встроенного метода подсчёта всех строк на всех уровнях, поэтому счётчик ведите в параметре процедуры или в переменной модуля. Если число совпадает с контрольным значением (например, с количеством элементов справочника, из которого строилось дерево), обход полный.
Как проверить результат
Совпадает с числом элементов в источнике данных
Меньше ожидаемого — часть веток пропущена, проверяйте условия в цикле
Отступы соответствуют уровням, вложенность видна визуально
Все строки на одном уровне — рекурсивный вызов не выполняется
Строка с глубокого уровня находится и возвращается
Возвращается Неопределено — потерян результат вложенного вызова
Частые вопросы
Можно ли обойти дерево без рекурсии?
Да, через явный стек: помещаете корневые строки в массив или структуру-стек и в цикле извлекаете строку, обрабатываете её и добавляете её подчинённые строки в стек. Такой вариант не упирается в глубину стека вызовов, но код получается заметно менее читаемым, поэтому без особых причин используют рекурсию.
Как обойти дерево снизу вверх, от листьев к корню?
Выполняйте обработку строки после рекурсивного вызова, а не до него: тогда сначала обработаются самые глубокие элементы, а корень — последним. Если нужен подъём по родителям от конкретной строки, у строки дерева есть свойство Родитель — можно идти вверх в цикле, пока оно не станет Неопределено.
Как ограничить глубину обхода?
Передайте в процедуру параметр текущего уровня и максимально допустимую глубину, а перед рекурсивным вызовом проверяйте условие: если уровень достиг предела, вложенные строки не обходятся. Это полезно, когда для отчёта нужны, например, только два верхних уровня иерархии.
Почему Найти() не находит строку на вложенном уровне?
По умолчанию метод Найти() ищет только в текущей коллекции строк. Чтобы поиск шёл по всем уровням дерева, передайте четвёртым параметром значение Истина. Если и тогда строка не находится, проверьте имя колонки и тип сравниваемого значения — ссылка и строковое представление не равны друг другу.
Как перенести иерархический справочник в дерево значений?
Выполните запрос с конструкцией ИТОГИ ПО Ссылка ИЕРАРХИЯ, получите выборку с обходом ПоГруппировкамСИерархией и рекурсивно заполните дерево: для каждой строки выборки добавьте строку в нужную ветку, а для вложенной выборки повторите вызов. Группировочные записи итогов обычно соответствуют группам справочника.
Что важно запомнить
Дерево в 1С обходится рекурсивной процедурой: цикл по строкам плюс вызов самой себя для подчинённых строк. Для справочников эффективнее запрос с итогами по иерархии. Проверяйте результат вложенных вызовов, не меняйте коллекцию во время обхода и контролируйте полноту обхода счётчиком строк.