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

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

1С: как начать выборку сначала

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

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

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

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

Почему выборка в 1С не умеет возвращаться назад

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

То же относится к выборкам, полученным через Справочники.Номенклатура.Выбрать(), Документы.РеализацияТоваровУслуг.Выбрать() и подобные методы менеджеров. Попытка «дойти до конца и начать заново» тем же объектом выборки не сработает: после последней строки Следующий() будет стабильно возвращать Ложь.

Как начать выборку запроса сначала

Правильный способ — сохранить результат запроса в переменную и вызывать Выбрать() столько раз, сколько нужно обходов. Результат запроса (тип «РезультатЗапроса») живёт в памяти, поэтому повторный вызов Выбрать() не обращается к СУБД заново и работает быстро.

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

Запрос.Текст = "ВЫБРАТЬ Ссылка, Наименование ИЗ Справочник.Номенклатура";

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

// Первый обход

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

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

// обработка строк

КонецЦикла;

// Второй обход — новая выборка из того же результата

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

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

// обход начался с первой строки

КонецЦикла;

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

Повторный обход выборки справочника или документа

Выборки, полученные через менеджеры объектов (Справочники.Х.Выбрать(), Документы.Х.Выбрать(), Перечисления.Х.Значения не считается), ведут себя так же: курсор только вперёд. Для второго прохода вызовите метод менеджера ещё раз с теми же параметрами отбора.

// Первый обход

Выборка = Справочники.Контрагенты.Выбрать();

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

// ...

КонецЦикла;

// Второй обход — новый вызов

Выборка = Справочники.Контрагенты.Выбрать();

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

// снова с первого элемента

КонецЦикла;

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

Обход без повторного чтения: таблица значений

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

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

// Сколько угодно обходов

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

// ...

КонецЦикла;

Для Инд = 0 По Таблица.Количество() - 1 Цикл

Строка = Таблица[Инд]; // доступ с любой позиции

КонецЦикла;

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

Способы повторного обхода

Повторный Выбрать() у результата запроса

Быстро, без нового обращения к СУБД. Подходит, когда результат запроса сохранён в переменной.

Повторный Выбрать() у менеджера объекта

Новое чтение из базы при каждом вызове. Данные могут отличаться между обходами.

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

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

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

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

Типичные сценарии и подводные камни

Вложенные обходы одной выборки

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

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

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

Выборка по группировкам

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

Типичные ошибки
01
Повторное использование старой выборки: после конца данных Следующий() всегда возвращает Ложь, цикл молча не выполняется.
02
Проверка первой строки через Следующий() перед основным циклом — первая запись теряется из обработки.
03
Вложенный обход той же выборки — внешний цикл завершается досрочно, данные обрабатываются частично.
04
Повторный Выполнить() запроса вместо повторного Выбрать() у сохранённого результата — лишняя нагрузка на СУБД.
05
Выгрузка миллионов строк в таблицу значений ради одного повторного обхода — перерасход памяти на сервере.

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

Самый простой контроль — счётчик обработанных строк в каждом проходе и сравнение с Выборка.Количество() (для результата запроса метод доступен) или с количеством строк таблицы значений. Если второй проход обработал ноль строк при непустом результате — вы переиспользовали старую выборку.

Для отладки удобно выводить первую строку каждого прохода: если повторный обход действительно начался сначала, первая строка обоих проходов совпадёт. В отладчике конфигуратора это проверяется точкой останова на первой итерации цикла.

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

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

Счётчик строк второго прохода
Норма

Равен числу строк первого прохода

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

Ноль или меньше — выборка не сброшена

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

Совпадает с первой строкой первого обхода

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

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

Источник второй выборки
Норма

Новый вызов Выбрать() у результата или менеджера

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

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

Порядок действий при необходимости повторного обхода

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

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

Выборка в 1С — однонаправленный курсор без перемотки. Повторный обход делается новым вызовом Выбрать() у сохранённого результата запроса (без обращения к СУБД) или у менеджера объекта (с новым чтением из базы). Для многократных и вложенных обходов выгружайте данные в таблицу значений.

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

Есть ли в 1С метод типа «Сбросить» или «ПерейтиВНачало» у выборки?

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

Повторный Выбрать() у результата запроса — это новое обращение к базе?

Нет. Результат запроса уже материализован в памяти, и Выбрать() просто создаёт новый курсор по нему. А вот повторный Выполнить() запроса или новый Выбрать() у менеджера справочника — это уже обращение к СУБД.

Как пропустить первые N строк выборки?

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

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

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

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

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

Можно ли начать обход таблицы значений сначала после цикла «Для Каждого»?

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