Как выгрузить иерархический справочник в Таблицу Значений с сохранением порядка иерархии

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

При разработке отчетов и обработок в 1С часто возникает задача получить данные иерархического справочника (например, Справочник.Номенклатура) не в виде дерева, а в виде плоской ТаблицаЗначений, но с сохранением визуальной структуры: сначала родительская папка, затем входящие в нее подпапки и элементы, отсортированные по наименованию или коду.

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

Почему порядок строк в выборке отличается от формы списка

Проанализируем типичные ошибки при попытке построить иерархическую выборку через язык запросов 1С:

  1. Некорректное использование модификатора ИЕРАРХИЯ в блоке УПОРЯДОЧИТЬ ПО: ключевое слово ИЕРАРХИЯ в секции сортировки применимо исключительно к полям ссылочного типа (например, Ссылка или Родитель). Попытка написать УПОРЯДОЧИТЬ ПО ЭтоГруппа ИЕРАРХИЯ синтаксически некорректна и игнорируется платформой, что приводит к непредсказуемому порядку вывода строк.
  2. Потеря групп при внутреннем соединении: если справочник соединяется через ВНУТРЕННЕЕ СОЕДИНЕНИЕ с таблицей цен или остатков (например, РегистрСведений.ЦеныВнутренние.СрезПоследних), в результат попадают только конечные элементы. Родительские папки в такой выборке полностью отсутствуют, так как по ним нет записей в регистре.
  3. Избыточные итоги без правильного обхода: использование секции ИТОГИ ПО Номенклатура ИЕРАРХИЯ строит иерархическое дерево на уровне результата запроса. Если просто выгрузить такой запрос в плоскую таблицу через Запрос.Выполнить().Выгрузить(), результат будет содержать системные агрегатные строки итогов, которые нарушают ожидаемый линейный список.

Способ 1. Иерархические итоги с АВТОУПОРЯДОЧИВАНИЕМ и рекурсивной выборкой (рекомендуемый)

Рассмотрим наиболее производительный и надежный способ. Использование секции ИТОГИ ПО ... ИЕРАРХИЯ заставляет платформу 1С автоматически восстановить цепочку родительских папок для всех найденных элементов, даже если для фильтрации использовалось внутреннее соединение с регистром.

Чтобы порядок обхода групп и элементов соответствовал стандартному представлению в форме списка справочника, используем ключевое слово АВТОУПОРЯДОЧИВАНИЕ, после чего выполним рекурсивный обход выборки.

Посмотрим на пример формирования запроса с использованием временной таблицы среза последних:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Цены.Номенклатура КАК Номенклатура,
|	Цены.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
|	РегистрСведений.ЦеныВнутренние.СрезПоследних(&Период, ) КАК Цены
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	СпрНоменклатура.Ссылка КАК Номенклатура,
|	ВТ_Цены.Цена КАК Цена
|ИЗ
|	Справочник.Номенклатура КАК СпрНоменклатура
|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Цены КАК ВТ_Цены
|		ПО СпрНоменклатура.Ссылка = ВТ_Цены.Номенклатура
|ИТОГИ
|	МАКСИМУМ(Цена)
|ПО
|	Номенклатура ИЕРАРХИЯ
|АВТОУПОРЯДОЧИВАНИЕ";

Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
РезультатЗапроса = Запрос.Выполнить();

Теперь разберем, как полученную иерархическую выборку перенести в плоскую таблицу значений с сохранением правильной последовательности строк:


// Создаем структуру результирующей таблицы
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТЗ.Колонки.Добавить("ЭтоГруппа", Новый ОписаниеТипов("Булево"));
ТЗ.Колонки.Добавить("Уровень", Новый ОписаниеТипов("Число"));

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

Процедура рекурсивного обхода выборки реализуется следующим образом:


Процедура ОбойтиВыборкуИерархически(Выборка, ТаблицаРезультат, Уровень)

	Пока Выборка.Следующий() Цикл
		// Пропускаем системный общий итог, если он пустой
		Если Выборка.Номенклатура = Null ИЛИ Выборка.Номенклатура = Неопределено Тогда
			Продолжить;
		КонецЕсли;

		НоваяСтрока = ТаблицаРезультат.Добавить();
		НоваяСтрока.Номенклатура = Выборка.Номенклатура;
		НоваяСтрока.Цена = Выборка.Цена;
		НоваяСтрока.ЭтоГруппа = Выборка.Номенклатура.ЭтоГруппа;
		НоваяСтрока.Уровень = Уровень;

		// Рекурсивно получаем подчиненные строки
		ПодчиненнаяВыборка = Выборка.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
		Если ПодчиненнаяВыборка.Количество() > 0 Тогда
			ОбойтиВыборкуИерархически(ПодчиненнаяВыборка, ТаблицаРезультат, Уровень + 1);
		КонецЕсли;
	КонецЦикла;

КонецПроцедуры

Способ 2. Выгрузка в Дерево Значений с последующим разворачиванием в Таблицу

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


Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура");
ТЗ.Колонки.Добавить("Цена");

РазвернутьДеревоВТаблицу(Дерево.Строки, ТЗ);

Рекурсивная функция обхода строк дерева значений:


Процедура РазвернутьДеревоВТаблицу(КоллекцияСтрок, ТаблицаРезультат)

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

		// Обходим вложенные строки текущей ветки
		РазвернутьДеревоВТаблицу(СтрокаДерева.Строки, ТаблицаРезультат);
	КонецЦикла;

КонецПроцедуры

Способ 3. Формирование плоской выборки напрямую запросом

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Спр.Ссылка КАК Номенклатура,
|	Спр.Наименование КАК Наименование,
|	Спр.ЭтоГруппа КАК ЭтоГруппа
|ИЗ
|	Справочник.Номенклатура КАК Спр
|УПОРЯДОЧИТЬ ПО
|	Спр.Ссылка ИЕРАРХИЯ,
|	Спр.ЭтоГруппа УБЫВ,
|	Спр.Наименование";

ТЗ = Запрос.Выполнить().Выгрузить();

Обратите внимание: данный способ строит корректную последовательность только для тех элементов, которые изначально присутствуют в выборке. Если применить фильтрацию или внутреннее соединение, родительские папки отсекутся, и восстановить полную цепочку каталога без конструкции ИТОГИ ПО ... ИЕРАРХИЯ не удастся.

Рекомендации по оптимизации на больших объемах данных

  1. Не используйте полное левое соединение со всем справочником: перебор сотен тысяч элементов справочника с последующим отсечением пустых цен в коде 1С создает избыточную нагрузку на оперативную память и процессор.
  2. Используйте временные таблицы с индексами: срезы регистров сведений всегда помещайте во временные таблицы с индексацией по соединяемым ссылочным полям (ИНДЕКСИРОВАТЬ ПО Номенклатура).
  3. Доверяйте построение иерархии СУБД: запрос с иерархическими итогами выбирает с сервера только необходимые строки и их прямых предков, сокращая объем передаваемых по сети данных до минимума.
← На главную