Как правильно организовать обход результата запроса по группировкам и общим итогам в 1С

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

При построении печатных форм и нестандартных отчетов в 1С часто возникает задача: выполнить запрос с расчетом итогов по группировкам и общих итогов, а затем последовательно вывести в табличный документ заголовок группы, детальные строки, промежуточные итоги по группе и в самом конце — общий итог отчета. Начинающие разработчики нередко сталкиваются с ситуацией, когда детальные записи начинают дублироваться или общие итоги рассчитываются неверно. Разберем по шагам, как устроена иерархия выборок в платформе 1С и как правильно выстроить программный обход данных.

Анализ структуры запроса с итогами

Рассмотрим типовую структуру запроса, в котором используются группировки и расчет итогов:


ВЫБРАТЬ
    Продажи.Контрагент КАК Покупатель,
    Маршруты.Маршрут КАК Маршрут,
    СУММА(Продажи.Количество * Продажи.Номенклатура.ЕдиницаХраненияОстатков.Вес) КАК Вес,
    СУММА(Продажи.Стоимость) КАК Сумма
ИЗ
    РегистрНакопления.Продажи КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Маршруты КАК Маршруты
    ПО Продажи.Контрагент = Маршруты.Контрагент
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент,
    Маршруты.Маршрут
ИТОГИ
    СУММА(Вес),
    СУММА(Сумма)
ПО
    ОБЩИЕ,
    Маршрут

Когда в секции ИТОГИ указано предложение ПО ОБЩИЕ, Маршрут, платформа 1С формирует результат запроса в виде строгого иерархического дерева. В этой структуре:

  1. Уровень 0 (Корень дерева): строка с общими итогами по всей выборке (секция ОБЩИЕ).
  2. Уровень 1 (Группировка первого уровня): строки с итогами по каждому значению поля Маршрут.
  3. Уровень 2 (Листья / Детальные записи): детальные строки с полями Покупатель, Вес и Сумма, относящиеся к конкретному маршруту.

Почему возникает задвоение детальных записей

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


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

Поскольку в тексте запроса присутствует директива ОБЩИЕ, прямым родителем для группировок является строка общего итога. Если мы не перешли к этой строке через метод Следующий(), внутренняя фильтрация детальных записей работает некорректно, и при каждом шаге цикла по маршруту могут выбираться все детальные записи запроса.

Пошаговый алгоритм правильного обхода выборки

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

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

Посмотрим на полный пример программного кода:


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

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

// Шаг 2. Получаем выборку по маршрутам из выборки общего итога
ВыборкаМаршрут = ВыборкаОбщийИтог.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Маршрут");

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

// Шаг 4. Вывод общего итога отчета
ОбластьОбщийИтог = Макет.ПолучитьОбласть("ПодвалОтчета");
ОбластьОбщийИтог.Параметры.Заполнить(ВыборкаОбщийИтог);
ТабДок.Вывести(ОбластьОбщийИтог);

Специфика параметров метода Выбрать

Проанализируем параметры метода Выбрать() объекта РезультатЗапроса и ВыборкаИзРезультатаЗапроса:

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

Альтернативные варианты реализации

В зависимости от архитектуры отчета можно применять и другие подходы:

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

Рекомендации по оптимизации текста запроса

При написании запросов к регистрам накопления для отчетов обратим внимание на важные правила оптимизации:

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