Как объединить строки результата запроса в одну строку в 1С без СКД?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов, обработок и механизмов интеграции в 1С:Предприятии часто возникает классическая задача: необходимо объединить множество строк из результата запроса в одну текстовую строку с определенным разделителем. Например, у нас есть список серийных номеров или кодов VIN для одного документа, и мы хотим вывести их через запятую или знак «плюс» — в виде единой строки: "VIN1 + VIN2 + VIN3".

Если бы мы использовали Систему компоновки данных (СКД), задача решилась бы очень просто — с помощью функции СоединитьСтроки() в ресурсах. Однако в обычном коде (например, при подготовке данных для печати, выгрузки в файл или записи в реквизит объекта) СКД использовать не всегда удобно или возможно. В этой статье мы подробно разберем, как решить эту задачу исключительно средствами платформы и языка запросов.

Решение 1. Оптимальная постобработка результата во встроенном языке (СтрСоединить)

Это самый быстрый, надежный и рекомендуемый метод для большинства задач, если результат объединения не требуется использовать далее в этом же запросе. Вместо того чтобы пытаться решить задачу «склеивания» на уровне базы данных, мы переносим эту операцию на сторону встроенного языка 1С.

Ранее разработчики часто использовали цикл Для Каждого и оператор сложения строк «+». Но этот способ обладает серьезным недостатком: при работе с большими объемами текстовых данных постоянное перевыделение памяти под новые строки существенно замедляет работу системы. Начиная с версии платформы 8.3.6, в 1С появился замечательный метод СтрСоединить(), который работает на уровне платформы чрезвычайно эффективно.

Давайте разберем по шагам, как применить этот метод к результату запроса:


// 1. Создаем и выполняем запрос для получения необходимых данных
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТЧ_Товары.VIN КАК VIN
    |ИЗ
    |	Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
    |ГДЕ
    |	ТЧ_Товары.Ссылка = &Ссылка И VIN <> """"";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();

// 2. Выгружаем колонку с VIN напрямую в массив без лишних обходов в цикле
МассивVIN = РезультатЗапроса.Выгрузить().ВыгрузитьКолонку("VIN");

// 3. Быстро объединяем элементы массива в одну строку с нужным разделителем
ИтоговаяСтрока = СтрСоединить(МассивVIN, " + ");

Этот метод работает практически мгновенно, код получается чистым, легко читаемым и избавляет базу данных от необходимости выполнять тяжелые операции со строками.

---

Решение 2. Динамическое построение запроса (Транспонирование строк в колонки)

Что делать, если итоговую объединенную строку обязательно нужно получить внутри самого запроса (например, для помещения во временную таблицу и последующего соединения с другими таблицами базы данных), а количество исходных строк заранее неизвестно? В этом случае на помощь приходит динамическое формирование текста запроса в цикле.

Суть метода заключается в транспонировании — превращении строк в колонки. Сначала мы выясняем, сколько именно строк нам нужно объединить, а затем генерируем текст запроса, который выбирает каждую строку как отдельную колонку и складывает их между собой.

Рассмотрим пример реализации этого подхода:


// Шаг 1. Получаем список уникальных значений для объединения
ЗапросСписок = Новый Запрос;
ЗапросСписок.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	ТЧ_Товары.VIN КАК VIN
    |ИЗ
    |	Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
    |ГДЕ
    |	ТЧ_Товары.Ссылка = &Ссылка";

ЗапросСписок.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
ТаблицаДанных = ЗапросСписок.Выполнить().Выгрузить();

КоличествоСтрок = ТаблицаДанных.Количество();

Если КоличествоСтрок > 0 Тогда
    
    // Шаг 2. Начинаем динамическую сборку текста запроса
    ТекстЗапроса = "ВЫБРАТЬ ";
    
    // Генерируем выборку значений во временную таблицу
    Для Индекс = 0 По КоличествоСтрок - 1 Цикл
        ТекущееЗначение = ТаблицаДанных[Индекс].VIN;
        ТекстЗапроса = ТекстЗапроса + """" + ТекущееЗначение + """ КАК Поле_" + Формат(Индекс, "ЧГ=0");
        
        Если Индекс < КоличествоСтрок - 1 Тогда
            ТекстЗапроса = ТекстЗапроса + ", " + Символы.ПС + "        ";
        КонецЕсли;
    КонецЦикла;
    
    ТекстЗапроса = ТекстЗапроса + " ПОМЕСТИТЬ ВТ_Строки;";
    
    // Шаг 3. Собираем колонки в одну строку
    ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ВЫБРАТЬ ";
    
    Для Индекс = 0 По КоличествоСтрок - 1 Цикл
        ТекстЗапроса = ТекстЗапроса + "ВТ_Строки.Поле_" + Формат(Индекс, "ЧГ=0");
        
        Если Индекс < КоличествоСтрок - 1 Тогда
            ТекстЗапроса = ТекстЗапроса + " + "" + "" + ";
        КонецЕсли;
    КонецЦикла;
    
    ТекстЗапроса = ТекстЗапроса + " КАК ОбъединеннаяСтрока ИЗ ВТ_Строки КАК ВТ_Строки";
    
    // Шаг 4. Выполняем итоговый запрос
    ИтоговыйЗапрос = Новый Запрос;
    ИтоговыйЗапрос.Текст = ТекстЗапроса;
    Выборка = ИтоговыйЗапрос.Выполнить().Выбрать();
    
    Если Выборка.Следующая() Тогда
        Результат = Выборка.ОбъединеннаяСтрока; // Получаем готовую строку
    КонецЕсли;
