В процессе разработки на платформе 1С:Предприятие программисты часто сталкиваются с задачей агрегации строк. Типичный пример: у одной причины может быть несколько последствий, хранящихся в табличной части. Стандартный запрос на каждую связку «Причина — Последствие» выдает отдельную строку. Если мы попытаемся использовать обычную группировку, платформа предложит нам только числовые агрегатные функции (сумма, максимум, количество), но не предложит «сложить» строки через запятую.
В языке запросов 1С (в отличие от некоторых диалектов SQL, где есть STRING_AGG или GROUP_CONCAT) нет встроенного механизма для конкатенации строк. Поэтому решение задачи переносится на уровень программной обработки результата или использования Системы компоновки данных (СКД). Рассмотрим подробнее основные способы решения этой проблемы.
Это классический и наиболее надежный метод. Мы заставляем запрос сгруппировать данные на уровне выборки, а затем обходим их иерархически. Разберем этот метод по шагам.
Сначала мы составляем запрос, используя предложение ИТОГИ ПО. Это позволит нам получить иерархическую структуру результата, где на верхнем уровне будет «владелец» (например, Причина), а на нижнем — детальные записи (Последствия).
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Причины.Ссылка КАК Причина,
| ПричиныПоследствия.Последствие КАК Последствие
|ИЗ
| Справочник.Причины КАК Причины
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Причины.Последствия КАК ПричиныПоследствия
| ПО Причины.Ссылка = ПричиныПоследствия.Ссылка
|ИТОГИ ПО
| Причина";
РезультатЗапроса = Запрос.Выполнить();
Проанализируем ситуацию далее. Теперь нам нужно правильно обработать этот результат. Вместо обычного линейного обхода мы используем обход по группировкам:
ВыборкаПричина = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПричина.Следующий() Цикл
СписокПоследствий = "";
// Выбираем детальные записи внутри текущей группировки
ВыборкаДетальныеЗаписи = ВыборкаПричина.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ЗначениеЗаполнено(ВыборкаДетальныеЗаписи.Последствие) Тогда
// Соединяем строки через запятую
СписокПоследствий = СписокПоследствий + ?(СписокПоследствий = "", "", ", ") + ВыборкаДетальныеЗаписи.Последствие;
КонецЕсли;
КонецЦикла;
// Теперь в переменной СписокПоследствий у нас строка вида "Последствие 1, Последствие 2"
// Можем выводить данные в макет или записывать в таблицу
Сообщить("Причина: " + ВыборкаПричина.Причина + " | Последствия: " + СписокПоследствий);
КонецЦикла;
Если по каким-то причинам вы не хотите использовать ИТОГИ (например, если у вас сложный пакетный запрос с временными таблицами, как у автора темы), можно выгрузить результат в ТаблицуЗначений и обработать её программно. Выясним, почему стандартный метод Свернуть() здесь не поможет напрямую: он умеет только суммировать числа или оставлять уникальные значения, но не склеивать текст.
Рассмотрим алгоритм действий:
ТЗ_Результат.ТЗ_Свернутая, в которой оставляем только колонки-группировки (например, «Причина»).ТЗ_Свернутая.Свернуть("Причина"), чтобы получить список уникальных причин.ТЗ_Свернутая колонку «СписокПоследствий» с типом Строка.ТЗ_Свернутая находим все подходящие строки в исходной ТЗ_Результат.
// Предположим, ТЗ_Результат уже заполнена данными из запроса
ТЗ_Свернутая = ТЗ_Результат.Скопировать();
ТЗ_Свернутая.Свернуть("Причина");
ТЗ_Свернутая.Колонки.Добавить("СписокПоследствий", Новый ОписаниеТипов("Строка"));
Для Каждого СтрИзСвернутой Из ТЗ_Свернутая Цикл
ПараметрыОтбора = Новый Структура("Причина", СтрИзСвернутой.Причина);
НайденныеСтроки = ТЗ_Результат.НайтиСтроки(ПараметрыОтбора);
МассивСтрок = Новый Массив;
Для Каждого Стр Из НайденныеСтроки Цикл
МассивСтрок.Добавить(Строка(Стр.Последствие));
КонецЦикла;
// Используем современную функцию СтрСоединить для быстрой склейки
СтрИзСвернутой.СписокПоследствий = СтрСоединить(МассивСтрок, ", ");
КонецЦикла;
Важный момент: использование функции СтрСоединить (доступна с версии платформы 8.3.1) гораздо предпочтительнее, чем конкатенация через оператор «+». При большом количестве строк это значительно ускоряет работу, так как система не создает новую строку в памяти на каждой итерации цикла.
Даже если вам не нужно выводить отчет, вы можете использовать Систему компоновки данных как вычислительный «движок». В СКД есть мощная встроенная функция СоединитьСтроки(), которая делает именно то, что нам нужно, прямо на уровне ресурсов.
Разберем, как это реализовать программно:
СоединитьСтроки(Последствие, ", ").Этот метод кажется сложным в настройке, но он работает быстрее всего на больших объемах данных и позволяет избежать написания громоздких циклов в коде. В выражении ресурса можно даже использовать дополнительные условия, например, СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Последствие"), "; ").
Проанализируем ситуацию, когда нам заранее известно, что количество «складываемых» значений невелико (например, не более трех). В этом случае можно обойтись чистым запросом, хотя это и считается «грязным хаком».
Идея заключается в том, чтобы пронумеровать строки внутри группы (что в 1С тоже делается непросто, через соединение таблицы самой с собой) и сделать несколько левых соединений. Затем в итоговом поле ВЫБРАТЬ использовать конструкцию:
ЕСТЬNULL(Таблица1.Значение, "")
+ ПОДСТРОКА(", ", 1, ВЫБОР КОГДА Таблица2.Значение ЕСТЬ NULL ТОГДА 0 ИНАЧЕ 2 КОНЕЦ)
+ ЕСТЬNULL(Таблица2.Значение, "")
Однако мы крайне не рекомендуем этот метод из-за плохой масштабируемости и сложности поддержки кода. Лучше придерживаться программного обхода или СКД.
Выбор конкретного метода зависит от вашей ситуации:
Помните, что попытка реализовать аналог PIVOT (разворот строк в колонки) через динамический запрос обычно приводит к созданию неудобных таблиц с переменным количеством колонок, что затрудняет дальнейшую работу с макетом. Склейка в одну строку через запятую — гораздо более гибкое и понятное решение для пользователя.