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