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