Как объединить строки в одну при группировке в 1С: проверенные способы и обход ограничений

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

При разработке отчетов и обработок в 1С часто возникает классическая задача: сгруппировать данные по определенному полю, а детальные текстовые значения из подчиненных строк объединить в одну строку через запятую или другой разделитель. Например, вместо нескольких записей по сотруднику Петрову нужно вывести одну строку: Петров: "01.01, 02.01, 03.01".

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

Почему стандартный язык запросов 1С не поддерживает конкатенацию строк?

Начнем с важного теоретического ограничения. В языке запросов 1С отсутствует стандартная агрегатная функция для конкатенации (объединения) строк по вертикали. Если для чисел у нас есть функция СУММА(), а для поиска крайних значений — МАКСИМУМ() и МИНИМУМ(), то аналога функции типа ОБЪЕДИНИТЬ_СТРОКИ() в запросах нет.

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

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

Если вы строите отчет на СКД, то это самый простой, быстрый и правильный путь. Платформа 1С предоставляет мощные встроенные функции компоновки, которые могут агрегировать массивы данных в одну строку на этапе расчета ресурсов.

Рассмотрим по шагам, как настроить такое объединение:

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

СоединитьСтроки(Массив(Дата), ", ")

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

Важная тонкость: борьба с дубликатами в СКД.

Если в исходной таблице даты могут повторяться, стандартный метод СоединитьСтроки(Массив(Дата)) выведет абсолютно все дубликаты (например: "01.01, 01.01, 02.01"). Чтобы получить только уникальные значения, мы должны применить более сложную системную функцию компоновки — ВычислитьВыражениеСГруппировкойМассив(). Пропишите в выражении ресурса следующую конструкцию:


СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Представление(Дата)", "Дата"), ", ")

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

Использование общих модулей в СКД:

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

ИмяОбщегоМодуля.МояФункцияСклеивания(Массив(Дата))

Внутри функции на встроенном языке вы сможете выполнить любые преобразования с переданным массивом значений.

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

Если отчет или обработка выводятся программно (без использования СКД), то лучшим решением по производительности и простоте поддержки будет обычная постобработка результата запроса. Мы получаем плоскую таблицу из запроса, а затем программно «сворачиваем» ее.

Рассмотрим оптимальный алгоритм реализации этого метода:


// 1. Получаем исходные данные простым запросом без группировки
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТЧ.Сотрудник КАК Сотрудник,
    |   ТЧ.Дата КАК Дата,
    |   ТЧ.Количество КАК Количество
    |ИЗ
    |   Документ.Начисление.Состав КАК ТЧ";

ТаблицаДанных = Запрос.Выполнить().Выгрузить();

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

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

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

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

Этот метод работает чрезвычайно быстро благодаря индексации таблицы значений по ключевому полю. На больших объемах данных (десятки тысяч строк) данный способ превосходит любые «хитрые» решения в запросах по скорости выполнения в десятки раз.

Решение №3: Метод бинарного сворачивания в запросе (сложный обходной путь)

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

Проанализируем общую логику этого подхода:

  1. Каждой строке исходной таблицы присваивается числовой индекс (от 1 до N).
  2. Создается несколько итерационных временных таблиц. В каждой последующей таблице индекс строки делится на два с округлением: ВЫРАЗИТЬ(Индекс/2 КАК ЧИСЛО(15,0)).
  3. Строки группируются по новому индексу, а левое (нечетное) и правое (четное) значение конкатенируются через конструкцию МАКСИМУМ(ВЫБОР...):

МАКСИМУМ(ВЫБОР КОГДА Индекс % 2 = 1 ТОГДА Строка ИНАЧЕ "") + ", " + МАКСИМУМ(ВЫБОР КОГДА Индекс % 2 = 0 ТОГДА Строка ИНАЧЕ "")

На первом шаге объединяются каждые 2 строки. На втором шаге полученные пары объединяются по 4, затем по 8, 16 и так далее. Чтобы объединить, например, до 1024 строк, в запросе придется прописать последовательную генерацию 10 временных таблиц.

Критические недостатки «запросного» метода:

Решение для прямых SQL-запросов (внешние источники данных)

Если вы работаете с внешними источниками данных через прямое SQL-подключение в обход стандартного движка запросов 1С, задача решается встроенными функциями баз данных:

Эти функции работают молниеносно на стороне сервера СУБД и возвращают уже готовую сгруппированную строку.

Резюме

Для решения задачи по объединению строк через разделитель при группировке всегда отдавайте предпочтение СКД (через функцию СоединитьСтроки()) или программной постобработке таблицы значений с помощью встроенного языка. Это гарантирует высокую скорость работы программы, отсутствие ошибок переполнения длины строки и сделает ваш код понятным для коллег-разработчиков.

← На главную