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