Как реализовать вывод произвольной иерархии справочника в виде дерева в 1С?

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

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

Анализ подхода с рекурсивными запросами и его риски

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

Проанализируем этот подход с точки зрения производительности и безопасности системы:

  1. Проблема запросов в цикле (N+1). Вызов языка запросов внутри рекурсии создает огромную нагрузку на СУБД. Если в организации работает несколько тысяч сотрудников, система выполнит тысячи отдельных запросов, что приведет к катастрофическому падению скорости работы.
  2. Риск закольцовывания (циклические ссылки). Если в данных возникнет ошибка (например, Сотрудник 1 назначен руководителем Сотрудника 2, а Сотрудник 2 — руководителем Сотрудника 1), рекурсивная процедура уйдет в бесконечный цикл и приведет к переполнению стека вызовов.

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

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

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

Рассмотрим порядок настройки собственной иерархии по шагам:

  1. В схеме компоновки данных создадим набор данных - запрос, который выбирает поля Ссылка и Руководитель из справочника сотрудников:
    
    ВЫБРАТЬ
    	Сотрудники.Ссылка КАК Ссылка,
    	Сотрудники.Руководитель КАК Руководитель
    ИЗ
    	Справочник.Сотрудники КАК Сотрудники
    
  2. Перейдем на вкладку Связи наборов данных и добавим связь набора данных с самим собой.
  3. В поле Источник связи укажем наш набор данных, в поле Приемник связи — этот же набор данных.
  4. В качестве выражения источника укажем Ссылка, а в качестве выражения приемника — Руководитель.
  5. В поле Параметр укажем имя параметра (например, Руководитель), а также обязательно установим флаг Список параметров. Это позволит СКД запрашивать данные уровней иерархии пакетами, избегая множественных одиночных обращений к базе.
  6. На вкладке Настройки добавим группировку по полю Ссылка и укажем тип группировки Иерархия.

Вариант 2. Формирование ДереваЗначений в памяти за один запрос

Если дерево необходимо построить программно в коде и передать в коллекцию ДеревоЗначений, алгоритм следует построить так, чтобы обращение к базе данных происходило всего один раз.

Разберем по шагам создание дерева в памяти:

  1. Выполняем один запрос к справочнику и получаем полный список сотрудников с их руководителями.
  2. Загружаем результат запроса в таблицу значений или обходим выборку, создавая карту связей с помощью объекта Соответствие, где ключом выступает ссылка на руководителя, а значением — массив его подчиненных.
  3. Рекурсивно или итеративно заполняем объект ДеревоЗначений в памяти, обращаясь только к подготовленной коллекции без повторных запросов к СУБД.
  4. При обходе дерева используем массив обработанных элементов для защиты от цикличных ссылок.

Посмотрим на пример алгоритма сборки дерева в памяти:


Функция ПолучитьДеревоПодчинения() Экспорт
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Сотрудники.Ссылка КАК Ссылка,
		|	Сотрудники.Руководитель КАК Руководитель
		|ИЗ
		|	Справочник.Сотрудники КАК Сотрудники";
	
	Выборка = Запрос.Выполнить().Выбрать();
	
	// Индексируем данные по руководителям
	СоответствиеУзлов = Новый Соответствие;
	Пока Выборка.Следующий() Цикл
		Подчиненные = СоответствиеУзлов[Выборка.Руководитель];
		Если Подчиненные = Неопределено Тогда
			Подчиненные = Новый Массив;
			СоответствиеУзлов.Вставить(Выборка.Руководитель, Подчиненные);
		КонецЕсли;
		Подчиненные.Добавить(Выборка.Ссылка);
	КонецЦикла;
	
	Дерево = Новый ДеревоЗначений;
	Дерево.Колонки.Добавить("Сотрудник");
	
	// Запускаем построение ветвей от верхнего уровня (где Руководитель = ПустаяСсылка)
	КорневыеСотрудники = СоответствиеУзлов[Справочники.Сотрудники.ПустаяСсылка()];
	Если КорневыеСотрудники <> Неопределено Тогда
		Для Каждого Корневой Из КорневыеСотрудники Цикл
			НоваяСтрока = Дерево.Строки.Добавить();
			НоваяСтрока.Сотрудник = Корневой;
			ЗаполнитьПодчиненных(НоваяСтрока, Корневой, СоответствиеУзлов);
		КонецЦикла;
	КонецЕсли;
	
	Возврат Дерево;
КонецФункции

Процедура ЗаполнитьПодчиненных(РодительскаяСтрока, Руководитель, СоответствиеУзлов)
	Подчиненные = СоответствиеУзлов[Руководитель];
	Если Подчиненные = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Для Каждого Подчиненный Из Подчиненные Цикл
		СтрокаУзла = РодительскаяСтрока.Строки.Добавить();
		СтрокаУзла.Сотрудник = Подчиненный;
		ЗаполнитьПодчиненных(СтрокаУзла, Подчиненный, СоответствиеУзлов);
	КонецЦикла;
КонецПроцедуры

Вариант 3. Ленивая (динамическая) подгрузка узлов на форме

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

  1. При открытии формы на сервере формируются только узлы верхнего уровня (главные руководители).
  2. Для каждого узла, у которого есть подчиненные, в дерево добавляется одна пустая строка-маркер («пустышка»). Это заставляет интерфейс 1С отобразить значок плюса для раскрытия ветки.
  3. В событии таблицы формы ПриРазворачиванииСтроки выполняется контекстный серверный вызов. Программа проверяет, загружены ли подчиненные, удаляет пустую строку-маркер и подгружает следующий уровень иерархии только для разворачиваемого сотрудника.

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

← На главную