При разработке отчетов и обработок в 1С нередко возникает необходимость трансформировать плоскую таблицу: перенести повторяющиеся строки с разными параметрами в горизонтальные колонки для каждого ключевого объекта (лицевого счета, контрагента или номенклатуры). В реляционных базах данных такая операция называется Pivot.
Рассмотрим, как правильно решить эту задачу, разберем ограничения платформы и выясним, почему простое транспонирование матрицы здесь не подходит.
Проанализируем структуру данных из задачи. В исходной таблице для одного лицевого счета (ЛС) может быть зафиксировано несколько приборов учета и их показаний. Если применить классическое транспонирование, строки и столбцы просто поменяются местами (размерность M × N превратится в N × M), но сгруппировать данные по уникальному значению ЛС не удастся.
В нашем случае требуется сформировать новую таблицу, где каждой уникальной записи ключевого поля соответствует ровно одна строка, а зависимые данные динамически распределяются по парам колонок: Счетчик_1, Показания_1, Счетчик_2, Показания_2 и так далее.
Важное ограничение: имена колонок в объекте ТаблицаЗначений обязаны соответствовать правилам идентификаторов 1С: они не могут содержать пробелов, специальных знаков и не должны начинаться с цифры.
Если коллекция ТаблицаЗначений уже сформирована в памяти, наиболее быстрым и контролируемым способом является алгоритмическая обработка без повторных обращений к СУБД. Разберем этот подход по шагам:
ЛС и перенесем значения в новую таблицу за один проход цикла.Посмотрим на пример программной реализации:
// 1. Подсчитываем максимальное количество повторений для одного ЛС
СчетчикиПоЛС = Новый Соответствие;
МаксСчетчиков = 0;
Для Каждого Стр Из ТЗ1 Цикл
ТекущееКолво = СчетчикиПоЛС[Стр.ЛС];
Если ТекущееКолво = Неопределено Тогда
ТекущееКолво = 1;
Иначе
ТекущееКолво = ТекущееКолво + 1;
КонецЕсли;
СчетчикиПоЛС.Вставить(Стр.ЛС, ТекущееКолво);
Если ТекущееКолво > МаксСчетчиков Тогда
МаксСчетчиков = ТекущееКолво;
КонецЕсли;
КонецЦикла;
// 2. Создаем структуру колонок результирующей таблицы
РезультатТЗ = Новый ТаблицаЗначений;
РезультатТЗ.Колонки.Добавить("ЛС", ТЗ1.Колонки.ЛС.ТипЗначения);
Для Номер = 1 По МаксСчетчиков Цикл
РезультатТЗ.Колонки.Добавить("Счетчик_" + Номер, ТЗ1.Колонки.Счетчик.ТипЗначения);
РезультатТЗ.Колонки.Добавить("Показания_" + Номер, ТЗ1.Колонки.Показания.ТипЗначения);
КонецЦикла;
// 3. Заполняем результирующую таблицу за один проход
ТЗ1.Сортировать("ЛС Возр");
ТекущийЛС = Неопределено;
СтрокаРез = Неопределено;
ИндексКолонки = 0;
Для Каждого Стр Из ТЗ1 Цикл
Если Стр.ЛС <> ТекущийЛС Тогда
ТекущийЛС = Стр.ЛС;
СтрокаРез = РезультатТЗ.Добавить();
СтрокаРез.ЛС = ТекущийЛС;
ИндексКолонки = 1;
Иначе
ИндексКолонки = ИндексКолонки + 1;
КонецЕсли;
СтрокаРез["Счетчик_" + ИндексКолонки] = Стр.Счетчик;
СтрокаРез["Показания_" + ИндексКолонки] = Стр.Показания;
КонецЦикла;
Этот способ работает с линейной сложностью O(N), не создает избыточной нагрузки на сервер и корректно масштабируется при любом количестве приборов учета.
Если разворот требуется получить на этапе выборки данных или через менеджер временных таблиц, можно применить соединение таблицы самой с собой. Для этого исходные данные разделяются на первую и вторую позиции счетчика.
Посмотрим на пример текста запроса для случая, когда на один лицевой счет приходится не более двух приборов учета:
ВЫБРАТЬ
Т.ЛицевойСчет КАК ЛицевойСчет,
МИНИМУМ(Т.Счетчик) КАК Счетчик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.Счетчик
Ограничения способа: данный запрос жестко завязан на фиксированное количество колонок (в примере — строго до двух счетчиков). При увеличении количества позиций запрос становится громоздким, а квадратичная сложность соединений существенно замедляет выполнение на больших объемах данных.
Механизм компоновки данных предоставляет встроенные средства для вывода кросс-таблиц. Если необходимо представить данные на форме или сформировать печатную форму отчета:
ТаблицаЗначений.ЛицевойСчет.Счетчик.Показания.СКД автоматически развернет колонки по уникальным значениям счетчиков для каждого лицевого счета. Однако если конечной целью является получение плоской структуры ТаблицаЗначений с независимыми именами колонок, наиболее простым и эффективным остается программный обход из Способа 1.