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