КонецЕсли;

Этот способ позволяет решить задачу прямо в СУБД, но требует выполнения дополнительного предварительного запроса для анализа структуры данных.

---

Решение 3. Использование функции «АВТОНОМЕРЗАПИСИ()» при ограниченном числе строк

Если вы точно знаете, что количество объединяемых строк ограничено разумным верхним пределом (например, не может быть больше 5 или 10 строк), можно обойтись без динамической сборки кода программными методами 1С. Начиная с версии 8.3.13, в языке запросов 1С появилась функция АВТОНОМЕРЗАПИСИ(), которая позволяет пронумеровать строки при помещении во временную таблицу.

Давайте проанализируем, как использовать автонумерацию для свертки строк:


// Помещаем исходные данные во временную таблицу и нумеруем их
ВЫБРАТЬ
	ТЧ_Товары.VIN КАК VIN,
	АВТОНОМЕРЗАПИСИ() КАК НомерСтроки
ПОМЕСТИТЬ ВТ_СНомерами
ИЗ
	Документ.ПриходнаяНакладная.Товары КАК ТЧ_Товары
ГДЕ
	ТЧ_Товары.Ссылка = &Ссылка
;

// Выполняем свертку строк по номерам с помощью агрегатных функций
ВЫБРАТЬ
	МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 1 ТОГДА VIN ИНАЧЕ "" КОНЕЦ) +
	МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 2 ТОГДА " + " + VIN ИНАЧЕ "" КОНЕЦ) +
	МАКСИМУМ(ВЫБОР КОГДА НомерСтроки = 3 ТОГДА " + " + VIN ИНАЧЕ "" КОНЕЦ) КАК ВсеVIN
ИЗ
	ВТ_СНомерами КАК ВТ_СНомерами

Важное предупреждение: Использование функции АВТОНОМЕРЗАПИСИ() таит в себе скрытую ловушку платформы. СУБД не гарантирует, что при повторном использовании одного и того же менеджера временных таблиц в рамках сеанса нумерация начнется строго с единицы. Счетчик автонумерации может продолжиться дальше. Поэтому обязательно используйте индивидуальный и чистый МенеджерВременныхТаблиц для каждого выполнения такого запроса.

---

Решение 4. Прямой SQL-запрос (для крупных клиент-серверных баз)

В высоконагруженных системах, работающих на СУБД MS SQL Server или PostgreSQL, иногда критически важно переложить склеивание строк полностью на сервер базы данных для минимизации трафика и оптимизации памяти сервера приложений. В стандартном языке запросов 1С функций конкатенации строк по группе нет, но они есть в промышленных СУБД.

Если вы используете интеграцию через внешние источники данных или выполняете прямой SQL-запрос, можно применить специализированные функции:

  1. Microsoft SQL Server (версии 2017 и новее): Для объединения строк используется встроенная функция STRING_AGG(Колонка, 'Разделитель'). Для более старых версий MS SQL обычно применяли конструкцию FOR XML PATH('').
  2. PostgreSQL: В этой СУБД для аналогичных целей служит встроенная функция string_agg(Колонка, 'Разделитель').

Помните, что использование прямых SQL-запросов лишает конфигурацию переносимости (например, при переходе с MS SQL на PostgreSQL придется переписывать код) и нарушает стандарты безопасной разработки фирмы «1С». Данный метод следует использовать осознанно и только при жестких требованиях к производительности.

---

Резюме и рекомендации

Для выбора оптимального пути решения задачи конкатенации строк руководствуйтесь следующими правилами:

← На главную