В процессе разработки на платформе 1С:Предприятие часто возникает задача вывода данных типа "один ко многим" в плоский список. Типичный пример: у одного основного объекта (например, "Риск") может быть несколько подчиненных объектов (например, "Причины"). Если использовать обычное соединение в запросе, система вернет столько строк, сколько существует причин, дублируя при этом основной объект. Наша цель — сгруппировать все причины и записать их через запятую в одну ячейку табличной части. Рассмотрим, как это сделать правильно и эффективно.
Когда мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ или ЛЕВОЕ СОЕДИНЕНИЕ между владельцем и подчиненным справочником, результат запроса выглядит избыточным. Проанализируем ситуацию: если для одного риска существует три причины, запрос выдаст три записи. При обычном обходе выборки в табличную часть добавятся три разные строки, что не соответствует требованиям интерфейса. Нам же необходимо получить одну строку, где в колонке Риск будет ссылка на объект, а в колонке Причина — текстовый перечень всех связанных элементов.
Наиболее элегантный способ решения этой задачи на уровне запроса — использование секции ИТОГИ ПО. Это позволяет структурировать результат запроса в виде иерархического дерева, где на верхнем уровне находятся наши основные объекты (риски), а на подчиненном — детальные записи (причины).
Разберем пример построения такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Услуги.Ссылка КАК Риск,
| ПричиныРисков.Ссылка КАК Причина
|ИЗ
| Справочник.Услуги КАК Услуги
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПричиныРисков КАК ПричиныРисков
| ПО Услуги.Ссылка = ПричиныРисков.Владелец
|ГДЕ
| Услуги.Ссылка В ИЕРАРХИИ(&Подразделение)
| И Услуги.ЭтоГруппа = ЛОЖЬ
| И ПричиныРисков.ПометкаУдаления = ЛОЖЬ
|ИТОГИ ПО
| Риск";
Запрос.УстановитьПараметр("Подразделение", Объект.Подразделение);
РезультатЗапроса = Запрос.Выполнить();
После выполнения запроса с итогами нам необходимо правильно обработать результат. Обычный метод Выбрать() здесь не подойдет — нам нужно использовать ОбходРезультатаЗапроса.ПоГруппировкам. Это позволит нам создать внешний цикл для формирования строк табличной части и вложенный цикл для сбора всех причин текущего риска в одну строку.
Рассмотрим алгоритм обработки:
Если ваша конфигурация работает на современной платформе без режима совместимости со старыми версиями (ниже 8.3.6), наиболее удобным способом будет использование встроенной функции СтрСоединить. Она автоматически объединяет элементы массива в строку с заданным разделителем.
ВыборкаРисков = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаРисков.Следующий() Цикл
// Создаем строку в ТЧ только один раз для каждого риска
НоваяСтрока = Объект.Причины.Добавить();
НоваяСтрока.Риск = ВыборкаРисков.Риск;
// Собираем причины во вложенном цикле
МассивПричин = Новый Массив;
ВыборкаДетальные = ВыборкаРисков.Выбрать();
Пока ВыборкаДетальные.Следующий() Цикл
// Добавляем наименование в массив
МассивПричин.Добавить(Строка(ВыборкаДетальные.Причина));
КонецЦикла;
// Объединяем массив в одну строку через запятую
НоваяСтрока.Причина = СтрСоединить(МассивПричин, ", ");
КонецЦикла;
Если при попытке использовать СтрСоединить система выдает ошибку "Функция не определена", значит, используется платформа версии ниже 8.3.6 или включен соответствующий режим совместимости. В этом случае мы реализуем объединение строк вручную с помощью обычного накопления в переменную.
Проанализируем пример кода с ручной конкатенацией:
ВыборкаРисков = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаРисков.Следующий() Цикл
НоваяСтрока = Объект.Причины.Добавить();
НоваяСтрока.Риск = ВыборкаРисков.Риск;
ТекстПричин = "";
ВыборкаДетальные = ВыборкаРисков.Выбрать();
Пока ВыборкаДетальные.Следующий() Цикл
НаименованиеПричины = Строка(ВыборкаДетальные.Причина);
// Проверяем, пустая ли строка, чтобы не ставить лишнюю запятую в начале
Если ПустаяСтрока(ТекстПричин) Тогда
ТекстПричин = НаименованиеПричины;
Иначе
ТекстПричин = ТекстПричин + ", " + НаименованиеПричины;
КонецЕсли;
КонецЦикла;
НоваяСтрока.Причина = ТекстПричин;
КонецЦикла;
При реализации подобных механизмов стоит обратить внимание на производительность, особенно если справочники содержат тысячи записей. Рассмотрим несколько советов:
ВременнаяТаблица, проиндексировать поле Ссылка, и только потом соединять с причинами.ЭтоГруппа и ПометкаУдаления в тексте запроса, чтобы не обрабатывать лишние данные.Причина в табличной части должен иметь тип Строка достаточной длины (или быть неограниченной длины), иначе длинный список причин будет обрезан.В ИЕРАРХИИ в запросах к большим справочникам может быть медленным. Если структура иерархии известна заранее, эффективнее передать массив ссылок.Таким образом, мы выяснили, что задача "запихивания" нескольких элементов в один реквизит решается не на уровне плоской выборки, а путем правильной группировки данных в запросе через ИТОГИ ПО и последующей обработки вложенных коллекций в коде 1С.