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