При работе с иерархическими справочниками в 1С часто возникает задача получить все подчиненные элементы для указанного родителя, причем на всю глубину вложенности. Стандартные подходы могут быть медленными на больших объемах данных. Давайте вместе разберемся, какие существуют способы решения этой задачи и выберем самый производительный и правильный с точки зрения архитектуры.
Проанализируем ситуацию и рассмотрим несколько подходов, от самых простых и очевидных до наиболее оптимальных, которые используются в типовых конфигурациях для высоконагруженных систем.
Это первые решения, которые приходят на ум, но у них есть серьезные недостатки в производительности и гибкости. Рассмотрим их подробнее.
Самый примитивный, но негибкий способ — это прямое обращение к родителям через точку. Например, Родитель.Родитель.Родитель. Этот подход категорически не рекомендуется, так как он работает только для иерархии с заранее известной, фиксированной глубиной. Любое изменение структуры справочника потребует доработки кода.
Более универсальным является встроенный в язык запросов оператор В ИЕРАРХИИ. Он позволяет получить все дочерние элементы для одного или нескольких родителей. Посмотрим на пример:
ВЫБРАТЬ
Ссылка
ИЗ
Справочник.Подразделения
ГДЕ
Ссылка В ИЕРАРХИИ (&МассивРодителей)
Этот оператор удобен, но на больших и глубоких иерархиях он может работать медленно. Причина в том, что платформа 1С для его выполнения рекурсивно выполняет несколько последовательных SQL-запросов к базе данных, чтобы обойти каждый уровень иерархии. Чем глубже иерархия, тем больше запросов и тем ниже производительность.
Вот более сложный пример из практики, где иерархическая выборка комбинируется с другими условиями из регистра сведений:
ВЫБРАТЬ
Подразделения.Ссылка
ИЗ
Справочник.Подразделения КАК Подразделения
ГДЕ
Подразделения.Ссылка В ИЕРАРХИИ(
ВЫБРАТЬ
ДоступныеПодразделения.Подразделение
ИЗ
РегистрСведений.ДоступныеПодразделения КАК ДоступныеПодразделения
ГДЕ
ДоступныеПодразделения.Сотрудник = &Сотрудник
И ДоступныеПодразделения.ДоступПоИерархии
)
ИЛИ Подразделения.Ссылка В (
ВЫБРАТЬ
ДоступныеПодразделения.Подразделение
ИЗ
РегистрСведений.ДоступныеПодразделения КАК ДоступныеПодразделения
ГДЕ
ДоступныеПодразделения.Сотрудник = &Сотрудник
И НЕ ДоступныеПодразделения.ДоступПоИерархии
)
Несмотря на свою функциональность, при большом количестве записей в регистре ДоступныеПодразделения и сложной структуре справочника Подразделения такой запрос может стать узким местом системы.
Если задача получения иерархии связана с разграничением прав доступа, правильным архитектурным решением будет использование механизма RLS (Row Level Security). В этом случае запрос становится тривиальным:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
Ссылка
ИЗ
Справочник.Подразделения
Однако здесь есть важный нюанс. Стандартные средства RLS не поддерживают конструкцию В ИЕРАРХИИ напрямую в шаблонах ограничений. Это означает, что для реализации сложной логики доступа по иерархии придется прибегать к более продвинутым техникам, о которых мы поговорим в следующем способе.
Это наиболее производительный и рекомендуемый способ для работы с иерархиями в высоконагруженных системах. Идея заключается в том, чтобы заранее вычислить и сохранить все возможные связи "предок-потомок" в отдельной, плоской таблице. В 1С для этого идеально подходит РегистрСведений. Такой подход используется, например, в конфигурации ЗУП для ограничения доступа к штатному расписанию.
Разберем по шагам, как это реализовать.
Создаем регистр сведений
Назовем его, к примеру, ПодчиненностьПодразделений. Он должен быть непериодическим, с режимом записи "Независимый". Структура регистра очень проста:
Предок (тип: СправочникСсылка.Подразделения, ведущее)Потомок (тип: СправочникСсылка.Подразделения, ведущее)В этом регистре для каждого подразделения (Потомка) будут храниться записи обо всех его родителях (Предках) на всех уровнях вложенности. Также обязательно добавляется запись, где Предок = Потомок.
Обеспечиваем актуальность данных в регистре
Ключевой момент — данные в нашем новом регистре всегда должны соответствовать актуальной иерархии в справочнике Подразделения. Для этого всю логику обновления нужно разместить в модуле объекта справочника Подразделения, в обработчике события ПриЗаписи().
Логика обновления будет следующей:
Предок и Потомок равны новому элементу. Затем, если у элемента указан родитель, для нового элемента копируются все записи о предках его родителя, где в качестве Потомка будет выступать наш новый элемент.Важно: после создания регистра необходимо выполнить первоначальное его заполнение с помощью специальной обработки, которая обойдет весь справочник и создаст нужные записи.
Используем регистр в запросах
Теперь, когда у нас есть готовая "сплющенная" иерархия, запрос на получение всех дочерних элементов для заданного родителя становится элементарным и, что самое главное, очень быстрым. Он не использует ресурсоемкий оператор В ИЕРАРХИИ и выполняется одним простым соединением.
Посмотрим на пример. Чтобы получить все дочерние подразделения для &ГоловноеПодразделение, запрос будет выглядеть так:
ВЫБРАТЬ
Подчиненность.Потомок КАК ПодчиненноеПодразделение
ИЗ
РегистрСведений.ПодчиненностьПодразделений КАК Подчиненность
ГДЕ
Подчиненность.Предок = &ГоловноеПодразделение
Такой запрос выполняется на уровне СУБД максимально эффективно. Он идеально подходит как для отчетов и динамических списков, так и для использования в механизме RLS для настройки сложного ограничения доступа по иерархии.
Подведем итог: хотя оператор В ИЕРАРХИИ удобен для простых задач, при работе с большими объемами данных и сложными иерархиями предпочтение следует отдавать методу с созданием вспомогательного регистра (транзитивного замыкания). Это требует больших начальных усилий на разработку, но обеспечивает максимальную производительность и масштабируемость вашего решения.