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