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

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

При построении иерархических структур данных через запросы с секцией ИТОГИ разработчики часто сталкиваются с ситуацией, когда в результирующем дереве значений появляется больше уровней группировки, чем ожидалось. Рассмотрим типовую ситуацию: в запросе рассчитываются итоги по двум полям, например, по сменному заданию и схеме раскроя. После стандартного вызова метода Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам) формируется трехуровневое дерево: общий итог по заданию, промежуточный по схеме и детальные записи. Если требуется исключить самый верхний уровень и сразу вывести группировку по схеме с детальными записями, прямая выгрузка не подойдет. Разберем по шагам, почему так происходит и какими способами можно сформировать требуемую структуру дерева значений.

Почему в результате запроса с итогами появляется лишний уровень

Проанализируем поведение платформы 1С при обработке секции ИТОГИ. Предположим, у нас есть следующий запрос:


ВЫБРАТЬ
    РС.СменноеЗадание КАК СменноеЗадание,
    РС.СхемаРаскроя КАК СхемаРаскроя,
    РС.КоличествоСъемов * РС.КоличествоКПроизводству КАК ВесГППланируемый
ИЗ
    РегистрСведений.РС КАК РС
ИТОГИ
    СУММА(ВесГППланируемый)
ПО
    СменноеЗадание,
    СхемаРаскроя

В языке запросов 1С конструкция ИТОГИ ПО Поле1, Поле2 строго определяет последовательную вложенную иерархию. Платформа формирует уровни данных следующим образом:

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

Таким образом, в результате запроса число уровней всегда на единицу больше, чем количество группировочных полей в секции ПО: два уровня итогов плюс детальные записи. Если у сменного задания присутствует всего одна схема раскроя, числовые суммы первого и второго уровней совпадут, из-за чего строки выглядят продублированными. Метод Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам) строго переносит эту трехуровневую иерархию в ДеревоЗначений. Чтобы убрать верхний уровень, выгрузку необходимо переопределить.

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

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

Посмотрим на практический пример кода:


// Создаем или очищаем результирующее дерево значений
ДеревоЗначений = Новый ДеревоЗначений;
ДеревоЗначений.Колонки.Добавить("СменноеЗадание");
ДеревоЗначений.Колонки.Добавить("СхемаРаскроя");
ДеревоЗначений.Колонки.Добавить("ВесГППланируемый", Новый ОписаниеТипов("Число"));

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

// Выбираем первый уровень (СменноеЗадание)
ВыборкаСмЗад = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаСмЗад.Следующий() Цикл
    
    // Пропускаем добавление строки первого уровня в дерево
    // и сразу переходим к выборке второго уровня (СхемаРаскроя)
    ВыборкаСхема = ВыборкаСмЗад.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    Пока ВыборкаСхема.Следующий() Цикл
        
        // Добавляем строку второго уровня сразу в корень дерева значений
        СтрокаДерева = ДеревоЗначений.Строки.Добавить();
        ЗаполнитьЗначенияСвойств(СтрокаДерева, ВыборкаСхема);
        
        // Получаем детальные записи, подчиненные данной схеме
        ВыборкаДеталей = ВыборкаСхема.Выбрать();
        Пока ВыборкаДеталей.Следующий() Цикл
            СтрокаДеталь = СтрокаДерева.Строки.Добавить();
            ЗаполнитьЗначенияСвойств(СтрокаДеталь, ВыборкаДеталей);
        КонецЦикла;
        
    КонецЦикла;
    
КонецЦикла;

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

Способ 2. Объединение группировочных полей в запросе через составной ключ

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

Разберем по шагам, как модифицировать текст запроса:

  1. Объединяем строковые представления ключевых полей в одно расчетное поле СоставнойКлюч.
  2. В секции ИТОГИ указываем группировку только по этому составному ключу.
  3. Сами поля СменноеЗадание и СхемаРаскроя получаем через агрегатную функцию МАКСИМУМ, чтобы они заполнились на уровне группировки.

Посмотрим на пример запроса:


ВЫБРАТЬ
    РС.СменноеЗадание КАК СменноеЗадание,
    РС.СхемаРаскроя КАК СхемаРаскроя,
    ВЫРАЗИТЬ(РС.СменноеЗадание.Номер КАК СТРОКА) + "_" + ВЫРАЗИТЬ(РС.СхемаРаскроя.Код КАК СТРОКА) КАК СоставнойКлюч,
    РС.КоличествоСъемов * РС.КоличествоКПроизводству КАК ВесГППланируемый
ИЗ
    РегистрСведений.РС КАК РС
ИТОГИ
    СУММА(ВесГППланируемый),
    МАКСИМУМ(СменноеЗадание),
    МАКСИМУМ(СхемаРаскроя)
ПО
    СоставнойКлюч

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


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

В этом случае платформа сформирует строго два уровня: в корне расположатся строки группировки по составному ключу с заполненными реквизитами задания и схемы, а внутри них — подчиненные детальные записи.

Способ 3. Постобработка и «поднятие» строк в готовом дереве

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


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

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

// Переносим строки второго уровня в корень нового дерева
Для Каждого СтрокаУровень1 Из ИсходноеДерево.Строки Цикл
    Для Каждого СтрокаУровень2 Из СтрокаУровень1.Строки Цикл
        
        НоваяСтрокаКорня = ИтоговоеДерево.Строки.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрокаКорня, СтрокаУровень2);
        
        // Копируем детальные записи
        Для Каждого ДетальнаяСтрока Из СтрокаУровень2.Строки Цикл
            НоваяДеталь = НоваяСтрокаКорня.Строки.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяДеталь, ДетальнаяСтрока);
        КонецЦикла;
        
    КонецЦикла;
КонецЦикла;

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

← На главную