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

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

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

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

Почему простая конкатенация в запросе не работает?

Прежде чем перейти к решениям, выясним причину ограничения. В классическом SQL для объединения строк из разных записей при группировке существуют специальные агрегатные функции, например, STRING_AGG в Microsoft SQL Server или GROUP_CONCAT в MySQL. Однако в языке запросов платформы 1С Предприятие аналогичной встроенной функции агрегации строк нет. Стандартная конкатенация с помощью оператора «+» в запросе 1С работает только в пределах одной и той же строки (например, можно сложить код и наименование текущего элемента). Сложить строки из разных записей (строк таблицы) при группировке чисто в запросе без дополнительных ухищрений невозможно.

Тем не менее, эта задача легко решается на уровне вывода или последующей обработки данных. Рассмотрим подробнее каждый из возможных сценариев.

Способ 1. Использование функции СоединитьСтроки() в СКД

Если ваша конечная цель — вывод данных в отчет, то оптимальным, самым быстрым и производительным решением будет использование Системы компоновки данных (СКД). В СКД присутствует мощный встроенный язык выражений, который работает уже после получения «сырых» данных из СУБД.

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

  1. Откройте схему компоновки данных вашего отчета и перейдите на вкладку «Ресурсы».
  2. Добавьте поле, строки которого необходимо объединить (в нашем примере это поле Коммент), в список ресурсов.
  3. В колонке «Выражение» для этого ресурса укажите системную функцию компоновки:
    СоединитьСтроки(Коммент, ", ")
    Здесь первым параметром передается имя поля, а вторым — строка-разделитель (запятая и пробел).
  4. В колонке «Рассчитывать по...» укажите группировку, для которой должна выполняться эта склейка (например, группировка по полю Заказ).

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

СоединитьСтроки(Массив(Коммент), ", ")

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

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

МойОбщийМодуль.СоединитьСтрокиМассива(Массив(Коммент))

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

Способ 2. Программная обработка результата запроса в коде 1С

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

Для этого мы воспользуемся методом свертки таблицы значений и эффективным методом глобального контекста СтрСоединить(). Разберем по шагам этот алгоритм:

  1. Выгрузим результат запроса, содержащий детальные записи (заказы и их комментарии), в таблицу значений.
  2. Создадим копию этой таблицы и свернем ее по ключевому полю (по заказу), чтобы получить уникальный список заказов.
  3. Добавим в свернутую таблицу новую колонку для итогового объединенного комментария.
  4. Обойдем свернутую таблицу циклом, найдем все строки-оригиналы для каждого заказа с помощью метода НайтиСтроки(), соберем их в массив и объединим через разделитель.

Посмотрим на пример программного кода, реализующего данный алгоритм:


// Получаем исходную таблицу значений из запроса
ТЗ_Исходная = Запрос.Выполнить().Выгрузить();

// Создаем копию таблицы для последующей свертки
ТЗ_Свернутая = ТЗ_Исходная.Скопировать();
ТЗ_Свернутая.Свернуть("Заказ"); // оставляем только уникальные заказы

// Добавляем колонку для объединенных комментариев
ТЗ_Свернутая.Колонки.Добавить("Коммент", Новый ОписаниеТипов("Строка"));

// Обходим уникальные заказы и собираем комментарии к ним
Для Каждого СтрокаСвернутая Из ТЗ_Свернутая Цикл
    
    ПараметрыОтбора = Новый Структура("Заказ", СтрокаСвернутая.Заказ);
    НайденныеСтроки = ТЗ_Исходная.НайтиСтроки(ПараметрыОтбора);
    
    МассивКомментов = Новый Массив;
    
    Для Каждого СтрокаИсточника Из НайденныеСтроки Цикл
        Если ЗначениеЗаполнено(СтрокаИсточника.Коммент) Тогда
            МассивКомментов.Добавить(СтрокаИсточника.Коммент);
        КонецЕсли;
    КонецЦикла;
    
    // Объединяем накопленный массив строк в одну строку через запятую
    СтрокаСвернутая.Коммент = СтрСоединить(МассивКомментов, ", ");
    
КонецЦикла;

Этот метод работает чрезвычайно быстро благодаря оптимизированным платформенным механизмам поиска строк в таблицах значений и эффективной работе функции СтрСоединить().

Способ 3. Обходной путь в чистом запросе (без СКД и кода)

Иногда возникают жесткие архитектурные ограничения, когда обработку нужно выполнить строго внутри одного запроса (например, при интеграции с внешними системами через REST/ODATA или в сложных динамических списках). Существует специфический обходной путь решения этой задачи средствами СУБД.

Важное ограничение: этот метод применим только в том случае, если вы заранее точно знаете максимальное количество объединяемых строк (например, у одного заказа гарантированно не может быть более 3 комментариев).

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

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

Проанализируем пример текста такого запроса:


ВЫБРАТЬ
    ВТ_Комментарии.Заказ КАК Заказ,
    ВТ_Комментарии.Коммент КАК Коммент,
    ВТ_Комментарии.НомерСтроки КАК НомерСтроки
ПОМЕСТИТЬ ВТ_РазнумерованныеСтроки
ИЗ
    &ТаблицаИсточника КАК ВТ_Комментарии
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Заказы.Ссылка КАК Заказ,
    ЕСТЬNULL(Строка1.Коммент, "") 
        + ВЫБОР КОГДА Строка2.Коммент ЕСТЬ НЕ NULL ТОГДА ", " + Строка2.Коммент ИНАЧЕ "" КОНЕЦ
        + ВЫБОР КОГДА Строка3.Коммент ЕСТЬ НЕ NULL ТОГДА ", " + Строка3.Коммент ИНАЧЕ "" КОНЕЦ КАК ОбъединенныйКоммент
ИЗ
    Документ.ЗаказКлиента КАК Заказы
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РазнумерованныеСтроки КАК Строка1 
        ПО Строка1.Заказ = Заказы.Ссылка И Строка1.НомерСтроки = 1
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РазнумерованныеСтроки КАК Строка2 
        ПО Строка2.Заказ = Заказы.Ссылка И Строка2.НомерСтроки = 2
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РазнумерованныеСтроки КАК Строка3 
        ПО Строка3.Заказ = Заказы.Ссылка И Строка3.НомерСтроки = 3

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

← На главную