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

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

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

Анализ структуры вложенных циклов

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


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

В приведенном примере кроется классическая невнимательность: метод Выбрать() для получения детальных записей вызывается у выборки верхнего уровня (ВыборкаПоОрганизациям), минуя уровень склада. Выясним причину, почему это не работает: вызывая метод у «дедушки», вы пытаетесь получить все детальные записи, относящиеся к организации, игнорируя текущий контекст «склада» во втором цикле. Правильный подход — всегда вызывать метод Выбрать() у выборки текущего уровня, чтобы получить подчиненные записи именно для этой ветки дерева.

Обязательность секции ИТОГИ в запросе

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

Рассмотрим правильное построение запроса для нашего примера:


ТекстЗапроса = "ВЫБРАТЬ
               |    Продажи.Организация КАК Организация,
               |    Продажи.Склад КАК Склад,
               |    Продажи.Номенклатура КАК Номенклатура,
               |    Продажи.Количество КАК Количество
               |ИЗ
               |    РегистрНакопления.Продажи КАК Продажи
               |ИТОГИ
               |    СУММА(Количество)
               |ПО
               |    Организация,
               |    Склад";

Обратите внимание: порядок полей в секции ПО должен строго соответствовать вложенности ваших циклов в коде 1С. Если в запросе сначала идет «Склад», а потом «Организация», то и первый цикл в коде должен обрабатывать склады.

Особенности доступа к полям на разных уровнях выборки

Разберем важный технический нюанс. При обходе по группировкам на каждом уровне иерархии доступны только определенные данные:

  1. На уровне Организация: доступно значение поля Организация и агрегатные функции (например, сумма по всей организации). Поле Номенклатура здесь будет равно NULL или будет пустым.
  2. На уровне Склад: доступно значение полей Организация (как родителя) и Склад, а также итоги по складу.
  3. На уровне Детальные записи: доступны все поля без исключения (Цена, Комментарий, Артикул и т.д.).

Если вы попытаетесь получить значение «Комментарий» из детальной записи, находясь в цикле по Организациям, вы получите пустое значение. Это распространенная причина ошибок, когда разработчик ожидает увидеть данные «шапки» документа в итоговых строках.

Использование методов отладки: Выгрузить() против Выбрать()

Если данные не выводятся, мы рекомендуем использовать метод Выгрузить() вместо Выбрать() для анализа проблемы. Метод Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам) создаст объект ДеревоЗначений. Посмотрев на него в отладчике, вы сразу увидите структуру иерархии и поймете, на каком уровне данные отсутствуют. Однако помните, что Выгрузить() помещает все данные в оперативную память, что неэффективно для больших выборок. Метод Выбрать() работает как «курсор», считывая данные порционно, что гораздо предпочтительнее для высоконагруженных систем.

Расширенные возможности обхода

Рассмотрим дополнительные инструменты, которые предоставляет платформа для управления выборками:

1. Метод Сбросить(): Если вам необходимо повторно пройтись по одной и той же выборке (например, сначала посчитать общие итоги, а затем заполнить строки), используйте Выборка.Сбросить(). Это вернет указатель в начало, избавляя от необходимости выполнять тяжелый запрос к базе данных повторно.

2. Проверка на группы: При работе с иерархическими справочниками (например, когда итоги считаются ПО Склад ИЕРАРХИЯ), в выборку попадут не только сами склады, но и их папки. В этом случае используйте свойство ЭтоГруппа, чтобы отличить итоговую запись по папке от записи по конкретному элементу.

3. Прямой прыжок к деталям: Малоизвестный факт — метод Выбрать() принимает параметры. Если вы находитесь на уровне Организации и хотите сразу получить все детальные записи, минуя уровень Складов, вы можете настроить параметры метода так, чтобы проигнорировать промежуточные итоги.

Итоговый корректный пример обхода

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


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

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

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

← На главную