В практике разработки на платформе 1С:Предприятие часто возникает задача, которую невозможно решить стандартными средствами языка запросов. Одна из таких задач — конкатенация (объединение) строк при группировке. Если для числовых данных мы можем использовать агрегатные функции СУММА или МАКСИМУМ, то для строковых полей встроенный SQL-движок 1С не предлагает прямого аналога функции типа string_agg или listagg.
Разберем подробно, как можно обойти это ограничение, используя Систему компоновки данных (СКД), программную постобработку и даже некоторые «хитрости» в самих запросах.
Самый правильный и надежный способ реализовать объединение строк — это использование механизмов Системы компоновки данных. В СКД предусмотрена встроенная функция агрегации СоединитьСтроки(), которая предназначена именно для этих целей.
Рассмотрим по шагам, как это настроить:
Контрагента и Комментарий без использования группировки СГРУППИРОВАТЬ ПО.Комментарий и в колонке «Выражение» напишем: СоединитьСтроки(Комментарий, ", ").Контрагент.Функция СоединитьСтроки обладает большой гибкостью. Проанализируем возможности её синтаксиса. Если нам нужно, чтобы комментарии внутри группы шли в определенном порядке (например, по дате), мы можем использовать вложенную функцию:
СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Комментарий", "Дата"), "; ")
Это позволит не просто свалить строки в кучу, а упорядочить их внутри итоговой строки.
Часто бывает так, что результат объединения нужен не в печатной форме отчета, а непосредственно в коде (в виде ТаблицаЗначений). Разберем, как программно выполнить СКД и получить данные обратно. Посмотрим на пример кода, который эффективно решает эту задачу:
// Получаем макет СКД из отчета или обработки
ОбъектОтчет = РеквизитФормыВЗначение("Отчет");
ОСКД = ОбъектОтчет.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
// Настраиваем компоновщик и параметры
НастройкиОСКД = ОСКД.НастройкиПоУмолчанию;
НастройкиОСКД.ПараметрыДанных.УстановитьЗначениеПараметра("СуммаМинимальная", 100);
// Инициализируем макет компоновки
КомпоновщикМакетаОСКД = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = КомпоновщикМакетаОСКД.Выполнить(ОСКД, НастройкиОСКД, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
// Процессор компоновки данных выполняет объединение
ПроцессорКомпоновкиОСКД = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиОСКД.Инициализировать(Макет);
// Выводим результат в Таблицу Значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ТЗ = Новый ТаблицаЗначений;
ПроцессорВывода.УстановитьОбъект(ТЗ);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиОСКД);
Этот метод является предпочтительным, так как всю тяжелую работу по склеиванию строк берет на себя платформа, оптимизируя потребление памяти.
Выясним причину, почему иногда программисты избегают СКД. В простых случаях кажется быстрее написать цикл. Если данных немного, мы можем выполнить обычный запрос без группировки по строковому полю и «схлопнуть» результат вручную.
Проанализируем ситуацию: использование метода НайтиСтроки() в цикле по большой таблице может привести к сильному замедлению. Вместо этого рассмотрим использование объекта Соответствие (хеш-таблица), который работает значительно быстрее.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Контрагент КАК Контрагент,
| Т.Комментарий КАК Комментарий
|ИЗ
| РегистрСведений.Комментарии КАК Т";
Результат = Запрос.Выполнить().Выгрузить();
// Используем Соответствие для быстрой группировки
СборкаДанных = Новый Соответствие;
Для Каждого Стр Из Результат Цикл
ТекЗначение = СборкаДанных.Получить(Стр.Контрагент);
Если ТекЗначение = Неопределено Тогда
СборкаДанных.Вставить(Стр.Контрагент, Стр.Комментарий);
Иначе
СборкаДанных.Вставить(Стр.Контрагент, ТекЗначение + ", " + Стр.Комментарий);
КонецЕсли;
КонецЦикла;
Этот подход уместен, когда количество уникальных ключей (контрагентов) не измеряется сотнями тысяч.
Существует также теоретическая возможность объединить строки прямо в запросе через итерационные соединения. Суть метода заключается в том, чтобы пронумеровать строки внутри группы и в несколько шагов соединять первую со второй, вторую с третьей и так далее.
Рассмотрим пример логики: если у нас есть 1024 строки, то за 10 итераций парного соединения (1+2, 3+4...) мы можем объединить их в одну. Однако мы настоятельно не рекомендуем этот метод для реальных задач, так как он создает колоссальную нагрузку на SQL-сервер и крайне сложен в отладке. Подобные решения скорее являются «спортивным программированием», чем промышленным стандартом.
Если ваша конфигурация работает на Microsoft SQL Server 2017 или выше, на уровне СУБД доступна функция STRING_AGG. В случае использования PostgreSQL — функция string_agg. К сожалению, 1С не транслирует свои запросы в эти специфические функции. Однако при использовании внешних источников данных или прямых запросов (что требует осторожности и соблюдения лицензионности), эти функции позволяют решить задачу мгновенно.
Важный момент: при объединении строк всегда помните об ограничении длины. Если результат превысит максимально допустимый размер строки для выбранного типа данных в БД, произойдет обрезка или ошибка выполнения.
Начиная с версии платформы 8.3.6, мы можем использовать сериализацию в JSON для передачи структурированных данных. Рассмотрим алгоритм:
Массив.ЗаписатьJSON.Хотя это не дает готовую строку «через запятую» сразу, этот метод позволяет передать неограниченное количество структурированных данных внутри одного поля запроса без потери информации.
Подведем итог: для большинства бизнес-задач оптимальным выбором будет программный вызов СКД с использованием функции СоединитьСтроки. Это решение наиболее сбалансировано с точки зрения производительности, читаемости кода и поддержки платформой.