Как в запросе 1С обойти итоги по нескольким полям без множества вложенных циклов?

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

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

В чем причина многоуровневой структуры при использовании ИТОГИ ПО?

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


ИТОГИ ПО
    Дата,
    Склад,
    КассаККМ

Платформа строит строгую иерархическую структуру (дерево итогов). Каждый указанный элемент формирует отдельный уровень вложенности:

  1. Первый уровень группировки формируется по полю Дата (значения полей Склад и КассаККМ на этом уровне не определены).
  2. Второй уровень формируется внутри каждой даты по полю Склад (поле КассаККМ еще не определено).
  3. Третий уровень группируется по значению КассаККМ.
  4. На четвертом уровне располагаются детальные записи документа.

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

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    ОтчетОРозничныхПродажахТовары.Ссылка.Дата КАК Дата,
|    ОтчетОРозничныхПродажахТовары.Ссылка.Склад КАК Склад,
|    ОтчетОРозничныхПродажахТовары.Ссылка.КассаККМ КАК КассаККМ,
|    ОтчетОРозничныхПродажахТовары.СтрокаЧека КАК СтрокаЧека,
|    ОтчетОРозничныхПродажахТовары.Номенклатура,
|    ОтчетОРозничныхПродажахТовары.Количество,
|    ОтчетОРозничныхПродажахТовары.Сумма
|ИЗ
|    Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
|ГДЕ
|    ОтчетОРозничныхПродажахТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
|УПОРЯДОЧИТЬ ПО
|    Дата,
|    Склад,
|    КассаККМ,
|    СтрокаЧека
|ИТОГИ ПО
|    Дата,
|    Склад,
|    КассаККМ";

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

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

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

Вариант 1. Линейный обход с ручным контролем смены группировки

Если промежуточные подуровни не требуются, а нужна единая шапка по трем полям и список строк под ней, наиболее производительным решением будет отказ от секции ИТОГИ. Мы упорядочиваем данные по ключевым полям и выполняем один плоский цикл обхода с отслеживанием момента смены значений.

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    ОтчетОРозничныхПродажахТовары.Ссылка.Дата КАК Дата,
|    ОтчетОРозничныхПродажахТовары.Ссылка.Склад КАК Склад,
|    ОтчетОРозничныхПродажахТовары.Ссылка.КассаККМ КАК КассаККМ,
|    ОтчетОРозничныхПродажахТовары.СтрокаЧека КАК СтрокаЧека,
|    ОтчетОРозничныхПродажахТовары.Номенклатура КАК Номенклатура,
|    ОтчетОРозничныхПродажахТовары.Количество КАК Количество,
|    ОтчетОРозничныхПродажахТовары.Сумма КАК Сумма
|ИЗ
|    Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
|ГДЕ
|    ОтчетОРозничныхПродажахТовары.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
|УПОРЯДОЧИТЬ ПО
|    Дата,
|    Склад,
|    КассаККМ,
|    СтрокаЧека";

Выборка = Запрос.Выполнить().Выбрать();

ТекущаяДата = Неопределено;
ТекущийСклад = Неопределено;
ТекущаяКасса = Неопределено;

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

Преимущества линейного обхода:

  1. Всего один цикл вместо четырех.
  2. СУБД не тратит ресурсы на расчет многоуровневых промежуточных итогов.
  3. Шапка выводится один раз на каждую комбинацию «Дата + Склад + КассаККМ».

Вариант 2. Разделение запроса на шапку и строки (два цикла вместо четырех)

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

Рассмотрим структуру такого запроса и алгоритм его обхода:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|    Отчет.Ссылка.Дата КАК Дата,
|    Отчет.Ссылка.Склад КАК Склад,
|    Отчет.Ссылка.КассаККМ КАК КассаККМ,
|    СУММА(Отчет.Количество) КАК ИтогКоличество,
|    СУММА(Отчет.Сумма) КАК ИтогСумма
|ИЗ
|    Документ.ОтчетОРозничныхПродажах.Товары КАК Отчет
|ГДЕ
|    Отчет.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
|СГРУППИРОВАТЬ ПО
|    Отчет.Ссылка.Дата,
|    Отчет.Ссылка.Склад,
|    Отчет.Ссылка.КассаККМ
|УПОРЯДОЧИТЬ ПО
|    Дата,
|    Склад,
|    КассаККМ;
|
|ВЫБРАТЬ
|    Отчет.Ссылка.Дата КАК Дата,
|    Отчет.Ссылка.Склад КАК Склад,
|    Отчет.Ссылка.КассаККМ КАК КассаККМ,
|    Отчет.Номенклатура КАК Номенклатура,
|    Отчет.Количество КАК Количество,
|    Отчет.Сумма КАК Сумма
|ИЗ
|    Документ.ОтчетОРозничныхПродажах.Товары КАК Отчет
|ГДЕ
|    Отчет.Ссылка.Дата МЕЖДУ &НачалоПериода И &ОкончаниеПериода
|УПОРЯДОЧИТЬ ПО
|    Дата,
|    Склад,
|    КассаККМ";

ПакетРезультатов = Запрос.ВыполнитьПакет();
ВыборкаГруппировок = ПакетРезультатов[0].Выбрать();
ТаблицаДеталей = ПакетРезультатов[1].Выгрузить();

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

В этом сценарии код содержит ровно два цикла: верхний (по плоской группировке трех полей) и нижний (по строкам, относящимся к этой группировке).

Вариант 3. Использование Системы компоновки данных (СКД)

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

  1. В схеме компоновки создается группировка строк, в которую добавляются сразу три поля: Дата, Склад и КассаККМ.
  2. Под этой группировкой создается подчиненный элемент <Детальные записи>.
  3. Платформа автоматически рассчитает итоги и выведет заголовок по трем полям без необходимости писать код обхода вручную.

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

← На главную