К статье
Практический материал

Разработка и Конфигурация · 14 мин

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

Как обойти результат запроса 1С по группировкам: Выбрать с ОбходРезультатаЗапроса, итоги, вложенные выборки, проверка ТипЗаписи и типичные ошибки разработчика.

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

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

Что возвращает запрос с итогами и зачем нужен специальный обход

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

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

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

Режимы обхода: прямой, по группировкам и по группировкам с иерархией

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

Выбор режима зависит от задачи. Для выгрузки плоских данных достаточно прямого обхода. Для отчёта «шапка группы + строки» нужен обход по группировкам с вложенной выборкой деталей. Режим с иерархией требуется редко — в основном когда группировка построена по иерархическому справочнику с указанием ИЕРАРХИЯ в секции ИТОГИ и нужно вывести промежуточные уровни дерева.

Режим обходаЧто попадает в циклКогда применять
ПрямойТолько детальные записиПлоская выгрузка, итоги не нужны
ПоГруппировкамЗаписи группировок указанного уровняШапки групп, далее вложенная выборка деталей
ПоГруппировкамСИерархиейГруппировки плюс итоги по иерархииИерархические справочники, вывод дерева

Обратите внимание: в режиме ПоГруппировкам общий итог (если указано ОБЩИЕ) также приходит в цикл как отдельная запись. Её нужно отличать по типу записи, иначе «шапка» с пустой группировкой попадёт в отчёт.

Типовой код обхода: группа и вложенные детальные записи

Классический шаблон выглядит так: внешняя выборка обходит группировки, внутри неё из текущей строки группы создаётся вложенная выборка детальных записей. Вложенная выборка получает только те детали, которые относятся к текущей группе, — это её главное удобство.

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Продажи.Контрагент КАК Контрагент,

| Продажи.Документ КАК Документ,

| Продажи.Сумма КАК Сумма

|ИЗ

| РегистрНакопления.Продажи КАК Продажи

|ИТОГИ

| СУММА(Сумма)

|ПО

| Контрагент";

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

ВыборкаГрупп = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаГрупп.Следующий() Цикл

// Строка группы: доступны Контрагент и итоговая Сумма

Сообщить("Контрагент: " + ВыборкаГрупп.Контрагент

+ ", итого: " + ВыборкаГрупп.Сумма);

ВыборкаДетали = ВыборкаГрупп.Выбрать();

Пока ВыборкаДетали.Следующий() Цикл

Сообщить(" " + ВыборкаДетали.Документ

+ " — " + ВыборкаДетали.Сумма);

КонецЦикла;

КонецЦикла;

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

Порядок действий
  1. 1
    Добавьте в текст запроса секцию ИТОГИ с агрегатными функциями и полями группировки
  2. 2
    Выполните запрос и получите результат через Запрос.Выполнить()
  3. 3
    Создайте выборку: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам)
  4. 4
    В цикле Пока Следующий() обработайте строку группы и её итоги
  5. 5
    Внутри цикла создайте вложенную выборку ВыборкаГрупп.Выбрать() и обойдите детальные записи
  6. 6
    Проверьте результат на контрольном наборе данных: итоги групп должны сходиться с суммой деталей

Несколько уровней группировок и обход по имени группировки

Если в запросе несколько группировок — например, ПО Контрагент, Договор, — дерево становится многоуровневым. Есть два подхода. Первый — рекурсивное вложение: внешняя выборка по первой группировке, внутри неё выборка по второй с указанием режима ПоГруппировкам, а уже из неё — детали. Второй — обход конкретного уровня по имени: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Договор") выдаст записи группировки «Договор» по всем контрагентам подряд.

Пример двухуровневого обхода:

ВыборкаКонтрагенты = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаКонтрагенты.Следующий() Цикл

// Уровень контрагента

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

Пока ВыборкаДоговоры.Следующий() Цикл

// Уровень договора внутри контрагента

ВыборкаДетали = ВыборкаДоговоры.Выбрать();

Пока ВыборкаДетали.Следующий() Цикл

// Детальные записи договора

КонецЦикла;

КонецЦикла;

КонецЦикла;

Обход по имени группировки удобен, когда промежуточные уровни не нужны: например, требуется список всех договоров с итогами без разбивки по контрагентам в выводе. Но помните, что при этом теряется связь «какой договор к какому контрагенту относится» в структуре обхода — значение поля контрагента в записи договора будет доступно только если оно входит в выборку.

Проверка типа записи: группа, деталь, общий итог

Метод ТипЗаписи() возвращает значение перечисления ТипЗаписиЗапроса: ИтогПоГруппировке, ДетальнаяЗапись, ОбщийИтог или ИтогПоИерархии. Проверка типа обязательна в двух случаях: когда в запросе есть ОБЩИЕ (общий итог придёт в цикл группировок первой записью) и когда используется смешанный обход, где в одном цикле могут встречаться разные типы.

Пока ВыборкаГрупп.Следующий() Цикл

Если ВыборкаГрупп.ТипЗаписи() = ТипЗаписиЗапроса.ОбщийИтог Тогда

Продолжить; // пропускаем общий итог

КонецЕсли;

// обработка группы

КонецЦикла;

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

Типичные ошибки
01
Обход без параметра при наличии ИТОГИ — итоговые строки недоступны, разработчик думает, что запрос их не вернул
02
Отсутствие проверки ТипЗаписи() при ИТОГИ ПО ... ОБЩИЕ — в вывод попадает лишняя строка общего итога с пустой группировкой
03
Вложенная выборка создаётся из Результат, а не из записи группы — детали перемешиваются между группами
04
Чтение детальных полей из записи группировки — там их нет, значения NULL или пустые
05
Использование ПоГруппировкамСИерархией без иерархической группировки — неожиданные дубли строк в обходе

