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