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