Как в отчете 1С вывести список значений в одной колонке через запятую

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке отчетов в 1С перед программистами часто возникает задача: вывести список связанных данных в одной ячейке таблицы через запятую. Типичный пример — отчет «Ведомость по товарам на складах» в конфигурации «Управление торговлей 10.3», где для каждой номенклатуры необходимо отобразить все ее места хранения на конкретном складе. Если решать эту задачу простым левым соединением в запросе, то строки отчета начнут дублироваться на каждое зарегистрированное место хранения. В результате вместо компактной ведомости мы получим раздутую таблицу с повторяющимися показателями остатков и оборотов.

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

Выясним причины, почему стандартные подходы могут давать сбой, и научимся обходить эти ограничения оптимальными методами.

---

Почему стандартное левое соединение в запросе не решает задачу?

Прежде чем перейти к написанию кода, проанализируем ситуацию. Допустим, у нас есть товар «Ноутбук», для которого на «Основном складе» задано два места хранения: «Секция А» и «Секция Б». Если мы свяжем таблицу остатков товаров с регистром сведений мест хранения по левому соединению, база данных вернет две физические строки для одной и той же номенклатуры. Каждая строка будет содержать одинаковые числовые показатели остатков, что приведет к их некорректному суммированию в итогах отчета.

Чтобы избежать искажения данных и сделать отчет удобным для чтения, нам необходимо свернуть места хранения в одну строку вида: «Секция А, Секция Б».

Рассмотрим, какими путями мы можем пойти для реализации этой логики.

---

Решение №1. Использование встроенных функций СКД

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

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

Чтобы настроить корректный вывод в СКД, выполним следующие шаги:

  1. Подготовка запроса: Оставьте связь с регистром мест хранения через ЛЕВОЕ СОЕДИНЕНИЕ в основном запросе на вкладке «Наборы данных». Это позволит получить сырые данные по местам хранения для каждой номенклатуры и склада.
  2. Настройка ресурсов: Перейдите на вкладку «Ресурсы». Именно здесь происходит агрегация данных при обсчете группировок.
  3. Добавьте поле МестоХранения в список ресурсов.
  4. В поле «Выражение» введите следующую формулу:

СоединитьСтроки(Массив(Различные МестоХранения), ", ")

Важная деталь: Использование конструкции Различные внутри функции Массив() гарантирует, что если по товару было несколько движений в рамках одного склада, места хранения не будут дублироваться в итоговой строке.

  1. В колонке «Рассчитывать по...» укажите конкретные группировки отчета, в которых должен выполняться этот расчет (например, по группировкам Номенклатура или Склад). Не указывайте расчет для детальных записей, чтобы избежать преждевременного вычисления на уровне отдельных строк СУБД.

---

Решение №2. Использование функции общего модуля в СКД (самый надежный способ)

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

Разберем этот метод по шагам:

  1. Создадим в конфигураторе новый общий модуль или используем существующий неглобальный серверный модуль. Назовем его, например, МойОтчетыПереопределяемый. Убедитесь, что у модуля установлены флажки Сервер, Клиент и Внешнее соединение.
  2. Напишем в модуле экспортную функцию, которая будет быстро получать список мест хранения по переданным параметрам:

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

МойОтчетыПереопределяемый.ПолучитьМестаХраненияСтрокой(Номенклатура, Склад)

Теперь это поле можно добавить в настройки вывода отчета (на вкладку «Выбранные поля»). Система сама вызовет код для каждой выводимой группировки, гарантируя корректный и красивый результат без удвоения числовых показателей.

---

Решение №3. Перехват и постобработка данных в Построителе отчета

А как быть, если перед нами старый отчет на обычных формах, использующий ПостроительОтчета (например, типовой Универсальный отчет в УТ 10.3), и переписывать его с нуля на СКД нецелесообразно? Нам нужно как-то внедриться в процесс между выполнением запроса и выводом результата в табличный документ.

Типовой код вывода в универсальном отчете выглядит лаконично:


