Как сгруппировать данные табличных частей нескольких документов при выводе на печать в 1С?

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

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

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

Причины возникновения проблемы и ограничения платформы

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

  1. Ограничение вложенных таблиц: В языке запросов 1С выборка через синтаксис вложенных таблиц не поддерживает группировку (СГРУППИРОВАТЬ ПО) и расчет общих итогов по полям внешнего документа. Вложенная таблица жестко привязана к конкретному экземпляру документа в базе и не может объединять строки из разных документов между собой. При попытке группировки платформа выдает ошибку: «Нельзя группировать по полям внешних таблиц при наличии вложенных таблиц».
  2. Задвоение данных шапки: При обращении к табличной части реквизиты шапки (например, сумма документа) дублируются на каждую строку табличной части. Поэтому суммировать реквизит шапки нельзя — итог по группе должен вычисляться агрегатной функцией СУММА(Сумма) от строк табличной части.
  3. Линейный обход выборки с итогами: Вызов метода Выбрать() без явного указания типа обхода выполняет прямой обход (ОбходРезультатаЗапроса.Прямой). При таком обходе в один цикл подряд попадают как итоговые записи групп, так и детальные строки, что приводит к некорректному выводу данных в макет.

Вариант 1. Запрос к табличной части с секцией ИТОГИ и многоуровневым обходом

Разберем по шагам наиболее эффективный и классический подход для формирования печатных форм. Обратимся напрямую к табличной части документа Документ.э_ПланЗакупок.СоставПланаЗакупок, настроим расчет итогов по двум уровням (код и подкод) и выполним иерархический обход выборки.

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


Процедура ПечатьПланаЗакупок() Экспорт

    ТабДок = Новый ТабличныйДокумент;
    Макет = Документы.э_ПланЗакупок.ПолучитьМакет("МакетПечатьПланаЗакупок");
    
    // Вывод шапки отчета
    ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");
    ТабДок.Вывести(ОбластьЗаголовок);
    
    ОбластьШапка = Макет.ПолучитьОбласть("ШапкаТабличнойЧасти");
    ТабДок.Вывести(ОбластьШапка);
    
    // Формирование запроса к табличной части
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Состав.Ссылка.КодCPV КАК КодCPV,
        |   Состав.Ссылка.Описание КАК Описание,
        |   Состав.ПодкодCPV КАК ПодкодCPV,
        |   Состав.ОписаниеЗакупки КАК ОписаниеЗакупки,
        |   Состав.Сумма КАК СуммаЭлемента
        |ИЗ
        |   Документ.э_ПланЗакупок.СоставПланаЗакупок КАК Состав
        |ИТОГИ
        |   СУММА(СуммаЭлемента)
        |ПО
        |   КодCPV,
        |   ПодкодCPV";
        
    РезультатЗапроса = Запрос.Выполнить();
    
    Если РезультатЗапроса.Пустой() Тогда
        Возврат;
    КонецЕсли;
    
    // Получение областей для строк
    ОбластьГруппа = Макет.ПолучитьОбласть("СтрокаТабличнойЧасти");
    ОбластьПодстрока = Макет.ПолучитьОбласть("ПодстрокаТабличнойЧасти");
    
    // Обход группировки верхнего уровня (КодCPV)
    ВыборкаКод = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "КодCPV");
    
    НомСтроки = 0;
    
    Пока ВыборкаКод.Следующий() Цикл
        
        НомСтроки = НомСтроки + 1;
        
        ОбластьГруппа.Параметры.НомерСтроки = НомСтроки;
        ОбластьГруппа.Параметры.КодПоКлассификатору = ВыборкаКод.КодCPV;
        ОбластьГруппа.Параметры.НаименованиеЗакупки = ВыборкаКод.Описание;
        ОбластьГруппа.Параметры.СуммаЗакупки = ВыборкаКод.СуммаЭлемента; // Рассчитанный итог по группе
        ТабДок.Вывести(ОбластьГруппа);
        
        // Подчиненная выборка второго уровня (ПодкодCPV)
        ВыборкаПодкод = ВыборкаКод.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ПодкодCPV");
        
        НомПодстроки = 0;
        
        Пока ВыборкаПодкод.Следующий() Цикл
            
            НомПодстроки = НомПодстроки + 1;
            
            // Формируем строковый номер вида "1.1", "1.2"
            ИерархическийНомер = Строка(НомСтроки) + "." + Строка(НомПодстроки);
            
            ОбластьПодстрока.Параметры.НомерПодстроки = ИерархическийНомер;
            ОбластьПодстрока.Параметры.ПодКодПоКлассификатору = ВыборкаПодкод.ПодкодCPV;
            ОбластьПодстрока.Параметры.ПодНаименованиеЗакупки = ВыборкаПодкод.ОписаниеЗакупки;
            ОбластьПодстрока.Параметры.СуммаЭлемента = ВыборкаПодкод.СуммаЭлемента;
            ТабДок.Вывести(ОбластьПодстрока);
            
        КонецЦикла;
        
    КонецЦикла;
    
    // Настройки отображения
    ТабДок.ОтображатьСетку = Ложь;
    ТабДок.Защита = Ложь;
    ТабДок.ТолькоПросмотр = Ложь;
    ТабДок.ОтображатьЗаголовки = Ложь;
    ТабДок.Показать();

