В практике разработчика 1С часто возникает ситуация, когда результат запроса возвращает несколько строк для одного объекта (например, список комплектующих для номенклатуры или список ролей пользователя), а нам необходимо вывести эти данные в одну строку через запятую. Стандартный язык запросов 1С, в отличие от некоторых диалектов SQL, не обладает встроенной функцией типа STRING_AGG или GROUP_CONCAT. Однако платформа предоставляет мощные инструменты в рамках Системы компоновки данных (СКД) и программные методы обработки таблиц.
В данной статье мы разберем несколько эффективных способов решения этой задачи, проанализируем их применимость в различных ситуациях и выясним, какой метод будет наиболее производительным.
Это наиболее современный и рекомендуемый метод для отчетов, построенных на базе СКД (включая «Универсальный отчет»). Начиная с версии платформы 8.3.6, в СКД появилась встроенная функция СоединитьСтроки(), которая берет на себя всю рутину по обходу группировок.
Рассмотрим по шагам, как применить эту функцию:
СписокНаименований.СоединитьСтроки(Выражение, Разделитель, ПорядокСортировки).СоединитьСтроки(Наименование, ", ", "Наименование Возр").Важный нюанс: Если мы хотим, чтобы это поле выводилось в итогах или в группировке более высокого уровня, нам нужно перейти на закладку «Ресурсы». Там мы выбираем наше вычисляемое поле и в качестве выражения ресурса снова указываем СоединитьСтроки(Наименование, ", "). Теперь при сворачивании данных по родителю, все наименования подчиненных элементов соберутся в одну красивую строку.
Проанализируем ситуацию, когда нам недостаточно простого соединения строк, а требуется, например, исключить дубли или применить сложную логику. В этом случае в СКД можно использовать комбинацию функций ВычислитьВыражениеСГруппировкойМассив и функций общих модулей.
Этот метод позволяет собрать все значения в массив прямо внутри контекста компоновки. Рассмотрим пример выражения:
Массив(ВычислитьВыражениеСГруппировкойМассив("Наименование"))
Полученный массив можно передать в функцию общего модуля, которая вернет готовую строку. Это дает полный контроль над результатом, позволяя сортировать элементы или фильтровать их по условию, которое сложно описать в стандартном запросе.
Если задача стоит вне рамок СКД — например, в динамическом списке или при формировании текста для выгрузки в файл — мы используем программный обход результата запроса. Разберем по шагам наиболее производительный алгоритм:
Во-первых, не рекомендуется использовать конкатенацию строк (оператор +) в цикле, если количество итераций велико (как в примере автора с 40+ элементами). Это связано с особенностями выделения памяти под строки в 1С. Гораздо быстрее работает метод СтрСоединить.
Проанализируем пример кода:
// Предположим, РезультатЗапроса содержит колонку "Наименование"
ТаблицаДанных = РезультатЗапроса.Выгрузить();
// Собираем все значения в массив
МассивНаименований = ТаблицаДанных.ВыгрузитьКолонку("Наименование");
// Объединяем массив в одну строку с разделителем
ИтоговаяСтрока = СтрСоединить(МассивНаименований, ", ");
// Выводим результат
Сообщить(ИтоговаяСтрока);
Если нам нужно сгруппировать строки по какому-то признаку (например, собрать товары по складам), мы можем использовать следующий подход:
ТаблицаРезультат = Новый ТаблицаЗначений;
ТаблицаРезультат.Колонки.Добавить("Склад");
ТаблицаРезультат.Колонки.Добавить("СписокТоваров");
// Группируем данные программно
Для Каждого СтрокаСклад Из ТаблицаСкладов Цикл
НоваяСтрока = ТаблицаРезультат.Добавить();
НоваяСтрока.Склад = СтрокаСклад.Склад;
// Отбираем товары этого склада из общей таблицы
ПараметрыОтбора = Новый Структура("Склад", СтрокаСклад.Склад);
СтрокиТоваров = ТаблицаВсехТоваров.НайтиСтроки(ПараметрыОтбора);
МассивТоваров = Новый Массив;
Для Каждого СтрТовар Из СтрокиТоваров Цикл
МассивТоваров.Добавить(СтрТовар.Наименование);
КонецЦикла;
НоваяСтрока.СписокТоваров = СтрСоединить(МассивТоваров, "; ");
КонецЦикла;
Как упоминалось в обсуждении, технически соединение возможно через последовательные левые соединения (ЛЕВОЕ СОЕДИНЕНИЕ) таблицы самой с собой. Однако посмотрим на проблему реалистично: если элементов больше 5-10, текст запроса превращается в нечитаемую конструкцию. При 40 элементах, как у автора вопроса, количество соединений сделает запрос крайне тяжелым для оптимизатора СУБД, что приведет к существенным задержкам или даже падению процесса выполнения.
В 1С нет поддержки рекурсивных запросов (CTE), которые позволяют решать такие задачи в стандартном SQL. Поэтому попытка решить задачу «только в запросе» при переменном количестве строк — это путь к созданию непроизводительного кода.
Поскольку «Универсальный отчет» строится на механизме СКД, для добавления такого поля без изменения кода конфигурации можно попробовать использовать «Пользовательские поля». В режиме «1С:Предприятие» при настройке отчета выберите «Изменить вариант», перейдите на вкладку «Пользовательские поля» и создайте новое поле-выражение.
Однако помните, что доступность агрегатных функций типа СоединитьСтроки в пользовательских полях может зависеть от настроек прав доступа и версии платформы. Если поле не сохраняется или выдает ошибку, лучшим решением будет добавление вычисляемого поля в саму схему через конфигуратор или расширение.
Подводя итог, можно выделить следующие рекомендации:
СоединитьСтроки() в ресурсах или вычисляемых полях.Массив и применяйте СтрСоединить().ЛЕВОЕ СОЕДИНЕНИЕ в запросах для склейки строк.STRING_AGG для MS SQL Server 2017+ или PostgreSQL).Эти методы позволяют эффективно решать задачу объединения данных, сохраняя при этом высокую производительность системы и чистоту кода.