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

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

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

Проблема стандартного метода Свернуть()

Метод Свернуть(КолонкиГруппировки, КолонкиСуммирования) работает по простому принципу: строки с одинаковыми значениями в «колонках группировки» объединяются, а значения в «колонках суммирования» складываются. Если в колонку суммирования передать строковое поле, платформа попытается привести его к числу, что зачастую приводит к обнулению данных или некорректным результатам. Наша задача — реализовать алгоритм, который позволит суммировать числа и объединять строки (делать конкатенацию).

Способ 1. Использование вспомогательной таблицы и метода НайтиСтроки

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

  1. Создаем копию исходной таблицы и оставляем в ней только нужные колонки для группировки и сумм.
  2. Сворачиваем копию стандартным методом.
  3. Добавляем в свернутую таблицу пустую колонку для строкового результата.
  4. В цикле по свернутой таблице ищем все соответствующие строки в исходной таблице и склеиваем строки.

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


// Предположим, ТаблицаИсходная имеет колонки: Контрагент, Итог, Договор
ТаблицаРезультата = ТаблицаИсходная.Скопировать();
// Сворачиваем только по ключевому полю и числам
ТаблицаРезультата.Свернуть("Контрагент", "Итог"); 

// Для ускорения работы обязательно добавляем индекс по ключевому полю в исходной таблице
ТаблицаИсходная.Индексы.Добавить("Контрагент");

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

Проанализируем ситуацию с производительностью: использование ТаблицаИсходная.Индексы.Добавить() критически важно. Без индекса поиск методом НайтиСтроки на больших таблицах будет работать крайне медленно, так как системе придется каждый раз перебирать всю исходную таблицу целиком.

Способ 2. Перебор отсортированной таблицы с удалением дублей

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

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

  1. Сортируем таблицу по колонке группировки.
  2. Обходим таблицу циклом (лучше делать это с конца или использовать вспомогательный массив для удаления).
  3. Сравниваем значение в текущей строке со значением в предыдущей.
  4. Если они совпадают — суммируем данные в «базовую» строку, а текущую помечаем на удаление.

Пример реализации с использованием массива для удаления строк:


Таблица.Сортировать("Контрагент");
МассивСтрокДляУдаления = Новый Массив;
ТекущийКонтрагент = Неопределено;
СтрокаДляНакопления = Неопределено;

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

// Удаляем ненужные строки
Для Каждого СтрокаДляУдаления Из МассивСтрокДляУдаления Цикл
    Таблица.Удалить(СтрокаДляУдаления);
КонецЦикла;

Способ 3. Использование функции СтрСоединить (для платформы 8.3.6 и выше)

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


// Сначала получаем уникальный список контрагентов со свернутыми суммами
ТаблицаРезультат = ТаблицаИсходная.Скопировать();
ТаблицаРезультат.Свернуть("Контрагент", "Итог");
ТаблицаРезультат.Колонки.Добавить("ДоговорыСписком");

ТаблицаИсходная.Индексы.Добавить("Контрагент");

Для Каждого СтрокаРез Из ТаблицаРезультат Цикл
    МассивДоговоров = Новый Массив;
    
    НайденныеСтроки = ТаблицаИсходная.НайтиСтроки(Новый Структура("Контрагент", СтрокаРез.Контрагент));
    Для Каждого СтрИсходная Из НайденныеСтроки Цикл
        // Собираем все договоры в массив
        Если ЗначениеЗаполнено(СтрИсходная.Договор) Тогда
            МассивДоговоров.Добавить(Строка(СтрИсходная.Договор));
        КонецЕсли;
    КонецЦикла;
    
    // Эффективно объединяем массив в строку
    СтрокаРез.ДоговорыСписком = СтрСоединить(МассивДоговоров, ", ");
КонецЦикла;

Вариант для продвинутых: Группировка через Запрос

Многие разработчики предпочитают использовать Запрос для обработки данных. Однако стоит помнить, что в языке запросов 1С нет функции аналогичной GROUP_CONCAT из SQL. Поэтому рассмотрим комбинированный метод:

  1. Загружаем таблицу во временную таблицу запроса.
  2. Запросом получаем сгруппированные суммы и список контрагентов.
  3. Выгружаем результат запроса с обходом по группировке «Контрагент».
  4. Внутри детальных записей группировки программно собираем строку договоров.

Это решение будет самым «красивым» с точки зрения архитектуры 1С, так как разделяет ответственность: СУБД (или механизм запросов) считает суммы, а программный код занимается обработкой строк.

Рекомендации по оптимизации

При выборе метода обратите внимание на следующие нюансы:

Таким образом, мы разобрали основные способы «ручной» свертки таблицы значений. Выбор конкретного варианта зависит от версии вашей платформы и личных предпочтений в стиле кодирования. Наиболее универсальным и производительным для современной 1С 8.3 является сочетание предварительной свертки сумм и последующего заполнения строковых полей через СтрСоединить.

← На главную