В практике разработки на платформе 1С часто возникает задача классификации данных по укрупненным категориям. Типичный пример: в отчете по продажам нужно сгруппировать номенклатуру не просто по папкам, в которых она лежит, а по самым верхним узлам справочника (корневым группам). Если дерево справочника имеет большую вложенность, стандартные методы могут работать медленно. В этой статье мы разберем несколько эффективных способов решения этой задачи, начиная от простых алгоритмов и заканчивая сложными архитектурными изменениями.
Рассмотрим стандартный подход, который часто используют начинающие разработчики. Он заключается в переборе родителей элемента в цикле Пока. Проанализируем типичный фрагмент такого кода:
Пока ЗначениеЗаполнено(Группа.Родитель) Цикл
Группа = Группа.Родитель;
КонецЦикла;
Этот алгоритм кажется простым, но при обработке тысяч строк в результате запроса он становится "узким местом". Причина в том, что каждое обращение к свойству Родитель — это потенциальный отдельный запрос к базе данных (если объект не закэширован). Если у вас 10 000 строк и глубина вложенности 5 уровней, вы можете получить десятки тысяч обращений к серверу БД. Для оптимизации программного обхода рекомендуется использовать кэширование в памяти (объект Соответствие), где ключом будет ссылка на номенклатуру, а значением — найденная категория.
Если структура вашего справочника ограничена разумными пределами (например, не более 5-6 уровней), можно использовать конструкцию ВЫБОР КОГДА прямо в тексте запроса. Это позволит получить корень иерархии за один проход, не выполняя постобработку в коде.
Разберем пример такого запроса:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар,
ВЫБОР
КОГДА Номенклатура.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Номенклатура.Ссылка
КОГДА Номенклатура.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Номенклатура.Родитель
КОГДА Номенклатура.Родитель.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ТОГДА Номенклатура.Родитель.Родитель
ИНАЧЕ Номенклатура.Родитель.Родитель.Родитель
КОНЕЦ КАК КорневаяГруппа
ИЗ
Справочник.Номенклатура КАК Номенклатура
Важный нюанс: этот метод ограничен тем количеством уровней, которое вы пропишете вручную. Если менеджер создаст группу седьмого уровня, запрос вернет не корень, а родителя третьего или четвертого уровня. Однако с точки зрения производительности SQL это решение работает очень быстро, так как превращается в серию LEFT JOIN.
Чтобы не ограничивать себя фиксированным количеством уровней, мы можем использовать объект СхемаЗапроса. Это позволит программно генерировать текст запроса с нужным количеством соединений. Проанализируем концепцию, где мы "пробиваем" иерархию на заданную глубину:
Функция ПолучитьТекстЗапросаИерархии(МаксимальнаяГлубина = 10)
Схема = Новый СхемаЗапроса;
// ... настройка источников и соединений ...
// В цикле добавляем вложенные соединения таблицы самой с собой
Для н = 1 По МаксимальнаяГлубина Цикл
// Добавление логики выбора родителя следующего уровня
КонецЦикла;
Возврат Схема.ПолучитьТекстЗапроса();
КонецФункции
Такой подход объединяет преимущества первого решения (скорость выполнения на стороне сервера) и гибкость (возможность задать любую глубину). Однако стоит помнить, что чрезмерное количество соединений (более 20-30) может негативно сказаться на оптимизаторе запросов SQL-сервера.
Рассмотрим ситуацию, когда отчет строится очень часто и на огромных объемах данных. В этом случае лучшим решением будет хранение корневого родителя непосредственно в элементе справочника.
Посмотрим, как это реализовать:
Номенклатура реквизит КорневаяКатегория (тип Ссылка).ПередЗаписью пропишем логику поиска корня.Пример кода в модуле справочника:
Процедура ПередЗаписью(Отказ)
Если ЭтоГруппа Тогда
Возврат;
КонецЕсли;
ТекущийРодитель = Родитель;
Пока ЗначениеЗаполнено(ТекущийРодитель.Родитель) Цикл
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
КорневаяКатегория = ТекущийРодитель;
КонецПроцедуры
Преимущества: В запросе отчета нам достаточно просто выбрать поле Номенклатура.КорневаяКатегория. Это работает мгновенно.
Минусы: Нужно обновить данные для уже существующих записей и следить за актуальностью при перемещении элементов между группами.
Система компоновки данных обладает мощным встроенным механизмом работы с иерархией. Если ваша задача — вывести отчет, а не просто получить таблицу в коде, проанализируем возможности СКД:
Номенклатура с типом Иерархия.ДеревоЗначений. В этом дереве узлы первого уровня и будут вашими корневыми категориями.СКД самостоятельно выполняет дополнительные запросы для построения дерева, оптимизируя их количество. Это избавляет разработчика от необходимости писать сложные рекурсивные алгоритмы вручную.
Это продвинутый метод, который позволяет найти родителя на глубине до 32 или 64 уровней всего за 5-6 соединений. Мы не просто соединяем таблицу с её родителем, а на каждом шаге соединяем результат с самим собой. На первом шаге получаем родителя (1 уровень), на втором — родителя родителя (2 уровень), на третьем — родителя четвертого уровня (2^2), на четвертом — восьмого уровня (2^3) и так далее. Это математически изящный способ "схлопнуть" иерархию любой сложности за минимальное количество операций.
Выясним, какой метод выбрать в зависимости от ситуации:
Соответствие.Выбор правильного метода оптимизации позволит сократить время формирования аналитических отчетов с минут до секунд, значительно снизив нагрузку на сервер базы данных.