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