Как вывести мало связанные таблицы в одном отчете СКД рядом друг с другом?

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

При разработке отчетов на системе компоновки данных (СКД) программисты часто сталкиваются с задачей объединения разнородной информации. Типичный пример: необходимо вывести детальный отчёт по складским остаткам и оборотам номенклатуры (из регистра ТоварыНаСкладах) и одновременно показать рядом в этой же строке информацию по заказам поставщикам (из регистра ЗаказыПоставщикам). Прямая связь этих таблиц по полю Номенклатура приводит к неприятному эффекту — декартовому произведению строк. Если по товару было 5 движений по складу и 5 заказов, в отчете мы получим 25 перемноженных строк, что делает информацию абсолютно нечитаемой и перегружает систему.

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

Способ 1. Настройка структуры СКД «Таблица внутри группировки»

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

Проанализируем шаги настройки структуры:

  1. В схеме компоновки данных добавьте два набора данных — запроса. Первый набор (например, НаборДанныхСклад) выбирает движения товаров из регистра ТоварыНаСкладах. Второй набор (НаборДанныхЗаказы) выбирает информацию по заказам из регистра ЗаказыПоставщикам.
  2. Перейдите на вкладку Связи наборов данных. Свяжите два набора по полю Номенклатура. В качестве источника связи укажите первый набор, в качестве приемника — второй.
  3. Перейдите на вкладку Настройки. В структуре отчета создайте группировку по полю Номенклатура.
  4. Внутри группировки Номенклатура добавьте новый элемент структуры типа Таблица.
  5. В строках этой вложенной таблицы добавьте детальные записи или группировки по полям заказов (например, ЗаказПоставщику, КоличествоОстаток). В колонках таблицы укажите детальные записи или периоды основного отчета по складу.
  6. Чтобы отчет выглядел аккуратно, перейдите в свойства группировки Номенклатура и на вкладке Другие настройки установите параметр Расположение группировок в значение Отдельно, а Расположение реквизитов — Отдельно.

При таком подходе СКД выведет показатели складских движений и заказов в рамках одной номенклатурной позиции, аккуратно разделив их визуально и избежав дублирования основных строк.

Способ 2. Вызов функции общего модуля (строковый компромисс)

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

Разберем реализацию этого метода по шагам:

Шаг 1. Создайте неглобальный общий модуль (например, ОтчетыСервер) со свойствами Сервер и Вызов сервера. Разместите в нем следующий экспортный метод:


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

Шаг 2. В схеме компоновки данных перейдите на вкладку Вычисляемые поля. Добавьте новое поле с именем СписокЗаказов.

Шаг 3. В колонке Выражение для нового поля пропишите вызов нашей функции общего модуля:

ОтчетыСервер.ПолучитьСписокЗаказов(Номенклатура)

Шаг 4. В настройках структуры отчета добавьте вычисляемое поле СписокЗаказов в выбранные поля группировки номенклатуры. Чтобы текст внутри ячейки переносился корректно по строкам, в условном оформлении этого поля задайте свойство Размещение в значение Переносить.

Этот метод гарантирует, что структура строк отчета останется линейной и плоской, а вся дополнительная информация по заказам аккуратно отобразится в правой колонке в виде компактного списка.

Способ 3. Использование встроенной агрегатной функции СКД `СоединитьСтроки()`

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

Рассмотрим, как это настроить:

  1. Свяжите наборы данных НаборДанныхСклад и НаборДанныхЗаказы по полю Номенклатура на соответствующей вкладке.
  2. Перейдите на вкладку Ресурсы.
  3. Добавьте поле ЗаказПоставщику в список ресурсов. В качестве выражения для расчета ресурса по группировке Номенклатура укажите:

    СоединитьСтроки(ЗаказПоставщику, Символы.ПС)

    или с выводом количества:

    СоединитьСтроки(Строка(ЗаказПоставщику) + " (" + Строка(КоличествоОстаток) + ")", Символы.ПС)

  4. Добавьте этот ресурс в выбранные поля вашего отчета.

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

Способ 4. Формирование набора данных - объект (Таблица Значений)

Если логика отбора движений и заказов слишком сложна и не поддается стандартным связям СКД без потери производительности, отличным решением будет подготовка единой таблицы данных в коде 1С перед выводом отчета. Мы объединим данные в единую таблицу значений вручную и передадим её в СКД как единый Набор данных - объект.

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


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

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

Способ 5. Программное присоединение областей макета (Присоединить)

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

Проанализируем алгоритм реализации такого вывода:

  1. Создаем макет компоновки и инициализируем процессор компоновки для основного отчета (Склад).
  2. Для вывода правой таблицы (Заказы) создаем отдельную схему компоновки (или отдельный набор данных) и также инициализируем для нее собственный процессор.
  3. В цикле обхода строк основного отчета получаем текущую номенклатуру.
  4. Выводим строку левой таблицы методом Вывести().
  5. Позиционируем процессор вывода второй таблицы на данные по текущей номенклатуре, формируем область и присоединяем её справа к основной строке методом ДокументРезультат.Присоединить(ОбластьПравойТаблицы).

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

Выбирайте наиболее подходящий для вашей конфигурации метод, основываясь на объемах данных и требованиях пользователей к интерфейсу отчета. Для простых отчетов идеально подойдут способы 2 и 3, а для сложных корпоративных систем с высокими требованиями к юзабилити — способы 1, 4 и 5.

← На главную