Как развернуть строки Таблицы Значений в колонки в 1С?

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

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

Рассмотрим, как правильно решить эту задачу, разберем ограничения платформы и выясним, почему простое транспонирование матрицы здесь не подходит.

Чем группировка строк в колонки отличается от транспонирования

Проанализируем структуру данных из задачи. В исходной таблице для одного лицевого счета (ЛС) может быть зафиксировано несколько приборов учета и их показаний. Если применить классическое транспонирование, строки и столбцы просто поменяются местами (размерность M × N превратится в N × M), но сгруппировать данные по уникальному значению ЛС не удастся.

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

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

Способ 1. Программная обработка во встроенном языке (рекомендуемый вариант)

Если коллекция ТаблицаЗначений уже сформирована в памяти, наиболее быстрым и контролируемым способом является алгоритмическая обработка без повторных обращений к СУБД. Разберем этот подход по шагам:

  1. Определим максимальное количество счетчиков, приходящихся на один лицевой счет, чтобы создать необходимое число колонок.
  2. Сформируем структуру колонок результирующей таблицы значений.
  3. Отсортируем исходные данные по ключевому полю ЛС и перенесем значения в новую таблицу за один проход цикла.

Посмотрим на пример программной реализации:


// 1. Подсчитываем максимальное количество повторений для одного ЛС
СчетчикиПоЛС = Новый Соответствие;
МаксСчетчиков = 0;

Для Каждого Стр Из ТЗ1 Цикл
    ТекущееКолво = СчетчикиПоЛС[Стр.ЛС];
    Если ТекущееКолво = Неопределено Тогда
        ТекущееКолво = 1;
    Иначе
        ТекущееКолво = ТекущееКолво + 1;
    КонецЕсли;
    СчетчикиПоЛС.Вставить(Стр.ЛС, ТекущееКолво);
    
    Если ТекущееКолво > МаксСчетчиков Тогда
        МаксСчетчиков = ТекущееКолво;
    КонецЕсли;
КонецЦикла;

// 2. Создаем структуру колонок результирующей таблицы
РезультатТЗ = Новый ТаблицаЗначений;
РезультатТЗ.Колонки.Добавить("ЛС", ТЗ1.Колонки.ЛС.ТипЗначения);

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

// 3. Заполняем результирующую таблицу за один проход
ТЗ1.Сортировать("ЛС Возр");

ТекущийЛС = Неопределено;
СтрокаРез = Неопределено;
ИндексКолонки = 0;

Для Каждого Стр Из ТЗ1 Цикл
    Если Стр.ЛС <> ТекущийЛС Тогда
        ТекущийЛС = Стр.ЛС;
        СтрокаРез = РезультатТЗ.Добавить();
        СтрокаРез.ЛС = ТекущийЛС;
        ИндексКолонки = 1;
    Иначе
        ИндексКолонки = ИндексКолонки + 1;
    КонецЕсли;
    
    СтрокаРез["Счетчик_" + ИндексКолонки] = Стр.Счетчик;
    СтрокаРез["Показания_" + ИндексКолонки] = Стр.Показания;
КонецЦикла;

Этот способ работает с линейной сложностью O(N), не создает избыточной нагрузки на сервер и корректно масштабируется при любом количестве приборов учета.

Способ 2. Преобразование через запрос с левым соединением

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

Посмотрим на пример текста запроса для случая, когда на один лицевой счет приходится не более двух приборов учета:


ВЫБРАТЬ
    Т.ЛицевойСчет КАК ЛицевойСчет,
    МИНИМУМ(Т.Счетчик) КАК Счетчик1
ПОМЕСТИТЬ ВТ_ПервыйСчетчик
ИЗ
    ИсходнаяТаблица КАК Т
СГРУППИРОВАТЬ ПО
    Т.ЛицевойСчет
;

ВЫБРАТЬ
    ВТ1.ЛицевойСчет КАК ЛицевойСчет,
    ВТ1.Счетчик1 КАК Счетчик1,
    МИНИМУМ(Т.Счетчик) КАК Счетчик2
ПОМЕСТИТЬ ВТ_Счетчики
ИЗ
    ВТ_ПервыйСчетчик КАК ВТ1
    ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК Т
        ПО ВТ1.ЛицевойСчет = Т.ЛицевойСчет 
        И ВТ1.Счетчик1 < Т.Счетчик
СГРУППИРОВАТЬ ПО
    ВТ1.ЛицевойСчет,
    ВТ1.Счетчик1
;

ВЫБРАТЬ
    ВТС.ЛицевойСчет КАК ЛицевойСчет,
    ВТС.Счетчик1 КАК Счетчик1,
    Т1.Показания КАК Показания1,
    ВТС.Счетчик2 КАК Счетчик2,
    Т2.Показания КАК Показания2
ИЗ
    ВТ_Счетчики КАК ВТС
    ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК Т1
        ПО ВТС.ЛицевойСчет = Т1.ЛицевойСчет 
        И ВТС.Счетчик1 = Т1.Счетчик
    ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица КАК Т2
        ПО ВТС.ЛицевойСчет = Т2.ЛицевойСчет 
        И ВТС.Счетчик2 = Т2.Счетчик

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

Способ 3. Применение Схемы компоновки данных (СКД)

Механизм компоновки данных предоставляет встроенные средства для вывода кросс-таблиц. Если необходимо представить данные на форме или сформировать печатную форму отчета:

  1. В схеме компоновки создается набор данных «Объект», куда в качестве источника передается исходная ТаблицаЗначений.
  2. В настройках структуры отчета добавляется элемент типа Таблица.
  3. В группировку Строки выводится поле ЛицевойСчет.
  4. В группировку Колонки помещается поле Счетчик.
  5. В область Ресурсы или выбранные поля добавляются Показания.

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

← На главную