КонецПроцедуры

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

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

Вариант 2. Использование СГРУППИРОВАТЬ ПО и выгрузка в ДеревоЗначений

Рассмотрим альтернативный способ, который удобен при необходимости предварительной обработки данных в коде перед их отправкой в макет. Запрос объединяет строки по полям группировки с помощью конструкции СГРУППИРОВАТЬ ПО, а секция ИТОГИ позволяет получить готовую иерархическую структуру.

Посмотрим на пример выгрузки данных запроса в объект ДеревоЗначений:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Состав.Ссылка.КодCPV КАК КодCPV,
    |   МАКСИМУМ(Состав.Ссылка.Описание) КАК Описание,
    |   Состав.ПодкодCPV КАК ПодкодCPV,
    |   МАКСИМУМ(Состав.ОписаниеЗакупки) КАК ОписаниеЗакупки,
    |   СУММА(Состав.Сумма) КАК Сумма
    |ИЗ
    |   Документ.э_ПланЗакупок.СоставПланаЗакупок КАК Состав
    |СГРУППИРОВАТЬ ПО
    |   Состав.Ссылка.КодCPV,
    |   Состав.ПодкодCPV
    |ИТОГИ
    |   СУММА(Сумма)
    |ПО
    |   КодCPV,
    |   ПодкодCPV";

РезультатЗапроса = Запрос.Выполнить();

// Выгружаем данные с сохранением иерархии группировок
ДеревоДанных = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

НомСтроки = 0;
Для Каждого СтрокаКод Из ДеревоДанных.Строки Цикл
    
    НомСтроки = НомСтроки + 1;
    
    // Вывод области верхнего уровня
    ОбластьГруппа.Параметры.НомерСтроки = НомСтроки;
    ОбластьГруппа.Параметры.КодПоКлассификатору = СтрокаКод.КодCPV;
    ОбластьГруппа.Параметры.НаименованиеЗакупки = СтрокаКод.Описание;
    ОбластьГруппа.Параметры.СуммаЗакупки = СтрокаКод.Сумма;
    ТабДок.Вывести(ОбластьГруппа);
    
    НомПодстроки = 0;
    Для Каждого СтрокаПодкод Из СтрокаКод.Строки Цикл
        
        НомПодстроки = НомПодстроки + 1;
        
        ОбластьПодстрока.Параметры.НомерПодстроки = Строка(НомСтроки) + "." + Строка(НомПодстроки);
        ОбластьПодстрока.Параметры.ПодКодПоКлассификатору = СтрокаПодкод.ПодкодCPV;
        ОбластьПодстрока.Параметры.ПодНаименованиеЗакупки = СтрокаПодкод.ОписаниеЗакупки;
        ОбластьПодстрока.Параметры.СуммаЭлемента = СтрокаПодкод.Сумма;
        ТабДок.Вывести(ОбластьПодстрока);
        
    КонецЦикла;
    
КонецЦикла;

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

← На главную