В практике разработки на платформе 1С часто возникает задача трансформации таблицы: данные, которые хранятся в строках (например, список товаров с указанием категории), необходимо вывести в виде колонок, соответствующих этим категориям. Основная сложность заключается в том, чтобы данные в колонках отображались плотным списком, без образования «лесенки» или пустых ячеек (NULL). Проанализируем ситуацию, разберем причины возникновения проблемы и рассмотрим несколько эффективных способов её решения.
Представим, что у нас есть исходная таблица с двумя колонками: Товар и Категория. Если мы попытаемся вывести эти данные в отчет простым условием в запросе (например, ВЫБОР КОГДА Категория = "А" ТОГДА Товар КОНЕЦ), то для каждой категории система создаст отдельную колонку. Однако, поскольку каждая строка исходных данных относится только к одной категории, в остальных колонках этой строки будут пустые значения. В результате получится ступенчатая структура, где в каждой строке заполнена только одна ячейка.
Для решения этой задачи нам необходимо «схлопнуть» таблицу так, чтобы товары из одной категории шли друг за другом в своей колонке, начиная с первой строки. Рассмотрим, как это реализовать.
Это классический метод, который подходит, если количество категорий заранее известно и ограничено. Разберем этот алгоритм по шагам:
ROW_NUMBER(), мы используем соединение таблицы самой с собой. Мы соединяем таблицу Т1 с таблицей Т2 по условию, что ссылки (или коды) товаров в Т2 «меньше или равны» ссылке в Т1. Количество таких совпадений и будет порядковым номером строки.НомерСтроки.Рассмотрим пример логики такого запроса для двух категорий:
ВЫБРАТЬ
Т_КатА.Товар КАК ТоварА,
Т_КатБ.Товар КАК ТоварБ
ИЗ
(ВЫБРАТЬ
Т1.Товар КАК Товар,
КОЛИЧЕСТВО(Т2.Товар) КАК НомерСтроки
ИЗ
ВТ_ИсходныеДанные КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
ПО Т1.Категория = "Кат А"
И Т2.Категория = "Кат А"
И Т1.Товар >= Т2.Товар
СГРУППИРОВАТЬ ПО Т1.Товар) КАК Т_КатА
ПОЛНОЕ СОЕДИНЕНИЕ
(ВЫБРАТЬ
Т1.Товар КАК Товар,
КОЛИЧЕСТВО(Т2.Товар) КАК НомерСтроки
ИЗ
ВТ_ИсходныеДанные КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
ПО Т1.Категория = "Кат Б"
И Т2.Категория = "Кат Б"
И Т1.Товар >= Т2.Товар
СГРУППИРОВАТЬ ПО Т1.Товар) КАК Т_КатБ
ПО Т_КатА.НомерСтроки = Т_КатБ.НомерСтроки
Важный нюанс: использование неравенства в соединении для нумерации (Т1.Товар >= Т2.Товар) может работать медленно на очень больших объемах данных. В таких случаях рекомендуется предварительно индексировать временные таблицы.
Если количество категорий может меняться или их слишком много для написания сложного запроса, оптимальным будет программный метод. Мы проанализируем подход, который минимизирует количество проходов по таблице и исключает медленный поиск пустых строк.
Алгоритм действий следующий:
Соответствие, где ключом будет имя категории, а значением — Массив, в который мы будем складывать найденные товары.ТаблицаЗначений и динамически добавляем в неё колонки по именам категорий из ключей соответствия.Посмотрим на пример кода:
// 1. Сбор данных в структуру массивов
ДанныеПоКатегориям = Новый Соответствие;
Для Каждого Стр Из ИсходнаяТЗ Цикл
МассивТоваров = ДанныеПоКатегориям[Стр.Категория];
Если МассивТоваров = Неопределено Тогда
МассивТоваров = Новый Массив;
ДанныеПоКатегориям.Вставить(Стр.Категория, МассивТоваров);
КонецЕсли;
МассивТоваров.Добавить(Стр.Товар);
КонецЦикла;
// 2. Подготовка результирующей таблицы
ИтоговаяТЗ = Новый ТаблицаЗначений;
МаксСтрок = 0;
Для Каждого КлючИЗначение Из ДанныеПоКатегориям Цикл
ИтоговаяТЗ.Колонки.Добавить(КлючИЗначение.Ключ);
МаксСтрок = Макс(МаксСтрок, КлючИЗначение.Значение.Количество());
КонецЦикла;
// 3. Заполнение таблицы без пустот
Для Сч = 0 По МаксСтрок - 1 Цикл
НоваяСтр = ИтоговаяТЗ.Добавить();
Для Каждого Колонка Из ИтоговаяТЗ.Колонки Цикл
МассивДанных = ДанныеПоКатегориям[Колонка.Имя];
Если Сч < МассивДанных.Количество() Тогда
НоваяСтр[Колонка.Имя] = МассивДанных[Сч];
КонецЕсли;
КонецЦикла;
КонецЦикла;
В СКД стандартный инструмент «Таблица» с группировкой колонок по категориям по умолчанию выдаст ту самую «лесенку». Чтобы этого избежать, нам нужно обмануть систему, предоставив ей общее поле для группировки строк.
Проанализируем шаги настройки СКД:
НомерСтроки внутри каждой категории (используя метод из Решения 1 или подготовив данные программно перед передачей в СКД).Категория.НомерСтроки.Товар (с функцией Максимум или Минимум, так как для каждой комбинации категории и номера строки у нас будет только одно значение).Благодаря тому, что НомерСтроки у первого товара Категории А и первого товара Категории Б совпадает (оба имеют индекс 1), СКД выведет их в одну горизонтальную строку отчета. Это наиболее изящный способ получения визуального результата без лишнего кода.
Если задача стоит только в выводе данных на печать или в табличный документ, мы можем использовать метод присоединения вертикальных областей. Проанализируем этот процесс:
Создадим в макете одну вертикальную область (колонку). В коде сначала получим список всех уникальных категорий. Для каждой категории выполним выборку товаров. С помощью метода Вывести() выведем первую колонку, а для всех последующих категорий будем использовать метод Присоединить(). Перед выводом каждой новой категории нам нужно будет возвращать курсор (текущую строку) табличного документа в начало, чтобы товары выстраивались параллельно друг другу.
Резюме: выбор метода зависит от контекста. Для простых отчетов в СКД лучше всего подходит расчет номера строки. Для сложных преобразований внутри алгоритмов обработки данных идеальным решением станет использование Соответствия и Массивов, так как это наиболее производительный способ манипуляции объектами ТаблицаЗначений в памяти.