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

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

При разработке отчетов, обработок и интерфейсных механизмов в 1С часто возникает задача преобразования плоской таблицы значений в иерархическую структуру — дерево значений. Например, когда необходимо сгруппировать список документов по шапке, а подчиненными строками вывести номенклатуру с количеством. На управляемых формах эта задача имеет свои нюансы: данные формы представлены специальными коллекциями (ДанныеФормыКоллекция и ДанныеФормыДерево), а серверные вызовы и перегонка структур требуют оптимизации. Разберем наиболее эффективные способы решения этой задачи, их плюсы, минусы и подводные камни.

Способ 1. Использование объекта ПостроительЗапроса

Рассмотрим один из самых быстрых и лаконичных способов группировки таблицы значений прямо в оперативной памяти сервера — использование объекта ПостроительЗапроса. Этот метод удобен тем, что не требует написания текста запроса вручную и прекрасно справляется с агрегацией плоских таблиц.

Посмотрим на пример универсальной функции:


Функция ТаблицаЗначенийВДеревоЗначений(ТЗ, ГруппировкаКолонка) Экспорт 
    ПЗ = Новый ПостроительЗапроса;
    ПЗ.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ);
    ПЗ.ДобавлениеПредставлений = ТипДобавленияПредставлений.НеДобавлять;
    ПЗ.ИсточникДанных.Колонки[ГруппировкаКолонка].Измерение = Истина;
    ПЗ.ЗаполнитьНастройки();
    ПЗ.Выполнить();
    
    Дерево = ПЗ.Результат.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
    Возврат Дерево;  
КонецФункции

Разберем важные нюансы работы этого метода:

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

Способ 2. Запрос к таблице значений с конструкцией ИТОГИ ПО

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


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

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

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

  1. Строгая типизация колонок: чтобы передать таблицу значений в запрос, все задействованные колонки таблицы обязаны иметь строго заданные типы (объект ОписаниеТипов). Если колонка создана с составным или неопределенным типом, платформа вернет ошибку.
  2. Накладные расходы СУБД: при помещении таблицы значений во временную таблицу данные сериализуются и передаются с сервера 1С на сервер СУБД в базу временных таблиц (например, tempdb в MS SQL Server). На небольших объемах (до нескольких тысяч строк) создание временной таблицы в СУБД работает существенно дольше, чем обработка в оперативной памяти.

Способ 3. Прямой обход отсортированной таблицы в памяти (максимальная скорость)

Когда таблица содержит до десятков тысяч строк и критически важна максимальная скорость отклика без обращения к СУБД, оптимальным вариантом является простая сортировка и один проход цикла. Платформа 1С использует высокоскоростную внутреннюю сортировку массива строк в оперативной памяти.

Разберем по шагам, как реализовать этот алгоритм:


Функция ПреобразоватьТЗвДеревоБыстро(ТЗ)
    Дерево = Новый ДеревоЗначений;
    Дерево.Колонки.Добавить("Документ");
    Дерево.Колонки.Добавить("Товар");
    Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

    // Сортируем по полю группировки
    ТЗ.Сортировать("Документ");

    ТекущийДокумент = Неопределено;
    ТекущийРодитель = Неопределено;

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

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

    Возврат Дерево;
КонецФункции

Отображение дерева на управляемой форме

На управляемой форме дерево значений отображается через реквизит формы с типом ДеревоЗначений, связанный с элементом управления «Таблица формы». Разберем, как передать полученную коллекцию в реквизит формы.

Вариант с выгрузкой через преобразование реквизитов:

Если дерево получено на сервере как прикладной объект ДеревоЗначений, его можно загрузить в реквизит формы с помощью штатного метода:


&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
    Таблица = РеквизитФормыВЗначение("ТаблицаНаФорме");
    Дерево = ТаблицаЗначенийВДеревоЗначений(Таблица, "Документ");
    
    // Загружаем прикладной объект в реквизит формы
    ЗначениеВРеквизитФормы(Дерево, "ДеревоНаФорме");
КонецПроцедуры

Оптимизация: прямое заполнение без двойной конвертации

Вызовы методов РеквизитФормыВЗначение и ЗначениеВРеквизитФормы создают полные копии данных в памяти и выполняют двойную трансформацию типов. Если в таблице много строк, эффективнее заполнять реквизит формы ДанныеФормыДерево напрямую:


&НаСервере
Процедура ЗаполнитьДеревоФормыНапрямую()
    КоллекцияДерева = ДеревоНаФорме.ПолучитьЭлементы();
    КоллекцияДерева.Очистить();

    // Заполняем узлы формы напрямую
    ТЗ = РеквизитФормыВЗначение("ТаблицаНаФорме");
    ТЗ.Сортировать("Документ");
    
    ТекущийДокумент = Неопределено;
    ТекущийУзел = Неопределено;
    
    Для Каждого Стр Из ТЗ Цикл
        Если Стр.Документ <> ТекущийДокумент Тогда
            ТекущийДокумент = Стр.Документ;
            ТекущийУзел = КоллекцияДерева.Добавить();
            ТекущийУзел.Документ = Стр.Документ;
        КонецЕсли;
        
        СтрокаДетали = ТекущийУзел.ПолучитьЭлементы().Добавить();
        ЗаполнитьЗначенияСвойств(СтрокаДетали, Стр);
    КонецЦикла;
КонецПроцедуры

Как программно развернуть все уровни дерева на клиенте

После заполнения дерева на форме часто требуется сразу показать пользователю все сгруппированные данные в раскрытом виде. Для этого используется метод таблицы формы Развернуть. Метод принимает идентификатор строки и булев флаг подчиненности.

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


&НаКлиенте
Процедура РазвернутьВсеУзлыДерева()
    Для Каждого СтрКорня Из ДеревоНаФорме.ПолучитьЭлементы() Цикл
        // Истина указывает на необходимость рекурсивного раскрытия всех вложенных строк
        Элементы.ТаблицаДеревоНаФорме.Развернуть(СтрКорня.ПолучитьИдентификатор(), Истина);
    КонецЦикла;
КонецПроцедуры

Обратите внимание на ограничение: таблица формы с типом «Дерево» не поддерживает порционную динамическую подгрузку данных при прокрутке. Если итоговый объем превышает 5 000–10 000 строк, вывод всей иерархии на клиент может вызвать существенные задержки интерфейса. В подобных ситуациях предпочтительнее проектировать форму по принципу связанных списков «Мастер — Деталь».

← На главную