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