Как максимально быстро заполнить дерево значений иерархическими данными в 1С

Программист 1С v8.3 (Обычные формы)
← На главную

Работа с большими объемами данных в иерархических структурах — одна из классических задач программиста 1С. Когда количество строк достигает десятков тысяч (например, 60 000 позиций номенклатуры), стандартные методы ручного перебора коллекций начинают демонстрировать крайне низкую производительность. В этой статье мы разберем, почему «ручной» обход в цикле работает медленно, и рассмотрим наиболее эффективные способы заполнения ДеревоЗначений, включая штатные механизмы платформы и архитектурные хитрости.

Проблема ручного формирования дерева в цикле

Рассмотрим ситуацию, с которой столкнулся автор темы. Попытка имитировать иерархию через фиксированное количество уровней в запросе (через Родитель.Родитель...) и последующий обход ТаблицаЗначений с проверкой изменения родителя — это путь к созданию избыточного и трудноподдерживаемого кода. Основные причины низкой скорости здесь:

  1. Множественные обращения через точку: В запросе конструкции типа Номенклатура.Родитель.Родитель заставляют СУБД выполнять неявные соединения таблиц, что замедляет получение данных.
  2. Алгоритмическая сложность: Постоянные проверки условий внутри цикла и метод Добавить() для каждой строки дерева создают огромную нагрузку на интерпретатор 1С.
  3. Отсутствие индексации: При поиске родительских узлов в больших коллекциях без индексов время выполнения растет экспоненциально.

Проанализируем более эффективные альтернативы.

Решение 1: Использование запроса с итогами и иерархией

Самый быстрый и правильный с точки зрения платформы способ — позволить СУБД и механизмам 1С самим построить дерево. Для этого в тексте запроса необходимо использовать предложение ИТОГИ ПО ... ИЕРАРХИЯ. Платформа 1С оптимизирована для того, чтобы нативно (на уровне C++) преобразовывать результат такого запроса в иерархическую коллекцию.

Рассмотрим пример реализации:


// 1. Формируем запрос с иерархическими итогами
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Номенклатура.Ссылка КАК Номенклатура,
    |	Номенклатура.Код КАК Код,
    |	Номенклатура.Артикул КАК Артикул
    |ИЗ
    |	Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |	Номенклатура.ЭтоГруппа = ЛОЖЬ
    |ИТОГИ ПО
    |	Номенклатура ИЕРАРХИЯ";

РезультатЗапроса = Запрос.Выполнить();

// 2. Выгружаем напрямую в дерево значений
// Параметр ОбходРезультатаЗапроса.ПоГруппировкамСИерархией критически важен
ДеревоРезультат = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

Этот метод работает в десятки раз быстрее ручного обхода, так как Выгрузить() выполняет заполнение структуры данных единым блоком памяти. Если данных очень много, именно этот способ является базовым.

Решение 2: Динамическая (ленивая) загрузка данных

Проанализируем ситуацию с точки зрения интерфейса. Нужно ли пользователю одновременно видеть 60 000 строк? Ответ — нет. Даже если дерево отображается «развернутым», человек физически не сможет обработать такой объем информации. Оптимальным решением будет загрузка данных только в те ветки, которые пользователь реально открывает.

Алгоритм действий следующий:

  1. Заполняем только верхний уровень дерева (элементы, у которых Родитель = Справочники.Номенклатура.ПустаяСсылка()).
  2. Для каждого узла, у которого потенциально есть дети, добавляем «пустую» строку-заглушку. Это заставит платформу отобразить «плюсик» рядом с узлом.
  3. В обработчике события ПриРазворачивании (для элемента формы «Поле дерева значений») проверяем, загружены ли данные. Если нет — удаляем заглушку и подтягиваем реальных «детей» одним быстрым запросом по родителю.

Рассмотрим пример кода для события:


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

Решение 3: Использование СКД как процессора вывода

Система компоновки данных (СКД) умеет работать с иерархией чрезвычайно быстро. Даже если вам не нужен отчет как таковой, вы можете программно использовать возможности СКД для трансформации плоской ТаблицаЗначений в ДеревоЗначений.

Рассмотрим этот процесс подробнее:

Выясним причину, почему это работает быстро. СКД использует внутренние механизмы индексации и оптимизированные итераторы. Мы можем передать в СКД таблицу, полученную из внешнего источника, настроить в схеме группировку «Иерархия» и использовать ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений. Это позволяет избежать написания сложных рекурсивных функций вручную.

Решение 4: Оптимизация ручного заполнения через индексы

Если специфика задачи все же требует ручного заполнения ДеревоЗначений на основе ТаблицаЗначений, критически важно использовать индексацию. В 1С 8.3 объекты типа ТаблицаЗначений позволяют создавать индексы.

Проанализируем шаги оптимизации:

  1. Добавьте в исходную таблицу колонку Родитель.
  2. Используйте метод ТЗ.Индексы.Добавить("Родитель").
  3. При заполнении дерева используйте метод ТЗ.НайтиСтроки(Новый Структура("Родитель", ТекущийУзел)).

Без индекса метод НайтиСтроки будет каждый раз полностью сканировать таблицу. С индексом — обращение будет происходить мгновенно.

Решение 5: Техника «Плоского дерева»

Для очень больших структур (более 100 000 строк) часто выгоднее отказаться от объекта ДеревоЗначений в пользу ТаблицаЗначений. Визуально иерархия имитируется с помощью специальной колонки «Отступ» или «Уровень», а раскрытие/сворачивание реализуется через фильтрацию строк (установка свойства Видимость). Платформа 1С отрисовывает плоские списки значительно быстрее, чем глубокие деревья, так как не тратит ресурсы на расчет геометрии связей между узлами.

Резюме: Чтобы добиться максимальной скорости, всегда старайтесь использовать метод Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией). Если же данных критически много — переходите на динамическую подгрузку узлов при разворачивании. Это обеспечит моментальный отклик интерфейса для пользователя.

← На главную