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

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

Как в 1С получить самого верхнего родителя элемента

Как в 1С найти верхнего родителя в иерархии: запрос с ИЕРАРХИЯ, рекурсия в коде, соединение таблиц. Примеры для справочников и планов видов характеристик.

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

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

Ниже разобраны рабочие варианты с примерами кода, их ограничения и способы проверить корректность результата.

Как устроена иерархия и почему задача не решается напрямую

В иерархическом справочнике каждый элемент хранит ссылку только на своего непосредственного родителя в стандартном реквизите Родитель. У элемента верхнего уровня родитель пустой — это и есть признак корня. Чтобы добраться до корня, нужно пройти цепочку Родитель → Родитель.Родитель → и так далее, пока родитель не станет пустым.

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

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

Способ 1. Рекурсивная функция в коде

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

Функция ВерхнийРодитель(Элемент)

Текущий = Элемент;

Пока ЗначениеЗаполнено(Текущий.Родитель) Цикл

Текущий = Текущий.Родитель;

КонецЦикла;

Возврат Текущий;

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

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

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

Обход в памяти для массовой обработки
  1. 1
    Запросом выберите все элементы справочника с полями Ссылка и Родитель
  2. 2
    Сложите результат в соответствие: ключ — ссылка, значение — ссылка на родителя
  3. 3
    Для каждого элемента поднимайтесь по соответствию, пока родитель не станет пустым
  4. 4
    Сохраните найденный корень в отдельное соответствие, чтобы не считать его повторно
  5. 5
    Используйте результат в отчёте или обработке без обращений к базе

Способ 2. Запрос с соединением таблицы самой с собой

Если максимальная глубина иерархии известна и невелика (обычно 3–5 уровней), верхнего родителя можно найти одним запросом, последовательно соединяя таблицу справочника саму с собой:

ВЫБРАТЬ

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

ВЫБОР

КОГДА Уровень3.Ссылка ЕСТЬ НЕ NULL ТОГДА Уровень3.Ссылка

КОГДА Уровень2.Ссылка ЕСТЬ НЕ NULL ТОГДА Уровень2.Ссылка

КОГДА Уровень1.Ссылка ЕСТЬ НЕ NULL ТОГДА Уровень1.Ссылка

ИНАЧЕ Номенклатура.Ссылка

КОНЕЦ КАК ВерхнийРодитель

ИЗ

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

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень1

ПО Номенклатура.Родитель = Уровень1.Ссылка

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень2

ПО Уровень1.Родитель = Уровень2.Ссылка

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень3

ПО Уровень2.Родитель = Уровень3.Ссылка

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

Плюс подхода — весь расчёт выполняется на сервере СУБД одним запросом, что удобно для отчётов. Минус — жёсткая привязка к глубине и рост стоимости запроса с каждым соединением.

Способ 3. Итоги по иерархии и конструкция В ИЕРАРХИИ

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

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

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

Сравнение способов

Рекурсивная функция в коде

Универсально, любая глубина. Медленно при массовой обработке из-за чтений из базы; оптимизируется кэшем в соответствии.

Соединение таблицы с собой

Быстро, работает в запросах и СКД. Ограничение: фиксированная глубина, риск молчаливой ошибки при её превышении.

Итоги по иерархии / СКД

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

Хранимый реквизит

Мгновенное чтение корня в любых запросах. Требует поддержки: подписки на запись и контроля актуальности.

Способ 4. Хранить верхнего родителя в отдельном реквизите

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

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

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

Проверка результата и типичные ошибки

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

Отдельный тест — элементы с «битой» ссылкой в родителе. Если родитель был удалён нештатно, обращение к его реквизитам может дать неожиданный результат; в обходе стоит проверять ЗначениеЗаполнено и, при необходимости, валидность ссылки.

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

Выбор решения под вашу задачу

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

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

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

Как выбрать действие

Нужен корень одного элемента в кодеИспользуйте функцию обхода по цепочке Родитель
Нужен корень для тысяч элементов в обработкеВыгрузите пары элемент–родитель в соответствие и обходите в памяти
Нужен корень в запросе или СКД при глубине до 5 уровнейИспользуйте соединение таблицы самой с собой с запасом по глубине
Корень нужен постоянно в отборах и отчётахДобавьте хранимый реквизит или регистр сведений с заполнением при записи
Итог

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

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

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

Как получить верхнего родителя в СКД без изменения конфигурации?

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

Что вернёт функция обхода для элемента верхнего уровня?

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

Есть ли ограничение на глубину рекурсии в 1С?

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

Как найти верхнего родителя у плана видов характеристик?

Точно так же, как у справочника: если у плана видов характеристик включена иерархия, у элементов есть стандартный реквизит Родитель, и все описанные способы — обход в коде, соединения в запросе — применимы без изменений, только имя таблицы в запросе будет ПланВидовХарактеристик.ИмяПВХ.

Что делать, если в иерархии есть зацикленные ссылки?

Штатно платформа не даёт сделать элемент родителем самого себя или своего потомка, но после нештатных загрузок цикл возможен. Защититесь счётчиком итераций в цикле обхода: если число шагов превысило разумный предел (например, 100), прервите обход и зафиксируйте проблемный элемент для ручного исправления.

Нужно ли пересчитывать хранимый реквизит при переносе группы?

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