ПостроительОтчета.Выполнить();
ПостроительОтчета.Вывести(ТабличныйДокумент);

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

Рассмотрим по шагам высокопроизводительный алгоритм, который не будет тормозить базу данных запросами в цикле:

  1. Исключите левое соединение мест хранения из основного запроса построителя. Основной запрос должен оставаться максимально легким и быстрым.
  2. В модуле отчета перед непосредственным выводом построителя перехватим таблицу данных:

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

// 2. Добавляем колонку, в которую будем записывать места хранения через запятую
ТаблицаДанных.Колонки.Добавить("МестаХраненияСтрокой", Новый ОписаниеТипов("Строка"));
  1. Чтобы избежать частой ошибки начинающих программистов — выполнения запроса в цикле по строкам таблицы, — мы получим все места хранения одним быстрым запросом и сопоставим их в оперативной памяти:

// 3. Выбираем все места хранения одним запросом во временную таблицу
ЗапросМест = Новый Запрос(
    "ВЫБРАТЬ
    |    МестаХранения.Номенклатура КАК Номенклатура,
    |    МестаХранения.Склад КАК Склад,
    |    МестаХранения.МестоХранения.Наименование КАК МестоХранения
    |ИЗ
    |    РегистрСведений.МестаХраненияНоменклатуры КАК МестаХранения"
);

ТаблицаМест = ЗапросМест.Выполнить().Выгрузить();

// Используем объект Соответствие для быстрой группировки в памяти
СоответствиеМест = Новый Соответствие;
Для Каждого СтрМесто Из ТаблицаМест Цикл
    // Создаем ключ связи по комбинации Номенклатура + Склад
    КлючСвязи = Новый Структура("Номенклатура, Склад", СтрМесто.Номенклатура, СтрМесто.Склад);
    
    ТекущийСписок = СоответствиеМест[КлючСвязи];
    Если ТекущийСписок = Неопределено Тогда
        ТекущийСписок = Новый Массив;
    КонецЕсли;
    ТекущийСписок.Добавить(СтрМесто.МестоХранения);
    СоответствиеМест.Вставить(КлючСвязи, ТекущийСписок);
КонецЦикла;
  1. Теперь быстро заполняем добавленную колонку в нашей основной таблице данных, используя подготовленное соответствие:

// 4. Заполняем строковую колонку мест хранения
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
    КлючПоиска = Новый Структура("Номенклатура, Склад", СтрокаДанных.Номенклатура, СтрокаДанных.Склад);
    СписокМест = СоответствиеМест[КлючПоиска];
    Если СписокМест <> Неопределено Тогда
        СтрокаДанных.МестаХраненияСтрокой = СтрСоединить(СписокМест, ", ");
    КонецЕсли;
КонецЦикла;
  1. После того как таблица полностью подготовлена, мы инициализируем новый построитель отчета (или перенастраиваем текущий) и передаем ему эту таблицу в качестве источника данных через ОписаниеИсточникаДанных:

// 5. Передаем обработанную ТЗ обратно в построитель
ПостроительОтчетаДанных = Новый ПостроительОтчета;
ПостроительОтчетаДанных.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаДанных);

// Не забываем добавить наше новое поле в список выбранных полей построителя
ПостроительОтчетаДанных.ВыбранныеПоля.Добавить("МестаХраненияСтрокой");

// 6. Выводим готовый отчет в Табличный Документ
ПостроительОтчетаДанных.Выполнить();
ПостроительОтчетаДанных.Вывести(ТабличныйДокумент);

Этот изящный прием позволяет легко расширять возможности любых старых отчетов без кардинального изменения их архитектуры и структуры сложных SQL-запросов.

---

Подведем итоги

Мы подробно разобрали три различных способа вывода списка значений через запятую в одну колонку отчета 1С. Каждый метод хорош в своей ситуации:

Выбирайте наиболее подходящий для вашей конфигурации метод и делайте ваши отчеты удобными и быстрыми!

← На главную