Как в 1С вывести разнородные данные с разной структурой группировок под одной сущностью в СКД?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

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

---

Метод 1. Программная постобработка и вывод «по-старинке» (Гибридный метод)

Этот метод часто называют компромиссным. Мы не отказываемся от СКД полностью — ведь она предоставляет удобные механизмы для наложения отборов, работы с параметрами и условного оформления. Вместо этого мы используем СКД исключительно для получения и компоновки «сырых» данных, после чего выгружаем их в дерево значений и выводим в итоговый табличный документ с помощью собственного макета.

Разберем этот процесс по шагам.

Шаг 1. Настройка схемы СКД

В схеме компоновки данных мы настраиваем два набора данных-запроса (один для реализации, второй для поступлений) и связываем их по полю Объект. На вкладке «Настройки» создаем структуру, которая возвращает данные в иерархическом виде (например, группировка по полю Объект, а внутри нее — детальные записи).

Шаг 2. Программное выполнение компоновки и обход дерева

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

Ниже представлен пример кода, реализующий этот алгоритм:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    // 1. Инициализируем схему и настройки
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    // 2. Компонуем макет данных
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    // 3. Выполняем компоновку
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
    
    // 4. Выгружаем данные в Дерево Значений
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
    ДеревоРезультата = Новый ДеревоЗначений;
    ПроцессорВывода.УстановитьОбъект(ДеревоРезультата);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);
    
    // 5. Выводим данные в Табличный Документ по нашему макету
    ВывестиДанныеВТабличныйДокумент(ДокументРезультат, ДеревоРезультата);
    
КонецПроцедуры

Процедура ВывестиДанныеВТабличныйДокумент(ТабДок, ДеревоРезультата)
    
    ТабДок.Очистить();
    Макет = ПолучитьМакет("МакетРучной"); // Наш собственный макет оформления
    
    // Выводим шапку отчета
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    ТабДок.Вывести(ОбластьШапка);
    
    // Обходим первый уровень группировки — Объекты
    Для Каждого СтрокаОбъект Из ДеревоРезультата.Строки Цикл
        
        ОбластьОбъект = Макет.ПолучитьОбласть("Объект");
        ОбластьОбъект.Параметры.Заполнить(СтрокаОбъект);
        ТабДок.Вывести(ОбластьОбъект);
        
        // Получаем подчиненные коллекции для реализации и поступлений
        // (Предполагается, что в запросе мы добавили разделитель "ТипДанных")
        ПараметрыОтбораРеализаций = Новый Структура("ТипДанных", "Реализация");
        СтрокиРеализации = СтрокаОбъект.Строки.НайтиСтроки(ПараметрыОтбораРеализаций, Истина);
        
        ПараметрыОтбораПоступлений = Новый Структура("ТипДанных", "Поступление");
        СтрокиПоступления = СтрокаОбъект.Строки.НайтиСтроки(ПараметрыОтбораПоступлений, Истина);
        
        // Определяем, какая таблица длиннее, чтобы вывести их параллельно
        МаксКоличествоСтрок = Макс(СтрокиРеализации.Количество(), СтрокиПоступления.Количество());
        
        Для Индекс = 0 По МаксКоличествоСтрок - 1 Цикл
            
            ОбластьСтрокаДанных = Макет.ПолучитьОбласть("СтрокаДанных");
            
            // Заполняем левую часть строки (Реализация)
            Если Индекс < СтрокиРеализации.Количество() Тогда
                ТекущаяРеализация = СтрокиРеализации[Индекс];
                ОбластьСтрокаДанных.Параметры.РеализацияДокумент = ТекущаяРеализация.Документ;
                ОбластьСтрокаДанных.Параметры.РеализацияСумма    = ТекущаяРеализация.Сумма;
            Иначе
                ОбластьСтрокаДанных.Параметры.РеализацияДокумент = "";
                ОбластьСтрокаДанных.Параметры.РеализацияСумма    = 0;
            КонецЕсли;
            
            // Заполняем правую часть строки (Поступление)
            Если Индекс < СтрокиПоступления.Количество() Тогда
                ТекущееПоступление = СтрокиПоступления[Индекс];
                ОбластьСтрокаДанных.Параметры.ПоступлениеДокумент = ТекущееПоступление.Документ;
                ОбластьСтрокаДанных.Параметры.ПоступлениеСумма    = ТекущееПоступление.Сумма;
            Иначе
                ОбластьСтрокаДанных.Параметры.ПоступлениеДокумент = "";
                ОбластьСтрокаДанных.Параметры.ПоступлениеСумма    = 0;
            КонецЕсли;
            
            ТабДок.Вывести(ОбластьСтрокаДанных);
            
        КонецЦикла;
        
    КонецЦикла;
    
КонецПроцедуры

Этот способ гарантирует 100% контроль над геометрией колонок и выводом строк, однако лишает пользователя возможности самостоятельно менять структуру отчета (менять местами колонки, добавлять новые пользовательские группировки) через настройки отчета.

---

Метод 2. Метод сквозной нумерации (Полное соединение по номеру строки в запросе)

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

Идея заключается в том, чтобы пронумеровать строки внутри каждого Объекта отдельно для реализации и отдельно для поступлений. Затем мы соединяем эти таблицы по условию равенства объектов и равенства номеров строк.

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    Реализация.Объект КАК Объект,
    Реализация.Ссылка КАК ДокументРеализации,
    Реализация.СуммаДокумента КАК СуммаРеализации,
    ROW_NUMBER() OVER (PARTITION BY Реализация.Объект ORDER BY Реализация.МоментВремени) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Реализации
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Проведен;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Поступление.Объект КАК Объект,
    Поступление.Ссылка КАК ДокументПоступления,
    Поступление.СуммаДокумента КАК СуммаПоступления,
    ROW_NUMBER() OVER (PARTITION BY Поступление.Объект ORDER BY Поступление.МоментВремени) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_Поступления
