При разработке отчетов в 1С перед программистами часто возникает задача: вывести список связанных данных в одной ячейке таблицы через запятую. Типичный пример — отчет «Ведомость по товарам на складах» в конфигурации «Управление торговлей 10.3», где для каждой номенклатуры необходимо отобразить все ее места хранения на конкретном складе. Если решать эту задачу простым левым соединением в запросе, то строки отчета начнут дублироваться на каждое зарегистрированное место хранения. В результате вместо компактной ведомости мы получим раздутую таблицу с повторяющимися показателями остатков и оборотов.
В этой статье мы подробно разберем, как решить эту проблему. Мы рассмотрим несколько эффективных способов: от современных механизмов Системы компоновки данных (СКД) до программной модификации Построителя отчета, который лежит в основе Универсального отчета в обычных формах.
Выясним причины, почему стандартные подходы могут давать сбой, и научимся обходить эти ограничения оптимальными методами.
---
Прежде чем перейти к написанию кода, проанализируем ситуацию. Допустим, у нас есть товар «Ноутбук», для которого на «Основном складе» задано два места хранения: «Секция А» и «Секция Б». Если мы свяжем таблицу остатков товаров с регистром сведений мест хранения по левому соединению, база данных вернет две физические строки для одной и той же номенклатуры. Каждая строка будет содержать одинаковые числовые показатели остатков, что приведет к их некорректному суммированию в итогах отчета.
Чтобы избежать искажения данных и сделать отчет удобным для чтения, нам необходимо свернуть места хранения в одну строку вида: «Секция А, Секция Б».
Рассмотрим, какими путями мы можем пойти для реализации этой логики.
---
Если ваш отчет построен (или переводится) на Системе компоновки данных, задача решается штатными агрегатными функциями. Однако здесь часто совершают ошибку, из-за которой в колонку выводится только одно значение вместо списка. Для быстрого обхода этой проблемы пригодится готовый пример настройки СКД для объединения значений из нескольких строк.
Выясним причину этой ошибки. Если использовать функции сворачивания строк в детальных записях без указания правильных настроек группировки, СКД рассчитывает выражение для каждой отдельной физической строки выборки. Так как в исходной выборке на одну детальную запись приходится ровно одно место хранения, функция возвращает его в единственном экземпляре.
Чтобы настроить корректный вывод в СКД, выполним следующие шаги:
ЛЕВОЕ СОЕДИНЕНИЕ в основном запросе на вкладке «Наборы данных». Это позволит получить сырые данные по местам хранения для каждой номенклатуры и склада.МестоХранения в список ресурсов.
СоединитьСтроки(Массив(Различные МестоХранения), ", ")
Важная деталь: Использование конструкции Различные внутри функции Массив() гарантирует, что если по товару было несколько движений в рамках одного склада, места хранения не будут дублироваться в итоговой строке.
Номенклатура или Склад). Не указывайте расчет для детальных записей, чтобы избежать преждевременного вычисления на уровне отдельных строк СУБД.---
Бывают ситуации, когда структура СКД достаточно сложная, отчет содержит много вложенных группировок, и стандартные агрегатные функции СКД начинают работать непредсказуемо. В этом случае лучшим и наиболее производительным решением будет делегирование сборки строки программному коду 1С.
Разберем этот метод по шагам:
МойОтчетыПереопределяемый. Убедитесь, что у модуля установлены флажки Сервер, Клиент и Внешнее соединение.
Функция ПолучитьМестаХраненияСтрокой(Номенклатура, Склад) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МестаХраненияНоменклатуры.МестоХранения.Наименование КАК Место
|ИЗ
| РегистрСведений.МестаХраненияНоменклатуры КАК МестаХраненияНоменклатуры
|ГДЕ
| МестаХраненияНоменклатуры.Номенклатура = &Номенклатура
| И МестаХраненияНоменклатуры.Склад = &Склад";
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
Запрос.УстановитьПараметр("Склад", Склад);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат "";
КонецЕсли;
Выборка = Результат.Выбрать();
МассивМест = Новый Массив;
Пока Выборка.Следующий() Цикл
МассивМест.Добавить(Выборка.Место);
КонецЦикла;
Возврат СтрСоединить(МассивМест, ", ");
КонецФункции
МестаХраненияСтрокой.
МойОтчетыПереопределяемый.ПолучитьМестаХраненияСтрокой(Номенклатура, Склад)
Теперь это поле можно добавить в настройки вывода отчета (на вкладку «Выбранные поля»). Система сама вызовет код для каждой выводимой группировки, гарантируя корректный и красивый результат без удвоения числовых показателей.
---
А как быть, если перед нами старый отчет на обычных формах, использующий ПостроительОтчета (например, типовой Универсальный отчет в УТ 10.3), и переписывать его с нуля на СКД нецелесообразно? Нам нужно как-то внедриться в процесс между выполнением запроса и выводом результата в табличный документ.
Типовой код вывода в универсальном отчете выглядит лаконично:
ПостроительОтчета.Выполнить();
ПостроительОтчета.Вывести(ТабличныйДокумент);
Кажется, что вмешаться в этот процесс невозможно. Однако мы можем применить следующий подход: перехватить результат выполнения запроса построителя, дополнить его нужной нам текстовой колонкой программно и скормить обработанную таблицу обратно построителю как новый источник данных.
Рассмотрим по шагам высокопроизводительный алгоритм, который не будет тормозить базу данных запросами в цикле:
// 1. Выполняем исходный запрос построителя и получаем чистые данные
ПостроительОтчета.Выполнить();
ТаблицаДанных = ПостроительОтчета.Result.Выгрузить();
// 2. Добавляем колонку, в которую будем записывать места хранения через запятую
ТаблицаДанных.Колонки.Добавить("МестаХраненияСтрокой", Новый ОписаниеТипов("Строка"));
// 3. Выбираем все места хранения одним запросом во временную таблицу
ЗапросМест = Новый Запрос(
"ВЫБРАТЬ
| МестаХранения.Номенклатура КАК Номенклатура,
| МестаХранения.Склад КАК Склад,
| МестаХранения.МестоХранения.Наименование КАК МестоХранения
|ИЗ
| РегистрСведений.МестаХраненияНоменклатуры КАК МестаХранения"
);
ТаблицаМест = ЗапросМест.Выполнить().Выгрузить();
// Используем объект Соответствие для быстрой группировки в памяти
СоответствиеМест = Новый Соответствие;
Для Каждого СтрМесто Из ТаблицаМест Цикл
// Создаем ключ связи по комбинации Номенклатура + Склад
КлючСвязи = Новый Структура("Номенклатура, Склад", СтрМесто.Номенклатура, СтрМесто.Склад);
ТекущийСписок = СоответствиеМест[КлючСвязи];
Если ТекущийСписок = Неопределено Тогда
ТекущийСписок = Новый Массив;
КонецЕсли;
ТекущийСписок.Добавить(СтрМесто.МестоХранения);
СоответствиеМест.Вставить(КлючСвязи, ТекущийСписок);
КонецЦикла;
// 4. Заполняем строковую колонку мест хранения
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
КлючПоиска = Новый Структура("Номенклатура, Склад", СтрокаДанных.Номенклатура, СтрокаДанных.Склад);
СписокМест = СоответствиеМест[КлючПоиска];
Если СписокМест <> Неопределено Тогда
СтрокаДанных.МестаХраненияСтрокой = СтрСоединить(СписокМест, ", ");
КонецЕсли;
КонецЦикла;
ОписаниеИсточникаДанных:
// 5. Передаем обработанную ТЗ обратно в построитель
ПостроительОтчетаДанных = Новый ПостроительОтчета;
ПостроительОтчетаДанных.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаДанных);
// Не забываем добавить наше новое поле в список выбранных полей построителя
ПостроительОтчетаДанных.ВыбранныеПоля.Добавить("МестаХраненияСтрокой");
// 6. Выводим готовый отчет в Табличный Документ
ПостроительОтчетаДанных.Выполнить();
ПостроительОтчетаДанных.Вывести(ТабличныйДокумент);
Этот изящный прием позволяет легко расширять возможности любых старых отчетов без кардинального изменения их архитектуры и структуры сложных SQL-запросов.
---
Мы подробно разобрали три различных способа вывода списка значений через запятую в одну колонку отчета 1С. Каждый метод хорош в своей ситуации:
Выбирайте наиболее подходящий для вашей конфигурации метод и делайте ваши отчеты удобными и быстрыми!