Как преобразовать таблицу дополнительных реквизитов из строк в колонки, если их количество заранее неизвестно?

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

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

Проанализируем ситуацию: стандартный язык запросов 1С требует, чтобы структура результирующей выборки была жестко описана на этапе написания кода. Мы не можем написать запрос, который сам «додумается» добавить новую колонку, если в базе появилась новая характеристика. Рассмотрим подробнее доступные инструменты и методики, которые позволяют решить эту задачу эффективно и правильно.

Метод 1. Система компоновки данных (СКД) — кросс-таблица

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

Выясним алгоритм настройки СКД для этой задачи:

  1. Создадим схему компоновки данных и добавим набор данных — запрос.
  2. Запрос должен получать данные в «плоском» виде. Например, выберем ссылки на объекты, названия их свойств и значения этих свойств:
    
    ВЫБРАТЬ
        ДополнительныеРеквизиты.Ссылка КАК Объект,
        ДополнительныеРеквизиты.Свойство КАК Свойство,
        ДополнительныеРеквизиты.Значение КАК Значение
    ИЗ
        РегистрСведений.ДополнительныеСведения КАК ДополнительныеРеквизиты
    
  3. Перейдем на закладку «Настройки». Вместо обычного детального списка добавим группировку типа «Таблица».
  4. В «Строки» таблицы добавим поле Объект.
  5. В «Колонки» таблицы добавим поле Свойство.
  6. На закладке «Ресурсы» укажем поле Значение с функцией Максимум(Значение) (поскольку для одного объекта и одного свойства значение обычно одно).

Проанализируем результат: при исполнении такого отчета СКД сама определит все уникальные значения поля Свойство, создаст для каждого из них колонку и распределит значения по ячейкам. Это именно то, что требуется в (0) сообщении форума, и, как отмечается в (18), это решение занимает всего пять минут.

Метод 2. Динамическое формирование текста запроса

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

Сначала нам нужно выяснить, какие именно свойства присутствуют в наборе данных. Выполним простой запрос для получения списка уникальных свойств. Затем, используя цикл, мы будем «достраивать» основной текст запроса, добавляя в него левые соединения или операторы ВЫБОР.

Пример кода для динамической сборки текста запроса:


ТекстВыбора = "ВЫБРАТЬ ОсновнаяТаблица.Ссылка КАК Объект";
ТекстСоединений = "ИЗ Справочник.Номенклатура КАК ОсновнаяТаблица";

// Получаем список уникальных свойств
ЗапросСвойств = Новый Запрос("ВЫБРАТЬ РАЗЛИЧНЫЕ Свойство ИЗ РегистрСведений.ДополнительныеСведения");
ВыборкаСвойств = ЗапросСвойств.Выполнить().Выбрать();

Номер = 0;
Пока ВыборкаСвойств.Следующий() Цикл
    Номер = Номер + 1;
    ИмяАлиаса = "Т_Свойство" + Номер;
    ИмяПараметра = "П_Свойство" + Номер;
    
    // Добавляем поле в секцию ВЫБРАТЬ
    ТекстВыбора = ТекстВыбора + ", 
    |	" + ИмяАлиаса + ".Значение КАК " + СтрЗаменить(ВыборкаСвойств.Свойство.Наименование, " ", "_");
    
    // Добавляем ЛЕВОЕ СОЕДИНЕНИЕ для каждого свойства
    ТекстСоединений = ТекстСоединений + "
    |ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеСведения КАК " + ИмяАлиаса + "
    |ПО " + ИмяАлиаса + ".Объект = ОсновнаяТаблица.Ссылка
    |И " + ИмяАлиаса + ".Свойство = &" + ИмяПараметра;
    
    Запрос.УстановитьПараметр(ИмяПараметра, ВыборкаСвойств.Свойство);
КонецЦикла;

Запрос.Текст = ТекстВыбора + " " + ТекстСоединений;

Важный технический нюанс: как отмечено в сообщении (17), «только запросом без собирания его текста — никак». Это связано с тем, что SQL-движок должен заранее знать количество и типы возвращаемых столбцов. Также помните о производительности: если свойств очень много (более 50–100), большое количество левых соединений может существенно замедлить выполнение запроса в СУБД.

Метод 3. Программная трансформация через Таблицу Значений

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

  1. Выполняем запрос и получаем плоский список: Объект | Свойство | Значение.
  2. Создаем новую ТаблицаЗначений.
  3. Добавляем в нее колонку «Объект».
  4. Проходим по результатам запроса и собираем все уникальные свойства. Для каждого свойства вызываем метод ТЗ.Колонки.Добавить().
  5. Заполняем таблицу:
    1. Группируем данные по объекту.
    2. Для каждой строки объекта находим нужную колонку по имени свойства и записываем туда значение.

Пример программного заполнения:


РезультатЗапроса = Запрос.Выполнить(); // Запрос: Объект, Свойство, Значение
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Объект");

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

// Теперь заполняем данными
Выборка = РезультатЗапроса.Выбрать();
ТекущийОбъект = Неопределено;
НоваяСтрока = Неопределено;

Пока Выборка.Следующий() Цикл
    Если Выборка.Объект <> ТекущийОбъект Тогда
        НоваяСтрока = ТЗ.Добавить();
        НоваяСтрока.Объект = Выборка.Объект;
        ТекущийОбъект = Выборка.Объект;
    КонецЕсли;
    
    ИмяКолонки = "Свойство_" + Выборка.Свойство.УникальныйИдентификатор();
    НоваяСтрока[ИмяКолонки] = Выборка.Значение;
КонецЦикла;

Сравнение подходов

Проанализируем, какой метод выбрать в зависимости от условий задачи:

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

← На главную