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

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет
← На главную

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

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

Разбор основной проблемы: почему появляются пустые строки

Рассмотрим ситуацию: разработчик создает запрос с секцией ИТОГИ по нескольким полям. При попытке вывести данные в иерархическом виде (сначала регион, затем контрагент и т.д.) в детальных записях начинают появляться лишние строки. Проанализируем причину на примере фрагмента кода:


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

В данном примере совершена критическая ошибка: внутри цикла по регионам вызывается Запрос.Выполнить(). Это заставляет систему каждый раз заново обращаться к базе данных, что катастрофически замедляет работу. Более того, метод Выбрать() вызывается от всего результата запроса, а не от текущей выборки региона. В результате во второй цикл попадают все контрагенты базы, а не только те, которые относятся к текущему региону. Это и порождает логические ошибки в выводе данных.

Правильный алгоритм вложенного обхода

Для корректного построения иерархии необходимо использовать каскадный метод Выбрать(). Мы должны получать вложенную выборку из родительской выборки, а не из объекта РезультатЗапроса. Рассмотрим правильную структуру обхода:

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

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


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

// 1 уровень - Регионы
ВыборкаРегион = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "КонтрагентРегион");
Пока ВыборкаРегион.Следующий() Цикл
    
    ОбластьРегион.Параметры.Регион = ВРег(ВыборкаРегион.КонтрагентРегион);
    ТабличныйДокумент.Вывести(ОбластьРегион);
    
    // 2 уровень - Контрагенты (выбираем ИЗ ВыборкаРегион)
    ВыборкаКонтрагент = ВыборкаРегион.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Контрагент");
    Пока ВыборкаКонтрагент.Следующий() Цикл
        
        ОбластьКонтрагент.Параметры.Заполнить(ВыборкаКонтрагент);
        ТабличныйДокумент.Вывести(ОбластьКонтрагент);
        
        // 3 уровень - Договоры (выбираем ИЗ ВыборкаКонтрагент)
        ВыборкаДоговор = ВыборкаКонтрагент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Договор");
        Пока ВыборкаДоговор.Следующий() Цикл
            
            ОбластьДоговор.Параметры.Заполнить(ВыборкаДоговор);
            ТабличныйДокумент.Вывести(ОбластьДоговор);
            
            // 4 уровень - Детальные записи (Документы)
            ВыборкаЗапись = ВыборкаДоговор.Выбрать(); 
            Пока ВыборкаЗапись.Следующий() Цикл
                ОбластьЗапись.Параметры.Заполнить(ВыборкаЗапись);
                ТабличныйДокумент.Вывести(ОбластьЗапись);
            КонецЦикла;
            
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

Использование временных таблиц для оптимизации запроса

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

Разберем преимущества временных таблиц:

Рассмотрим пример того, как можно переписать расчет банковских дней из производственного календаря, используя временную таблицу ВТ_Остатки. Сначала мы выбираем остатки во временную таблицу, а затем соединяем её с регистром сведений ЗП_РегламентированныйПроизводственныйКалендарь. Это позволит избежать лишних группировок внутри основного тела запроса.

Работа с параметрами областей макета

При выводе данных в ТабличныйДокумент часто встречается код вида Область.Параметры.Контрагент = Выборка.Контрагент;. Если в области макета много параметров, такой код становится громоздким. Выясним причину, по которой стоит использовать метод Заполнить().

Объект Параметры области макета имеет встроенный метод Заполнить(Источник). Если имена параметров в макете совпадают с именами полей в выборке (псевдонимами в запросе), мы можем заменить десятки строк одной командой:

Область.Параметры.Заполнить(Выборка);

Важный нюанс: Если в запросе поле называется СуммаОстаток, а в макете параметр назван Сумма, автоматическое заполнение для этого поля не сработает. Всегда старайтесь давать полям запроса осмысленные псевдонимы, совпадающие с макетом.

Особенности группировки по итогам

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

Если в поле группировки (например, Регион) попадает значение NULL, 1С выведет его как отдельную пустую группировку. Чтобы отчет выглядел профессионально, используйте функцию ЕСТЬNULL(Контрагент.Регион, "Регион не определен") прямо в тексте запроса. Это избавит пользователя от созерцания пустых заголовков в отчете.

Заключение

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

← На главную