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