Сбрасываем курсор выборки и читаем записи заново

· 9 мин

Содержание

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

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

Что такое выборка и зачем ей курсор

Выборка (ВыборкаИзРезультатаЗапроса) — это объект для последовательного перебора строк результата запроса. Сам результат целиком лежит в памяти, а выборка — это «бегунок» по нему.

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

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

  • 🔽 вперёд на одну запись — метод Следующий();
  • ⏮ в самое начало — метод Сбросить().

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

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

Как получить выборку и обойти её циклом

Выборку возвращает метод Выбрать() у результата запроса. Базовый шаблон:

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

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

Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование);
КонецЦикла;

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

К полям текущей записи обращаются по имени из запроса — Выборка.Наименование. Если имя поля вычисляется в коде или содержит пробелы, работает индексная форма Выборка["Наименование"]. Имена полей берутся из списка колонок после слова ВЫБРАТЬ с учётом псевдонимов КАК.

Узнать число записей заранее можно методом Количество():

Выборка = Результат.Выбрать();
Сообщить("Всего строк: " + Выборка.Количество());

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

Пропуск записей: метод СледующийПоЗначениюПоля()

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

Это удобно, когда результат отсортирован по этому полю и нужно пройтись только по первым строкам каждой группы — без вложенного цикла:

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

Пока Выборка.СледующийПоЗначениюПоля("Склад") Цикл
    Сообщить("Новый склад в списке: " + Выборка.Склад);
КонецЦикла;

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

Метод Сбросить(): вернуть курсор в начало

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

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

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

// Первый проход — считаем сумму
ОбщаяСумма = 0;
Пока Выборка.Следующий() Цикл
    ОбщаяСумма = ОбщаяСумма + Выборка.Сумма;
КонецЦикла;

// Возвращаем курсор в начало
Выборка.Сбросить();

// Второй проход — выводим долю каждой строки
Пока Выборка.Следующий() Цикл
    Если ОбщаяСумма > 0 Тогда
        Доля = Выборка.Сумма / ОбщаяСумма * 100;
        Сообщить(Выборка.Наименование + ": " + Формат(Доля, "ЧДЦ=1") + "%");
    КонецЕсли;
КонецЦикла;

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

Сбросить() возвращает курсор только в самое начало. Вернуться на одну запись назад или перейти к конкретной строке по номеру этим методом нельзя — выборка такого не умеет.

Выборка по группировкам: сброс в иерархии

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

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

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

    // Подчинённые записи текущей группы
    ВыборкаДетали = Выборка.Выбрать();
    Пока ВыборкаДетали.Следующий() Цикл
        Сообщить("   " + ВыборкаДетали.Наименование);
    КонецЦикла;
КонецЦикла;

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

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

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

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

У Сбросить() есть две альтернативы — выбор зависит от задачи.

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

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

ВыборкаПервая = Результат.Выбрать();   // первый обход
// ...
ВыборкаВторая = Результат.Выбрать();   // ещё один обход с нуля

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

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

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

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

// Произвольный доступ, сортировка, поиск — всё доступно
Таблица.Сортировать("Наименование");
Строка = Таблица[0];              // доступ по индексу
Найденные = Таблица.НайтиСтроки(Новый Структура("Склад", СкладСсылка));

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

Есть ли у выборки «Очистить» и как освободить память

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

Метод Очистить() — это про другие объекты: он есть у таблицы значений, списка значений, соответствия. Если под «очистить выборку» имеется в виду освободить данные, которые вы уже загрузили в таблицу значений, подойдёт Таблица.Очистить() — он удаляет все строки, оставляя колонки.

Освобождать память вручную в большинстве случаев не нужно — этим занимается сборщик мусора платформы. Объект результата и выборка исчезнут из памяти, как только на них не останется ссылок:

Результат = Неопределено;
Выборка = Неопределено;

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

Главный способ сэкономить память — не тянуть в запрос лишние поля и ограничивать выборку условиями ГДЕ, а не фильтровать потом в коде.

Что именно вам нужно от повторной работы с данными?

Частые ошибки и оптимизация

Разберём, что чаще всего ломает работу с выборкой.

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

Обращение к полю до первого Следующий(). До старта цикла курсор стоит перед первой записью. Строки Сообщить(Выборка.Наименование) без предшествующего Следующий() упадут с ошибкой.

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

Попытка шагнуть назад или к строке по номеру. Выборка однонаправленная. Если логика требует возвращаться к предыдущим строкам или прыгать по индексу — это сигнал выгрузить результат в таблицу значений.

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

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

Сведём способы повторной работы с данными в таблицу:

СпособЧто делаетКогда применять
Сбросить()Курсор той же выборки — в началоВторой проход по тем же записям
Повторный Выбрать()Новая выборка из того же результатаТо же, альтернатива сбросу
Выгрузить() в таблицуДанные в коллекцию, доступ по индексуМного проходов, сортировка, поиск
Повторный Выполнить()Новый запрос к базеНужны изменившиеся данные
Проверка перед повторным обходом выборки
Выполнено: 0 / 5

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

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