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

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

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

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

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

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

Базовая цепочка: Выполнить, Выбрать, Следующий

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

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

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

"ВЫБРАТЬ

| Номенклатура.Ссылка КАК Товар,

| Номенклатура.Наименование КАК Наименование

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ГДЕ

| НЕ Номенклатура.ПометкаУдаления";

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

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

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

Сообщить(Выборка.Наименование);

КонецЦикла;

Метод Следующий() сдвигает указатель выборки на одну строку и возвращает Истина, если строка получена, или Ложь, когда строки закончились. Именно поэтому стандартная конструкция — Пока Выборка.Следующий() Цикл: цикл сам завершится, когда данные исчерпаются. Обращение к полям строки выполняется через точку по имени поля, указанному в списке выборки запроса (или по его псевдониму после слова КАК).

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

Проверка результата перед перебором

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

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

Если РезультатЗапроса.Пустой() Тогда

Сообщить("По заданным условиям данные не найдены");

Возврат;

КонецЕсли;

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

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

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

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

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

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

// Уровень группировки: например, склад

Сообщить("Склад: " + ВыборкаГруппы.Склад);

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

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

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

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

КонецЦикла;

КонецЦикла;

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

Выгрузка в таблицу значений и её перебор

Альтернатива выборке — метод Выгрузить(), который помещает весь результат запроса в таблицу значений. Этот вариант выбирают, когда данные нужно обходить несколько раз, сортировать, искать строки через Найти() или НайтиСтроки(), сворачивать по колонкам или передавать между процедурами.

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

Для Каждого СтрокаТаблицы Из ТаблицаРезультата Цикл

Сообщить(СтрокаТаблицы.Наименование);

КонецЦикла;

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

Выборка или таблица значений

Выборка (Выбрать)

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

Таблица значений (Выгрузить)

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

Дерево значений (Выгрузить с группировками)

Подходит для иерархических данных и вывода в дерево на форме; обход сложнее — через рекурсию по коллекции Строки.

Метод НайтиСледующий и частичный перебор

Иногда нужно не пройти все строки, а найти в выборке конкретную. Метод НайтиСледующий(СтруктураОтбора) сдвигает выборку до первой строки, соответствующей отбору, и возвращает Истина, если такая строка найдена.

Отбор = Новый Структура("Склад", СкладОтгрузки);

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

// Обработка только строк по нужному складу

КонецЦикла;

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

Типичные ошибки при переборе запроса

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

Типичные ошибки
01
Вызов Выбрать() без Выполнить() — выборка окажется пустой или возникнет ошибка, потому что результат запроса не получен
02
Пропущен Выборка.Следующий() внутри цикла Пока Истина — цикл становится бесконечным и сеанс зависает
03
Обращение к полю, которого нет в списке выборки — ошибка «Поле объекта не обнаружено»; добавьте поле в текст запроса
04
Обход запроса с ИТОГАМИ линейным циклом — итоговые строки смешиваются с детальными и данные задваиваются
05
Запрос внутри цикла обхода — резкое падение производительности; заменяйте соединением или временными таблицами
06
Обращение к полю через точку от ссылки в цикле (Выборка.Товар.Наименование) — скрытые запросы к базе на каждой итерации; выбирайте нужные поля сразу в тексте запроса

Отдельно стоит упомянуть обращение к реквизитам ссылочных полей через точку в коде. Конструкция Выборка.Товар.Наименование заставляет платформу обращаться к базе за каждым объектом. Правильный подход — получить Наименование прямо в тексте запроса через точку в языке запросов: платформа выполнит соединение один раз на уровне СУБД.

Как проверить, что перебор работает правильно

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

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

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

Количество обработанных строк
Норма

Совпадает с числом строк в консоли запросов при тех же параметрах

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

Строк меньше (потеряны условия отбора) или больше (смешаны итоги с деталями)

Время выполнения
Норма

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

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

Каждая итерация «подтормаживает» — признак скрытых запросов через точку от ссылки

Значения полей в цикле
Норма

Все поля заполнены ожидаемыми данными

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

Ошибка «Поле не обнаружено» или пустые значения там, где в базе они есть

Завершение цикла
Норма

Цикл завершается сам по окончании выборки

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

Цикл не завершается — проверьте наличие Следующий() в теле цикла

Удобный инструмент проверки — консоль запросов (внешняя обработка или встроенная в режиме предприятия у некоторых конфигураций). Выполните текст запроса с теми же параметрами и сравните набор строк с тем, что обходит ваш цикл. Это самый быстрый способ отделить ошибки в тексте запроса от ошибок в коде обхода.

Итог

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

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

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

Чем отличается Выбрать() от Выгрузить()?

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

Как узнать количество строк в результате запроса?

У выборки есть метод Количество(), но он может вернуть -1 для некоторых типов обхода. Надёжнее выгрузить результат в таблицу значений и взять её Количество(), либо посчитать строки в самом запросе функцией КОЛИЧЕСТВО(*).

Почему цикл по выборке зависает и не заканчивается?

Почти всегда причина в том, что в теле цикла забыли вызвать Выборка.Следующий() при использовании конструкции Пока Истина, либо условие цикла не связано с выборкой. Правильный шаблон — Пока Выборка.Следующий() Цикл: он гарантированно завершится.

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

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

Можно ли в цикле изменить данные, которые читает запрос?

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

Что быстрее: обход выборки или обход таблицы значений?

Для однократного последовательного чтения разница невелика, но выборка экономнее по памяти на больших объёмах. Таблица значений выигрывает, когда данные обходятся повторно или по ним выполняется поиск: повторное получение выборки означает повторное обращение к результату запроса.