При разработке отчетов и обработок в 1С часто возникает задача получить данные иерархического справочника (например, Справочник.Номенклатура) не в виде дерева, а в виде плоской ТаблицаЗначений, но с сохранением визуальной структуры: сначала родительская папка, затем входящие в нее подпапки и элементы, отсортированные по наименованию или коду.
Если решать эту задачу простым запросом, разработчики часто сталкиваются с тем, что порядок строк нарушается, вложенные папки оказываются выше родителей, либо при соединении с регистрами отсекаются вышестоящие группы. Разберем по шагам причины такого поведения и рассмотрим правильные варианты реализации.
Проанализируем типичные ошибки при попытке построить иерархическую выборку через язык запросов 1С:
ИЕРАРХИЯ в блоке УПОРЯДОЧИТЬ ПО: ключевое слово ИЕРАРХИЯ в секции сортировки применимо исключительно к полям ссылочного типа (например, Ссылка или Родитель). Попытка написать УПОРЯДОЧИТЬ ПО ЭтоГруппа ИЕРАРХИЯ синтаксически некорректна и игнорируется платформой, что приводит к непредсказуемому порядку вывода строк.ВНУТРЕННЕЕ СОЕДИНЕНИЕ с таблицей цен или остатков (например, РегистрСведений.ЦеныВнутренние.СрезПоследних), в результат попадают только конечные элементы. Родительские папки в такой выборке полностью отсутствуют, так как по ним нет записей в регистре.ИТОГИ ПО Номенклатура ИЕРАРХИЯ строит иерархическое дерево на уровне результата запроса. Если просто выгрузить такой запрос в плоскую таблицу через Запрос.Выполнить().Выгрузить(), результат будет содержать системные агрегатные строки итогов, которые нарушают ожидаемый линейный список.Рассмотрим наиболее производительный и надежный способ. Использование секции ИТОГИ ПО ... ИЕРАРХИЯ заставляет платформу 1С автоматически восстановить цепочку родительских папок для всех найденных элементов, даже если для фильтрации использовалось внутреннее соединение с регистром.
Чтобы порядок обхода групп и элементов соответствовал стандартному представлению в форме списка справочника, используем ключевое слово АВТОУПОРЯДОЧИВАНИЕ, после чего выполним рекурсивный обход выборки.
Посмотрим на пример формирования запроса с использованием временной таблицы среза последних:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_Цены
|ИЗ
| РегистрСведений.ЦеныВнутренние.СрезПоследних(&Период, ) КАК Цены
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СпрНоменклатура.Ссылка КАК Номенклатура,
| ВТ_Цены.Цена КАК Цена
|ИЗ
| Справочник.Номенклатура КАК СпрНоменклатура
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Цены КАК ВТ_Цены
| ПО СпрНоменклатура.Ссылка = ВТ_Цены.Номенклатура
|ИТОГИ
| МАКСИМУМ(Цена)
|ПО
| Номенклатура ИЕРАРХИЯ
|АВТОУПОРЯДОЧИВАНИЕ";
Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
РезультатЗапроса = Запрос.Выполнить();
Теперь разберем, как полученную иерархическую выборку перенести в плоскую таблицу значений с сохранением правильной последовательности строк:
// Создаем структуру результирующей таблицы
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
ТЗ.Колонки.Добавить("ЭтоГруппа", Новый ОписаниеТипов("Булево"));
ТЗ.Колонки.Добавить("Уровень", Новый ОписаниеТипов("Число"));
// Запускаем рекурсивный обход
ВыборкаВерхнийУровень = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
ОбойтиВыборкуИерархически(ВыборкаВерхнийУровень, ТЗ, 0);
Процедура рекурсивного обхода выборки реализуется следующим образом:
Процедура ОбойтиВыборкуИерархически(Выборка, ТаблицаРезультат, Уровень)
Пока Выборка.Следующий() Цикл
// Пропускаем системный общий итог, если он пустой
Если Выборка.Номенклатура = Null ИЛИ Выборка.Номенклатура = Неопределено Тогда
Продолжить;
КонецЕсли;
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.Номенклатура = Выборка.Номенклатура;
НоваяСтрока.Цена = Выборка.Цена;
НоваяСтрока.ЭтоГруппа = Выборка.Номенклатура.ЭтоГруппа;
НоваяСтрока.Уровень = Уровень;
// Рекурсивно получаем подчиненные строки
ПодчиненнаяВыборка = Выборка.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
Если ПодчиненнаяВыборка.Количество() > 0 Тогда
ОбойтиВыборкуИерархически(ПодчиненнаяВыборка, ТаблицаРезультат, Уровень + 1);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если объем данных сравнительно небольшой, можно выгрузить результат запроса в ДеревоЗначений методом Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) и последовательно обойти его ветви сверху вниз:
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура");
ТЗ.Колонки.Добавить("Цена");
РазвернутьДеревоВТаблицу(Дерево.Строки, ТЗ);
Рекурсивная функция обхода строк дерева значений:
Процедура РазвернутьДеревоВТаблицу(КоллекцияСтрок, ТаблицаРезультат)
Для Каждого СтрокаДерева Из КоллекцияСтрок Цикл
Если ЗначениеЗаполнено(СтрокаДерева.Номенклатура) Тогда
НоваяСтрока = ТаблицаРезультат.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДерева);
КонецЕсли;
// Обходим вложенные строки текущей ветки
РазвернутьДеревоВТаблицу(СтрокаДерева.Строки, ТаблицаРезультат);
КонецЦикла;
КонецПроцедуры
Если требуется получить плоский список без использования итогов, сортировку по иерархии необходимо накладывать на само ссылочное поле Ссылка:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Номенклатура,
| Спр.Наименование КАК Наименование,
| Спр.ЭтоГруппа КАК ЭтоГруппа
|ИЗ
| Справочник.Номенклатура КАК Спр
|УПОРЯДОЧИТЬ ПО
| Спр.Ссылка ИЕРАРХИЯ,
| Спр.ЭтоГруппа УБЫВ,
| Спр.Наименование";
ТЗ = Запрос.Выполнить().Выгрузить();
Обратите внимание: данный способ строит корректную последовательность только для тех элементов, которые изначально присутствуют в выборке. Если применить фильтрацию или внутреннее соединение, родительские папки отсекутся, и восстановить полную цепочку каталога без конструкции ИТОГИ ПО ... ИЕРАРХИЯ не удастся.
ИНДЕКСИРОВАТЬ ПО Номенклатура).