Как в 1С преобразовать строки в колонки и сгруппировать данные без пустот?

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

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

Суть проблемы: почему возникает «лесенка»?

Представим, что у нас есть исходная таблица с двумя колонками: Товар и Категория. Если мы попытаемся вывести эти данные в отчет простым условием в запросе (например, ВЫБОР КОГДА Категория = "А" ТОГДА Товар КОНЕЦ), то для каждой категории система создаст отдельную колонку. Однако, поскольку каждая строка исходных данных относится только к одной категории, в остальных колонках этой строки будут пустые значения. В результате получится ступенчатая структура, где в каждой строке заполнена только одна ячейка.

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

Решение 1: Использование запроса с нумерацией строк и полным соединением

Это классический метод, который подходит, если количество категорий заранее известно и ограничено. Разберем этот алгоритм по шагам:

  1. Создание отдельных выборок для каждой категории. Мы пишем несколько подзапросов (или используем временные таблицы), в каждом из которых выбираем товары только одной конкретной категории.
  2. Нумерация строк внутри каждой выборки. Поскольку в языке запросов 1С нет функции ROW_NUMBER(), мы используем соединение таблицы самой с собой. Мы соединяем таблицу Т1 с таблицей Т2 по условию, что ссылки (или коды) товаров в Т2 «меньше или равны» ссылке в Т1. Количество таких совпадений и будет порядковым номером строки.
  3. Полное соединение по номеру строки. На финальном этапе мы соединяем все полученные выборки по полю НомерСтроки.

Рассмотрим пример логики такого запроса для двух категорий:


ВЫБРАТЬ
    Т_КатА.Товар КАК ТоварА,
    Т_КатБ.Товар КАК ТоварБ
ИЗ
    (ВЫБРАТЬ
        Т1.Товар КАК Товар,
        КОЛИЧЕСТВО(Т2.Товар) КАК НомерСтроки
    ИЗ
        ВТ_ИсходныеДанные КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
        ПО Т1.Категория = "Кат А" 
        И Т2.Категория = "Кат А" 
        И Т1.Товар >= Т2.Товар
    СГРУППИРОВАТЬ ПО Т1.Товар) КАК Т_КатА

ПОЛНОЕ СОЕДИНЕНИЕ

    (ВЫБРАТЬ
        Т1.Товар КАК Товар,
        КОЛИЧЕСТВО(Т2.Товар) КАК НомерСтроки
    ИЗ
        ВТ_ИсходныеДанные КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК Т2
        ПО Т1.Категория = "Кат Б" 
        И Т2.Категория = "Кат Б" 
        И Т1.Товар >= Т2.Товар
    СГРУППИРОВАТЬ ПО Т1.Товар) КАК Т_КатБ

ПО Т_КатА.НомерСтроки = Т_КатБ.НомерСтроки

Важный нюанс: использование неравенства в соединении для нумерации (Т1.Товар >= Т2.Товар) может работать медленно на очень больших объемах данных. В таких случаях рекомендуется предварительно индексировать временные таблицы.

Решение 2: Программная сборка таблицы через массивы

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

Алгоритм действий следующий:

  1. Создаем объект Соответствие, где ключом будет имя категории, а значением — Массив, в который мы будем складывать найденные товары.
  2. Обходим исходную таблицу значений в цикле. Для каждой строки проверяем: если категории еще нет в соответствии, добавляем её и инициализируем новый массив. Добавляем товар в массив соответствующей категории.
  3. Определяем максимальное количество строк в будущей таблице (это длина самого большого массива в соответствии).
  4. Создаем новую ТаблицаЗначений и динамически добавляем в неё колонки по именам категорий из ключей соответствия.
  5. В цикле от 0 до «МаксКоличествоСтрок - 1» добавляем новую строку в ТЗ и заполняем ячейки данными из массивов по текущему индексу.

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


// 1. Сбор данных в структуру массивов
ДанныеПоКатегориям = Новый Соответствие;
Для Каждого Стр Из ИсходнаяТЗ Цикл
    МассивТоваров = ДанныеПоКатегориям[Стр.Категория];
    Если МассивТоваров = Неопределено Тогда
        МассивТоваров = Новый Массив;
        ДанныеПоКатегориям.Вставить(Стр.Категория, МассивТоваров);
    КонецЕсли;
    МассивТоваров.Добавить(Стр.Товар);
КонецЦикла;

// 2. Подготовка результирующей таблицы
ИтоговаяТЗ = Новый ТаблицаЗначений;
МаксСтрок = 0;
Для Каждого КлючИЗначение Из ДанныеПоКатегориям Цикл
    ИтоговаяТЗ.Колонки.Добавить(КлючИЗначение.Ключ);
    МаксСтрок = Макс(МаксСтрок, КлючИЗначение.Значение.Количество());
КонецЦикла;

// 3. Заполнение таблицы без пустот
Для Сч = 0 По МаксСтрок - 1 Цикл
    НоваяСтр = ИтоговаяТЗ.Добавить();
    Для Каждого Колонка Из ИтоговаяТЗ.Колонки Цикл
        МассивДанных = ДанныеПоКатегориям[Колонка.Имя];
        Если Сч < МассивДанных.Количество() Тогда
            НоваяСтр[Колонка.Имя] = МассивДанных[Сч];
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

Решение 3: Использование СКД (Система компоновки данных)

В СКД стандартный инструмент «Таблица» с группировкой колонок по категориям по умолчанию выдаст ту самую «лесенку». Чтобы этого избежать, нам нужно обмануть систему, предоставив ей общее поле для группировки строк.

Проанализируем шаги настройки СКД:

  1. В запросе набора данных необходимо вычислить НомерСтроки внутри каждой категории (используя метод из Решения 1 или подготовив данные программно перед передачей в СКД).
  2. В настройках отчета добавляем структуру типа «Таблица».
  3. В Группировки колонок добавляем поле Категория.
  4. В Группировки строк добавляем наше вычисляемое поле НомерСтроки.
  5. В выбранные поля (ресурсы) добавляем Товар (с функцией Максимум или Минимум, так как для каждой комбинации категории и номера строки у нас будет только одно значение).

Благодаря тому, что НомерСтроки у первого товара Категории А и первого товара Категории Б совпадает (оба имеют индекс 1), СКД выведет их в одну горизонтальную строку отчета. Это наиболее изящный способ получения визуального результата без лишнего кода.

Решение 4: Динамические секции в макете

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

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

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

← На главную