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