При разработке отчетов, обработок и механизмов интеграции в 1С:Предприятии часто возникает классическая задача: необходимо объединить множество строк из результата запроса в одну текстовую строку с определенным разделителем. Например, у нас есть список серийных номеров или кодов VIN для одного документа, и мы хотим вывести их через запятую или знак «плюс» — в виде единой строки: "VIN1 + VIN2 + VIN3".
Если бы мы использовали Систему компоновки данных (СКД), задача решилась бы очень просто — с помощью функции СоединитьСтроки() в ресурсах. Однако в обычном коде (например, при подготовке данных для печати, выгрузки в файл или записи в реквизит объекта) СКД использовать не всегда удобно или возможно. В этой статье мы подробно разберем, как решить эту задачу исключительно средствами платформы и языка запросов.
Это самый быстрый, надежный и рекомендуемый метод для большинства задач, если результат объединения не требуется использовать далее в этом же запросе. Вместо того чтобы пытаться решить задачу «склеивания» на уровне базы данных, мы переносим эту операцию на сторону встроенного языка 1С.
Ранее разработчики часто использовали цикл Для Каждого и оператор сложения строк «+». Но этот способ обладает серьезным недостатком: при работе с большими объемами текстовых данных постоянное перевыделение памяти под новые строки существенно замедляет работу системы. Начиная с версии платформы 8.3.6, в 1С появился замечательный метод СтрСоединить(), который работает на уровне платформы чрезвычайно эффективно.
Давайте разберем по шагам, как применить этот метод к результату запроса:
// 1. Создаем и выполняем запрос для получения необходимых данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ_Товары.VIN КАК VIN
|ИЗ
| Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
|ГДЕ
| ТЧ_Товары.Ссылка = &Ссылка И VIN <> """"";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();
// 2. Выгружаем колонку с VIN напрямую в массив без лишних обходов в цикле
МассивVIN = РезультатЗапроса.Выгрузить().ВыгрузитьКолонку("VIN");
// 3. Быстро объединяем элементы массива в одну строку с нужным разделителем
ИтоговаяСтрока = СтрСоединить(МассивVIN, " + ");
Этот метод работает практически мгновенно, код получается чистым, легко читаемым и избавляет базу данных от необходимости выполнять тяжелые операции со строками.
---
Что делать, если итоговую объединенную строку обязательно нужно получить внутри самого запроса (например, для помещения во временную таблицу и последующего соединения с другими таблицами базы данных), а количество исходных строк заранее неизвестно? В этом случае на помощь приходит динамическое формирование текста запроса в цикле.
Суть метода заключается в транспонировании — превращении строк в колонки. Сначала мы выясняем, сколько именно строк нам нужно объединить, а затем генерируем текст запроса, который выбирает каждую строку как отдельную колонку и складывает их между собой.
Рассмотрим пример реализации этого подхода:
// Шаг 1. Получаем список уникальных значений для объединения
ЗапросСписок = Новый Запрос;
ЗапросСписок.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ТЧ_Товары.VIN КАК VIN
|ИЗ
| Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
|ГДЕ
| ТЧ_Товары.Ссылка = &Ссылка";
ЗапросСписок.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
ТаблицаДанных = ЗапросСписок.Выполнить().Выгрузить();
КоличествоСтрок = ТаблицаДанных.Количество();
Если КоличествоСтрок > 0 Тогда
// Шаг 2. Начинаем динамическую сборку текста запроса
ТекстЗапроса = "ВЫБРАТЬ ";
// Генерируем выборку значений во временную таблицу
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
ТекущееЗначение = ТаблицаДанных[Индекс].VIN;
ТекстЗапроса = ТекстЗапроса + """" + ТекущееЗначение + """ КАК Поле_" + Формат(Индекс, "ЧГ=0");
Если Индекс < КоличествоСтрок - 1 Тогда
ТекстЗапроса = ТекстЗапроса + ", " + Символы.ПС + " ";
КонецЕсли;
КонецЦикла;
ТекстЗапроса = ТекстЗапроса + " ПОМЕСТИТЬ ВТ_Строки;";
// Шаг 3. Собираем колонки в одну строку
ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ВЫБРАТЬ ";
Для Индекс = 0 По КоличествоСтрок - 1 Цикл
ТекстЗапроса = ТекстЗапроса + "ВТ_Строки.Поле_" + Формат(Индекс, "ЧГ=0");
Если Индекс < КоличествоСтрок - 1 Тогда
ТекстЗапроса = ТекстЗапроса + " + "" + "" + ";
КонецЕсли;
КонецЦикла;
ТекстЗапроса = ТекстЗапроса + " КАК ОбъединеннаяСтрока ИЗ ВТ_Строки КАК ВТ_Строки";
// Шаг 4. Выполняем итоговый запрос
ИтоговыйЗапрос = Новый Запрос;
ИтоговыйЗапрос.Текст = ТекстЗапроса;
Выборка = ИтоговыйЗапрос.Выполнить().Выбрать();
Если Выборка.Следующая() Тогда
Результат = Выборка.ОбъединеннаяСтрока; // Получаем готовую строку
КонецЕсли;
КонецЕсли;
Этот способ позволяет решить задачу прямо в СУБД, но требует выполнения дополнительного предварительного запроса для анализа структуры данных.
---
Если вы точно знаете, что количество объединяемых строк ограничено разумным верхним пределом (например, не может быть больше 5 или 10 строк), можно обойтись без динамической сборки кода программными методами 1С. Начиная с версии 8.3.13, в языке запросов 1С появилась функция АВТОНОМЕРЗАПИСИ(), которая позволяет пронумеровать строки при помещении во временную таблицу.
Давайте проанализируем, как использовать автонумерацию для свертки строк:
// Помещаем исходные данные во временную таблицу и нумеруем их
ВЫБРАТЬ
ТЧ_Товары.VIN КАК VIN,
АВТОНОМЕРЗАПИСИ() КАК НомерСтроки
ПОМЕСТИТЬ ВТ_СНомерами
ИЗ
Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
ГДЕ
ТЧ_Товары.Ссылка = &Ссылка
;
// Выполняем свертку строк по номерам с помощью агрегатных функций
ВЫБРАТЬ
МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 1 ТОГДА VIN ИНАЧЕ "" КОНЕЦ) +
МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 2 ТОГДА " + " + VIN ИНАЧЕ "" КОНЕЦ) +
МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 3 ТОГДА " + " + VIN ИНАЧЕ "" КОНЕЦ) КАК ВсеVIN
ИЗ
ВТ_СНомерами КАК ВТ_СНомерами
Важное предупреждение: Использование функции АВТОНОМЕРЗАПИСИ() таит в себе скрытую ловушку платформы. СУБД не гарантирует, что при повторном использовании одного и того же менеджера временных таблиц в рамках сеанса нумерация начнется строго с единицы. Счетчик автонумерации может продолжиться дальше. Поэтому обязательно используйте индивидуальный и чистый МенеджерВременныхТаблиц для каждого выполнения такого запроса.
---
В высоконагруженных системах, работающих на СУБД MS SQL Server или PostgreSQL, иногда критически важно переложить склеивание строк полностью на сервер базы данных для минимизации трафика и оптимизации памяти сервера приложений. В стандартном языке запросов 1С функций конкатенации строк по группе нет, но они есть в промышленных СУБД.
Если вы используете интеграцию через внешние источники данных или выполняете прямой SQL-запрос, можно применить специализированные функции:
STRING_AGG(Колонка, 'Разделитель'). Для более старых версий MS SQL обычно применяли конструкцию FOR XML PATH('').string_agg(Колонка, 'Разделитель').Помните, что использование прямых SQL-запросов лишает конфигурацию переносимости (например, при переходе с MS SQL на PostgreSQL придется переписывать код) и нарушает стандарты безопасной разработки фирмы «1С». Данный метод следует использовать осознанно и только при жестких требованиях к производительности.
---
Для выбора оптимального пути решения задачи конкатенации строк руководствуйтесь следующими правилами: