При разработке отчетов, обработок и интерфейсных механизмов в 1С часто возникает задача преобразования плоской таблицы значений в иерархическую структуру — дерево значений. Например, когда необходимо сгруппировать список документов по шапке, а подчиненными строками вывести номенклатуру с количеством. На управляемых формах эта задача имеет свои нюансы: данные формы представлены специальными коллекциями (ДанныеФормыКоллекция и ДанныеФормыДерево), а серверные вызовы и перегонка структур требуют оптимизации. Разберем наиболее эффективные способы решения этой задачи, их плюсы, минусы и подводные камни.
Рассмотрим один из самых быстрых и лаконичных способов группировки таблицы значений прямо в оперативной памяти сервера — использование объекта ПостроительЗапроса. Этот метод удобен тем, что не требует написания текста запроса вручную и прекрасно справляется с агрегацией плоских таблиц.
Посмотрим на пример универсальной функции:
Функция ТаблицаЗначенийВДеревоЗначений(ТЗ, ГруппировкаКолонка) Экспорт
ПЗ = Новый ПостроительЗапроса;
ПЗ.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ);
ПЗ.ДобавлениеПредставлений = ТипДобавленияПредставлений.НеДобавлять;
ПЗ.ИсточникДанных.Колонки[ГруппировкаКолонка].Измерение = Истина;
ПЗ.ЗаполнитьНастройки();
ПЗ.Выполнить();
Дерево = ПЗ.Результат.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
Возврат Дерево;
КонецФункции
Разберем важные нюансы работы этого метода:
ПостроительЗапроса доступен исключительно на сервере, в толстом клиенте или во внешнем соединении. На тонком клиенте этот код выполняться не будет.Проанализируем классический подход с использованием языка запросов платформы 1С. В этом варианте таблица значений передается в запрос в качестве параметра и помещается во временную таблицу, после чего формируются необходимые итоги.
Процедура ПреобразоватьТаблицуВДеревоЧерезЗапрос(ТаблицаДанных)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Документ КАК Документ,
| Т.Товар КАК Товар,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТаблицаДанных КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Данные.Документ КАК Документ,
| ВТ_Данные.Товар КАК Товар,
| ВТ_Данные.Количество КАК Количество
|ИЗ
| ВТ_Данные КАК ВТ_Данные
|ИТОГИ
| СУММА(Количество)
|ПО
| Документ";
Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
РезультатЗапроса = Запрос.Выполнить();
// Выгружаем результат сразу в дерево значений
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
КонецПроцедуры
Выясним причину, почему этот способ не всегда является идеальным с точки зрения производительности:
ОписаниеТипов). Если колонка создана с составным или неопределенным типом, платформа вернет ошибку.tempdb в MS SQL Server). На небольших объемах (до нескольких тысяч строк) создание временной таблицы в СУБД работает существенно дольше, чем обработка в оперативной памяти.Когда таблица содержит до десятков тысяч строк и критически важна максимальная скорость отклика без обращения к СУБД, оптимальным вариантом является простая сортировка и один проход цикла. Платформа 1С использует высокоскоростную внутреннюю сортировку массива строк в оперативной памяти.
Разберем по шагам, как реализовать этот алгоритм:
Функция ПреобразоватьТЗвДеревоБыстро(ТЗ)
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Документ");
Дерево.Колонки.Добавить("Товар");
Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// Сортируем по полю группировки
ТЗ.Сортировать("Документ");
ТекущийДокумент = Неопределено;
ТекущийРодитель = Неопределено;
Для Каждого Стр Из ТЗ Цикл
Если Стр.Документ <> ТекущийДокумент Тогда
ТекущийДокумент = Стр.Документ;
ТекущийРодитель = Дерево.Строки.Добавить();
ТекущийРодитель.Документ = Стр.Документ;
КонецЕсли;
ПодчиненнаяСтрока = ТекущийРодитель.Строки.Добавить();
ЗаполнитьЗначенияСвойств(ПодчиненнаяСтрока, Стр);
// Накапливаем итог в родительском узле
ТекущийРодитель.Количество = ТекущийРодитель.Количество + Стр.Количество;
КонецЦикла;
Возврат Дерево;
КонецФункции
На управляемой форме дерево значений отображается через реквизит формы с типом ДеревоЗначений, связанный с элементом управления «Таблица формы». Разберем, как передать полученную коллекцию в реквизит формы.
Вариант с выгрузкой через преобразование реквизитов:
Если дерево получено на сервере как прикладной объект ДеревоЗначений, его можно загрузить в реквизит формы с помощью штатного метода:
&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
Таблица = РеквизитФормыВЗначение("ТаблицаНаФорме");
Дерево = ТаблицаЗначенийВДеревоЗначений(Таблица, "Документ");
// Загружаем прикладной объект в реквизит формы
ЗначениеВРеквизитФормы(Дерево, "ДеревоНаФорме");
КонецПроцедуры
Оптимизация: прямое заполнение без двойной конвертации
Вызовы методов РеквизитФормыВЗначение и ЗначениеВРеквизитФормы создают полные копии данных в памяти и выполняют двойную трансформацию типов. Если в таблице много строк, эффективнее заполнять реквизит формы ДанныеФормыДерево напрямую:
&НаСервере
Процедура ЗаполнитьДеревоФормыНапрямую()
КоллекцияДерева = ДеревоНаФорме.ПолучитьЭлементы();
КоллекцияДерева.Очистить();
// Заполняем узлы формы напрямую
ТЗ = РеквизитФормыВЗначение("ТаблицаНаФорме");
ТЗ.Сортировать("Документ");
ТекущийДокумент = Неопределено;
ТекущийУзел = Неопределено;
Для Каждого Стр Из ТЗ Цикл
Если Стр.Документ <> ТекущийДокумент Тогда
ТекущийДокумент = Стр.Документ;
ТекущийУзел = КоллекцияДерева.Добавить();
ТекущийУзел.Документ = Стр.Документ;
КонецЕсли;
СтрокаДетали = ТекущийУзел.ПолучитьЭлементы().Добавить();
ЗаполнитьЗначенияСвойств(СтрокаДетали, Стр);
КонецЦикла;
КонецПроцедуры
После заполнения дерева на форме часто требуется сразу показать пользователю все сгруппированные данные в раскрытом виде. Для этого используется метод таблицы формы Развернуть. Метод принимает идентификатор строки и булев флаг подчиненности.
Чтобы развернуть абсолютно все узлы, достаточно на клиенте обойти строки только первого уровня и передать параметр Истина:
&НаКлиенте
Процедура РазвернутьВсеУзлыДерева()
Для Каждого СтрКорня Из ДеревоНаФорме.ПолучитьЭлементы() Цикл
// Истина указывает на необходимость рекурсивного раскрытия всех вложенных строк
Элементы.ТаблицаДеревоНаФорме.Развернуть(СтрКорня.ПолучитьИдентификатор(), Истина);
КонецЦикла;
КонецПроцедуры
Обратите внимание на ограничение: таблица формы с типом «Дерево» не поддерживает порционную динамическую подгрузку данных при прокрутке. Если итоговый объем превышает 5 000–10 000 строк, вывод всей иерархии на клиент может вызвать существенные задержки интерфейса. В подобных ситуациях предпочтительнее проектировать форму по принципу связанных списков «Мастер — Деталь».