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