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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с иерархическими справочниками в 1С:Предприятие, такими как Номенклатура, Контрагенты или Подразделения, часто возникает необходимость определить «самого старшего» предка для конкретного элемента. Например, если у нас есть структура Товары (0 уровень) — Оргтехника (1 уровень) — Принтеры (2 уровень) — Принтер HP (элемент), нам может потребоваться программно получить ссылку на группу Товары.

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

Способ 1. Программный обход в цикле

Это самый распространенный и интуитивно понятный метод. Мы последовательно поднимаемся вверх по дереву иерархии, пока не встретим пустую ссылку в поле Родитель. Проанализируем, как это реализовать наиболее эффективно.

Рассмотрим базовый алгоритм обхода. Для того чтобы не нагружать сервер лишними вызовами, рекомендуется работать со ссылками или использовать метод Уровень().


// Функция получает ссылку на элемент и возвращает его корневого родителя
Функция ПолучитьКорневогоРодителя(ЭлементСсылка) Экспорт
    
    Если ЭлементСсылка.Пустая() Тогда
        Возврат Справочники.Номенклатура.ПустаяСсылка();
    КонецЕсли;
    
    ТекущийОбъект = ЭлементСсылка;
    
    // Цикл продолжается, пока у текущего элемента есть родитель
    Пока ЗначениеЗаполнено(ТекущийОбъект.Родитель) Цикл
        ТекущийОбъект = ТекущийОбъект.Родитель;
    КонецЦикла;
    
    Возврат ТекущийОбъект;
    
КонецФункции

Оптимизация: В платформе 1С 8.3 у ссылок справочников есть метод Уровень(). Мы можем заранее узнать количество итераций цикла, что сделает код более структурированным:


КоличествоУровней = ЭлементСсылка.Уровень();
ТекущийРодитель = ЭлементСсылка;

Для Сч = 1 По КоличествоУровней Цикл
    ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;

Корень = ТекущийРодитель;

Важно помнить, что обращение через точку (ТекущийРодитель.Родитель) в цикле может порождать множество мелких запросов к базе данных, если объект не кэширован. Если элементов много, лучше использовать запросы.

Способ 2. Использование языка запросов (Левые соединения)

Язык запросов 1С не поддерживает рекурсию напрямую (в отличие от стандартного SQL с CTE). Однако, если глубина иерархии в вашей базе ограничена (например, не более 5-10 уровней), мы можем применить метод последовательных левых соединений.

Разберем пример запроса, который находит корень для элемента при вложенности до 3-х уровней:


ВЫБРАТЬ
    ЕСТЬNULL(РодительУровень2.Ссылка, 
        ЕСТЬNULL(РодительУровень1.Ссылка, 
            ЕСТЬNULL(Номенклатура.Родитель, Номенклатура.Ссылка))) КАК КорневойРодитель
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК РодительУровень1
        ПО Номенклатура.Родитель = РодительУровень1.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК РодительУровень2
        ПО РодительУровень1.Родитель = РодительУровень2.Ссылка
ГДЕ
    Номенклатура.Ссылка = &Ссылка

Этот метод очень быстр, но он не универсален. Если пользователь создаст группу 11-го уровня, запрос вернет не корень, а родителя 10-го уровня.

Способ 3. Система компоновки данных (СКД)

Если задача стоит в рамках построения отчета, то СКД — лучший инструмент. В СКД реализован механизм связей наборов данных, который умеет обрабатывать иерархию автоматически.

Проанализируем шаги настройки:

  1. Создаем набор данных — запрос, выбирающий ссылку и родителя.
  2. На закладке Связи наборов данных настраиваем связь поля Родитель с полем Ссылка.
  3. В поле "Параметр" указываем условие иерархии.

При использовании группировки "Иерархия" в настройках отчета, 1С сама построит дерево, и корень всегда будет на верхнем уровне группировки.

Способ 4. Денормализация данных (Реквизит "Корневой родитель")

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

Рассмотрим шаги реализации:

  1. Добавляем в справочник Номенклатура новый реквизит КорневойРодитель (тип: СправочникСсылка.Номенклатура).
  2. В модуле объекта в процедуре ПередЗаписью прописываем логику заполнения:

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

Преимущество: Теперь в любом запросе или списке мы можем мгновенно получить корень просто обратившись к полю Номенклатура.КорневойРодитель. Это радикально повышает производительность.

Способ 5. Использование Регистра Сведений (Транзитивное замыкание)

Это классический метод из теории баз данных. Мы создаем вспомогательный регистр сведений СвязиИерархии с измерениями Элемент и Предок. Для каждого элемента справочника в этот регистр записываются все его вышестоящие родители.

Например, для элемента "Принтер HP" записи будут такими:

Чтобы найти корень, нам достаточно выполнить простой запрос к этому регистру:


ВЫБРАТЬ ПЕРВЫЕ 1
    СвязиИерархии.Предок
ИЗ
    РегистрСведений.СвязиИерархии КАК СвязиИерархии
ГДЕ
    СвязиИерархии.Элемент = &НужныйЭлемент
УПОРЯДОЧИТЬ ПО
    СвязиИерархии.Уровень УБЫВ

Способ 6. "Хак" через полное наименование или полный код

Иногда требуется быстро узнать имя корневой группы без обращения к метаданным. Рассмотрим метод ПолныйКод() или ПолноеНаименование(). Эти методы возвращают строку вида "Товары / Оргтехника / Принтеры / Принтер HP".

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


ПолныйПуть = ОбъектСсылка.ПолноеНаименование();
ПозицияРазделителя = Найти(ПолныйПуть, "/");
Если ПозицияРазделителя > 0 Тогда
    ИмяКорня = СокрЛП(Лев(ПолныйПуть, ПозицияРазделителя - 1));
КонецЕсли;

Внимание: Этот метод ненадежен для логики программы, так как он возвращает строку, а не ссылку. К тому же, если в названии самой группы содержится слэш, логика сломается. Используйте его только для оперативного вывода информации в отчетах "на скорую руку".

Подведем итоги

Мы проанализировали различные ситуации и выяснили следующее:

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

← На главную