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