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