Как преобразовать плоскую таблицу свойств в дерево значений с итогами и группировками

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

В практике разработчика 1С часто возникает задача преобразования данных из «вертикального» вида (где свойства объекта идут отдельными строками) в «горизонтальный» иерархический вид. Типичный пример: у нас есть список объектов с их цветами и размерами, и нам нужно построить Дерево значений, где верхним уровнем будет «Цвет», вторым — «Размер», а на нижнем уровне окажутся сами объекты.

В этой статье мы подробно разберем несколько способов решения этой задачи: от классических запросов до использования Системы компоновки данных (СКД).

Метод 1. Транспонирование таблицы через левые соединения в запросе

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

Рассмотрим пример запроса, который позволяет собрать свойства в одну строку для каждого объекта:


ВЫБРАТЬ
    ИсходнаяТаблица.Объект КАК Объект,
    ТаблицаЦвет.Значение КАК Цвет,
    ТаблицаРазмер.Значение КАК Размер
ИЗ
    ИсходнаяТаблица КАК ИсходнаяТаблица
        ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК ТаблицаЦвет
        ПО ИсходнаяТаблица.Объект = ТаблицаЦвет.Объект
            И ТаблицаЦвет.Свойство = "Цвет"
        ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК ТаблицаРазмер
        ПО ИсходнаяТаблица.Объект = ТаблицаРазмер.Объект
            И ТаблицаРазмер.Свойство = "Размер"
ГДЕ
    ИсходнаяТаблица.Свойство = "Цвет" // Или другой фильтр для получения уникального списка объектов
ИТОГИ ПО
    Цвет,
    Размер

Важный нюанс: использование большого количества ЛЕВОЕ СОЕДИНЕНИЕ может существенно замедлить работу базы данных, если свойств (колонок) будет много. Этот метод хорош только для фиксированного и небольшого набора свойств.

Метод 2. Оптимизированный запрос через агрегацию (МАКСИМУМ ВЫБОР)

Разберем более изящный способ получения «широкой» таблицы. Вместо множественных соединений мы можем использовать группировку с условием. Это работает быстрее и выглядит чище.

Посмотрим на пример такого запроса:


ВЫБРАТЬ
    Объект,
    МАКСИМУМ(ВЫБОР КОГДА Свойство = "Цвет" ТОГДА Значение КОНЕЦ) КАК Цвет,
    МАКСИМУМ(ВЫБОР КОГДА Свойство = "Размер" ТОГДА Значение КОНЕЦ) КАК Размер
ИЗ
    ИсходнаяТаблица
СГРУППИРОВАТЬ ПО
    Объект
ИТОГИ ПО
    Цвет,
    Размер

В данном случае мы проходим по исходной таблице всего один раз. Оператор МАКСИМУМ здесь используется для того, чтобы «схлопнуть» все строки объекта в одну, выбрав непустое значение для каждой созданной колонки.

Метод 3. Построение дерева через Систему компоновки данных (СКД)

Если ваша задача — вывести эти данные пользователю или получить готовый объект ДеревоЗначений, то самым правильным инструментом будет СКД. Система компоновки данных создана именно для таких трансформаций.

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

  1. Создайте новый отчет или схему компоновки данных.
  2. Добавьте «Внешний набор данных» (если исходная таблица уже находится в памяти) или напишите простой запрос к регистру сведений/таблице.
  3. В настройках СКД на закладке «Настройки» добавьте группировку по полю Цвет, внутрь нее добавьте группировку по полю Размер, а внутрь — детальные записи (поле Объект).
  4. Выполните компоновку. СКД автоматически создаст иерархию, обработает пустые значения и сгруппирует данные.

Преимущество: СКД позволяет пользователю самостоятельно менять порядок группировок (например, сначала Размер, потом Цвет) без переписывания программного кода.

Метод 4. Программное формирование дерева значений

Иногда требуется построить дерево программно, без использования механизмов запросов. Рассмотрим алгоритм эффективного заполнения объекта ДеревоЗначений.

Для реализации нам понадобится метод Найти() для поиска уже существующих узлов, чтобы не плодить дубли:


// Создаем структуру дерева
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("ЗначениеГруппировки");
Дерево.Колонки.Добавить("Объект");

Для Каждого СтрокаТЗ Из ИсходнаяТЗ Цикл
    
    // Ищем или создаем уровень Цвета
    СтрокаЦвет = Дерево.Строки.Найти(СтрокаТЗ.Цвет, "ЗначениеГруппировки");
    Если СтрокаЦвет = Неопределено Тогда
        СтрокаЦвет = Дерево.Строки.Добавить();
        СтрокаЦвет.ЗначениеГруппировки = СтрокаТЗ.Цвет;
    КонецЕсли;
    
    // Ищем или создаем уровень Размера внутри Цвета
    СтрокаРазмер = СтрокаЦвет.Строки.Найти(СтрокаТЗ.Размер, "ЗначениеГруппировки");
    Если СтрокаРазмер = Неопределено Тогда
        СтрокаРазмер = СтрокаЦвет.Строки.Добавить();
        СтрокаРазмер.ЗначениеГруппировки = СтрокаТЗ.Размер;
    КонецЕсли;
    
    // Добавляем конечный объект
    НоваяСтрока = СтрокаРазмер.Строки.Добавить();
    НоваяСтрока.Объект = СтрокаТЗ.Объект;
    
КонецЦикла;

Рекомендации и важные нюансы

При работе с подобными структурами данных важно учитывать следующие моменты:

  1. Типизация колонок: Если вы создаете дерево программно, убедитесь, что колонки имеют правильное ОписаниеТипов. Если в колонке «Значение» могут быть и строки, и ссылки, используйте составной тип данных.
  2. Пустые значения: Если у объекта нет значения какого-то свойства, запрос с ЛЕВОЕ СОЕДИНЕНИЕ вернет NULL. В дереве это может выглядеть как пустая ветка. Используйте функцию ЕСТЬNULL() в запросе, чтобы заменить NULL на понятную пользователю строку, например, "Не указано".
  3. Универсальность: Если состав свойств заранее неизвестен (динамические наборы), вам придется строить текст запроса программно, перебирая доступные виды свойств в цикле.

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

← На главную