ИЗ
    Документ.ПоступлениеТоваровУслуг КАК Поступление
ГДЕ
    Поступление.Проведен;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЕСТЬNULL(ВТ_Реализации.Объект, ВТ_Поступления.Объект) КАК Объект,
    ВТ_Реализации.ДокументРеализации КАК ДокументРеализации,
    ВТ_Реализации.СуммаРеализации КАК СуммаРеализации,
    ВТ_Поступления.ДокументПоступления КАК ДокументПоступления,
    ВТ_Поступления.СуммаПоступления КАК СуммаПоступления
ИЗ
    ВТ_Реализации КАК ВТ_Реализации
        ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Поступления КАК ВТ_Поступления
        ПО ВТ_Реализации.Объект = ВТ_Поступления.Объект
            И ВТ_Реализации.НомерСтроки = ВТ_Поступления.НомерСтроки

Почему это работает:

Благодаря оконной функции ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) мы получаем уникальный порядковый номер для каждого документа внутри конкретного объекта. При полном соединении по двум полям (Объект и НомерСтроки) первая строка реализации сопоставится с первой строкой поступления, вторая со второй и так далее. Если у одного объекта документов реализации больше, чем поступлений, то в колонках поступлений выведутся значения NULL, не вызывая дублирования и умножения данных.

---

Метод 3. Параллельные группировки и связи наборов данных в СКД

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

Для этого в схеме компоновки данных мы создаем три независимых набора данных:

  1. НаборДанных_Объекты: Запрос, возвращающий только уникальный список объектов: ВЫБРАТЬ Ссылка КАК Объект ИЗ Справочник.ОбъектыСтроительства
  2. НаборДанных_Реализации: Запрос, выбирающий документы реализации с их реквизитами.
  3. НаборДанных_Поступления: Запрос, выбирающий документы поступления с их реквизитами (или дерево значений, если данные иерархические).

Настройка связей наборов данных:

На вкладке «Связи наборов данных» мы добавляем две строки:

Настройка структуры отчета:

Для того чтобы вывести данные параллельно в рамках одного объекта, мы используем элемент структуры «Таблица»:

---

Метод 4. Использование вложенных схем компоновки данных

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

Посмотрим, как это реализовать:

  1. Создаем основную схему компоновки, где главным набором данных будет список объектов.
  2. В настройках основной СКД для группировки Объект добавляем два вложенных отчета: «Реализация» и «Поступления».
  3. Для каждого вложенного отчета создаем отдельную подчиненную схему компоновки. Во вложенной схеме «Реализация» настраиваем плоский вывод документов, а в схеме «Поступления» — вывод дерева с итогами по группам.
  4. Связываем параметры вложенных отчетов с полем Объект родительского отчета через вкладку «Вложенные отчеты» в настройках схемы компоновки.

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

---

Метод 5. Использование вычисляемых полей и функций общего модуля

Иногда жесткие требования заказчика к "параллельности" данных можно обойти, предложив альтернативное визуальное представление. Например, объединить детальные записи документов в рамках одной строки в виде списка, разделенного переносом строки.

Для этого в СКД можно использовать механизм вычисляемых полей совместно с агрегатной функцией ВычислитьВыражениеСГруппировкойМассив.

Разберем пример настройки вычисляемого поля:

  1. На вкладке «Вычисляемые поля» добавляем поле СписокРеализаций.
  2. В выражении этого поля прописываем вызов функции: СоединитьСтроки(ВычислитьВыражениеСГруппировкойМассив("Представление(Ссылка)", "Ссылка")).
  3. В качестве выражения ресурса для этого поля указываем ту же функцию. СКД соберет все детальные документы реализации по данному объекту в массив, преобразует в строки и объединит их через запятую или символ переноса строки.

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

---

Сравнение методов: какой подход выбрать?

Проанализируем все рассмотренные варианты, чтобы выбрать наиболее подходящий под ваши задачи:

  1. Гибридный метод (Программный вывод):
    • Плюсы: Полная свобода действий в оформлении макета, неограниченная сложность геометрии таблиц.
    • Минусы: Высокая трудоемкость, потеря стандартных возможностей интерактивной настройки отчета пользователем.
  2. Оконные функции (Сквозная нумерация):
    • Плюсы: Чистый СКД-отчет, сохранение всех интерактивных возможностей настройки, высокая производительность на уровне СУБД.
    • Минусы: Требуется версия платформы не ниже 8.3.15 и поддержка СУБД. Сложный для понимания SQL-запрос.
  3. Параллельные наборы данных:
    • Плюсы: Отсутствие дублирования строк на уровне запроса, стандартные механизмы СКД.
    • Минусы: Сложная настройка структуры колонок и группировок в компоновщике.
  4. Вложенные схемы компоновки:
    • Плюсы: Позволяет легко сочетать плоские таблицы и деревья в рамках одного отчета.
    • Минусы: Возможна просадка производительности при большом количестве объектов, так как для каждого объекта будет выполняться отдельный вложенный запрос.

Рекомендация: Если у вас современная платформа 1С (8.3.15 и выше), в первую очередь попробуйте реализовать задачу через Метод 2 (Оконные функции). Это наиболее изящный способ, сохраняющий все преимущества платформенного механизма компоновки данных. Если же геометрия колонок макета выходит за рамки стандартных возможностей ячеек СКД, используйте Метод 1 (Гибридный метод) с выводом через дерево значений.

← На главную