Иерархические группировки и режим ПоГруппировкамСИерархией

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

У такой выборки есть полезное свойство Уровень() — оно возвращает глубину текущей записи в дереве. По уровню удобно строить отступы при выводе в табличный документ или определять, нужно ли обрабатывать запись как лист дерева. Типовая логика: записи типа ИтогПоИерархии выводятся как узлы, детальные записи — как содержимое.

Если иерархия в запросе не используется, применять этот режим не следует: для обычных группировок он может вернуть дополнительные записи, и логика обхода усложнится без выигрыша.

Проверка результата и типичные ошибки

После написания обхода результат стоит проверить на контрольном примере. Надёжный способ: сравнить итог, который выводит ваш код по каждой группе, с суммой детальных строк той же группы — они должны совпадать. Дополнительно сверьте общее количество обработанных детальных записей с количеством строк в результате прямого обхода того же запроса.

Частая ошибка — попытка прочитать из записи группировки поле, которое не входит ни в группировку, ни в агрегаты. Такого поля в строке итога нет: обращение вернёт NULL или пустое значение. Все неагрегатные поля доступны только в детальных записях. Обратная ошибка — ожидать в детальной записи итоговую сумму группы: её там нет, нужно либо сохранить значение из строки группы в переменную, либо использовать соединение с итогами в самом запросе.

Ещё одна ловушка — изменение коллекции во время обхода. Если внутри цикла по выборке выполняется код, который перезаполняет тот же результат запроса или обращается к нему повторно, поведение выборки может стать непредсказуемым. Сначала соберите нужные данные в структуру или таблицу значений, затем выполняйте действия.

Контрольные точки

Как проверить результат

Итог группы в выводе
Норма

Равен сумме детальных записей этой группы

Тревожный признак

Отличается — пропущены или задвоены детали

Первая строка обхода
Норма

Это реальная группа с заполненным полем группировки

Тревожный признак

Пустая группировка с итогом по всему отчёту — не отфильтрован общий итог

Поля в строке группы
Норма

Доступны поля группировки и агрегаты

Тревожный признак

Пытаетесь читать детальные поля — получаете NULL

Количество деталей
Норма

Совпадает с числом строк прямого обхода

Тревожный признак

Меньше — вложенная выборка создана не из той записи

Альтернативы: СКД, свёртка в таблицу значений и временные таблицы

Ручной обход по группировкам — не единственный способ получить иерархические итоги. Если задача — вывести отчёт пользователю, почти всегда рациональнее использовать СКД: она сама строит группировки, итоги и иерархию, а код обхода не нужен вовсе. Ручной обход оправдан, когда результат нужен для дальнейших вычислений, выгрузки или нестандартного формирования документов.

Если иерархия не нужна, а нужна просто агрегация, проще выгрузить результат в таблицу значений (Результат.Выгрузить()) и свернуть её методом Свернуть() — это короче и быстрее, чем обход по группировкам. Для сложной многошаговой логики иногда выгоднее разбить запрос на пакет с временными таблицами и обходить простые плоские результаты вместо одного дерева.

Сравнение вариантов

Обход по группировкам

Полный контроль над порядком и логикой; подходит для выгрузок и нестандартной обработки. Требует аккуратной работы с типами записей.

СКД

Готовые группировки, итоги и иерархия без кода обхода; лучший выбор для отчётов пользователю. Сложнее встроить в произвольный алгоритм.

Выгрузить() + Свернуть()

Просто и быстро для плоской агрегации без иерархии. Не даёт дерева групп и вложенных деталей.

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

Прозрачная пошаговая логика для сложных вычислений. Больше кода запроса, нет готовых итогов по дереву.

Итог

Что важно запомнить

Итоги запроса читаются только при обходе ПоГруппировкам, детали — вложенной выборкой из записи группы. Проверяйте ТипЗаписи(), чтобы отсечь общий итог, и не ищите детальные поля в строках группировок. Для отчётов пользователю предпочтительнее СКД.

Частые вопросы

Почему выборка не содержит итогов, хотя в запросе есть ИТОГИ?

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

Как получить и итог группы, и её детали в одном цикле?

В одном цикле — никак: это разные типы записей. Стандартный приём — внешний цикл по группировкам, где доступны итоги, и вложенная выборка ВыборкаГрупп.Выбрать() для детальных записей текущей группы. Сохраните итог в переменную до входа во вложенный цикл, если он нужен при обработке каждой детали.

Что делать с записью общего итога при ИТОГИ ... ОБЩИЕ?

Она приходит в обход по группировкам как отдельная запись с типом ОбщийИтог и пустыми полями группировки. Либо пропустите её проверкой ТипЗаписи(), либо используйте осознанно — например, для вывода строки «Итого» в конце отчёта.

Как обойти только второй уровень группировок, минуя первый?

Укажите имя группировки вторым параметром: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ИмяГруппировки"). Цикл пройдёт по всем записям этого уровня независимо от родительских групп. Учтите, что связь с родителем при этом в структуре обхода теряется.

Чем отличается ПоГруппировкам от ПоГруппировкамСИерархией?

Первый режим возвращает только записи итогов по группировкам. Второй дополнительно выдаёт записи типа ИтогПоИерархии для иерархических группировок (с ключевым словом ИЕРАРХИЯ в секции ИТОГИ) и позволяет через Уровень() определять глубину записи в дереве. Без иерархической группировки применять его не нужно.

Можно ли внутри обхода изменять данные в базе?

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