Чтение данных из результата запроса: Следующий(), поля выборки, итоги и выгрузка в таблицу

· 9 мин

Содержание

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

Какой способ выбрать, зависит от задачи. Перебрать тысячи строк и сразу их обработать — выборка. Отсортировать, свернуть, передать в другую процедуру или на клиент — таблица значений. Нужно ровно одно число — запрос с ВЫБРАТЬ ПЕРВЫЕ 1 и проверка Если.

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

Цепочка всегда одна и та же. Запрос → Выполнить() → объект РезультатЗапроса → Выбрать() → объект ВыборкаИзРезультатаЗапроса.

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

Что важно помнить про курсор:

  • 👉 двигается только вперёд: вернуться на предыдущую строку нельзя;
  • 🔄 начать сначала можно методом Сбросить() — курсор снова встанет перед первой записью;
  • 📏 Количество() возвращает число записей на текущем уровне выборки, без сдвига курсора;
  • 🖥️ запросы выполняются на сервере, поэтому код с выборкой пишут в процедурах &НаСервере или в серверных общих модулях.

Минимальный рабочий пример:

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

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

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

Имя поля после точки — это псевдоним из КАК. Переименовали поле в запросе — меняйте и обращение в коде.

Перебор всех строк циклом Пока Выборка.Следующий()

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

Пример: собрать сумму продаж по контрагентам в соответствие.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Продажи.Контрагент КАК Контрагент,
|	СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
|	РегистрНакопления.Продажи КАК Продажи
|ГДЕ
|	Продажи.Период МЕЖДУ &Начало И &Конец
|СГРУППИРОВАТЬ ПО
|	Продажи.Контрагент";
Запрос.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДатаСеанса()));

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

Имя регистра и его измерения в вашей конфигурации могут быть другими — пример показывает приём, а не конкретные метаданные.

Если тело цикла разрослось на сотню строк, вынесите обработку одной записи в отдельную процедуру и передавайте туда выборку. Читать такой код проще, а курсор остаётся один.

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

Когда запрос возвращает одну строку (остаток, курс, последняя цена), цикл не нужен:

Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
|	Цены.Цена КАК Цена
|ИЗ
|	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Товар) КАК Цены";
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("Товар", Товар);

Цена = 0;
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
	Цена = Выборка.Цена;
КонецЕсли;

Значение по умолчанию задаётся до проверки. Если записей нет, переменная не останется неопределённой и код ниже не упадёт.

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

Цикл Для каждого: когда он работает, а когда нет

ВыборкаИзРезультатаЗапроса не поддерживает обход Для каждого. Написать Для каждого Строка Из Выборка Цикл нельзя: платформа сообщит, что для этого значения не определён итератор.

Цикл Для каждого работает по коллекциям. Чтобы получить коллекцию из результата запроса, вызовите Выгрузить():

Таблица = Запрос.Выполнить().Выгрузить();
Для каждого СтрокаТаблицы Из Таблица Цикл
	Сообщить(СтрокаТаблицы.Наименование);
КонецЦикла;

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

Ещё два полезных приёма:

  • 📋 Результат.Выгрузить().ВыгрузитьКолонку("Ссылка") — сразу массив значений одной колонки, удобно для параметра В (&Список) следующего запроса;
  • 🔍 Таблица.Найти(Значение, "Колонка") — поиск строки по значению без ручного перебора.
ЗадачаЧем пользоватьсяПочему
Обработать строки по одной и забытьВыбрать() + Пока Следующий()нет лишней копии данных в памяти
Одно значениеПЕРВЫЕ 1 + Если Следующий()минимум кода и данных
Сортировать, сворачивать, искатьВыгрузить() + Для каждогометоды таблицы значений
Передать на клиент или в другую процедуруВыгрузить()выборка на клиенте недоступна
Список значений одной колонкиВыгрузить().ВыгрузитьКолонку()сразу массив

Поля выборки: NULL, ссылки и типы данных

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

NULL после левого соединения

Если в ЛЕВОЕ СОЕДИНЕНИЕ справа ничего не нашлось, поле вернёт NULL. Сложить NULL с числом нельзя — будет ошибка преобразования. Надёжнее убрать NULL прямо в запросе:

"ВЫБРАТЬ
|	Товары.Ссылка КАК Товар,
|	ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
|ИЗ
|	Справочник.Номенклатура КАК Товары
|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|		ПО Товары.Ссылка = Остатки.Номенклатура"

Если заменить в запросе нельзя, проверяйте в коде: Если Выборка.Остаток = NULL Тогда.

Ссылка и её реквизиты

Обращение Выборка.Контрагент.ИНН формально работает, но для каждой строки платформа делает отдельное чтение объекта из базы. На 10 строках этого не видно, на 50 000 — отчёт ждёт минуты.

Правильно — получить реквизит в самом запросе:

"ВЫБРАТЬ
|	Заказы.Контрагент КАК Контрагент,
|	Заказы.Контрагент.ИНН КАК ИНН,
|	ПРЕДСТАВЛЕНИЕ(Заказы.Контрагент) КАК КонтрагентПредставление
|ИЗ
|	Документ.ЗаказКлиента КАК Заказы"

В запросе разыменование через точку превращается в соединение и выполняется одним обращением к СУБД, а не тысячей.

Поле по имени из переменной и по номеру

Иногда имя поля заранее неизвестно — например, в универсальной выгрузке. Тогда пригодятся:

  • 🧩 Результат.Колонки — коллекция колонок результата, у каждой есть Имя и ТипЗначения;
  • 🔢 Выборка.Получить(Индекс) — значение поля по номеру колонки, нумерация с нуля.
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
	Для Индекс = 0 По Результат.Колонки.Количество() - 1 Цикл
		ИмяКолонки = Результат.Колонки[Индекс].Имя;
		Значение = Выборка.Получить(Индекс);
		Сообщить(ИмяКолонки + ": " + Строка(Значение));
	КонецЦикла;
КонецЦикла;

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

Итоги и вложенные выборки

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

Запрос.Текст =
"ВЫБРАТЬ
|	Продажи.Контрагент КАК Контрагент,
|	Продажи.Номенклатура КАК Номенклатура,
|	СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
|	РегистрНакопления.Продажи КАК Продажи
|СГРУППИРОВАТЬ ПО
|	Продажи.Контрагент,
|	Продажи.Номенклатура
|ИТОГИ
|	СУММА(Сумма)
|ПО
|	Контрагент";

ВыборкаКлиенты = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаКлиенты.Следующий() Цикл
	Сообщить("Клиент: " + ВыборкаКлиенты.Контрагент + ", итого " + Формат(ВыборкаКлиенты.Сумма, "ЧДЦ=2"));
	ВыборкаТовары = ВыборкаКлиенты.Выбрать();
	Пока ВыборкаТовары.Следующий() Цикл
		Сообщить("   " + ВыборкаТовары.Номенклатура + ": " + Формат(ВыборкаТовары.Сумма, "ЧДЦ=2"));
	КонецЦикла;
КонецЦикла;

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

Три варианта обхода:

Значение ОбходРезультатаЗапросаЧто получите
Прямойвсе записи подряд (по умолчанию)
ПоГруппировкамобход по уровням группировок, без учёта иерархии
ПоГруппировкамСИерархиейпо уровням с учётом иерархии справочника

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

Поиск записи без ручного перебора

НайтиСледующий() двигает курсор вперёд до первой записи с нужным значением и возвращает Истина, если нашёл:

Отбор = Новый Структура("Контрагент", Контрагент);
Выборка = Результат.Выбрать();
Если Выборка.НайтиСледующий(Отбор) Тогда
	Сумма = Выборка.Сумма;
КонецЕсли;

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

Скорость: как не тормозить на больших выборках

Медленный код с выборкой почти всегда тормозит из-за обращений к базе внутри цикла, а не из-за самого цикла.

Что проверить в первую очередь:

  1. ⛔ Запрос в цикле. Запрос.Выполнить() внутри Пока Выборка.Следующий() на 5 000 строк — это 5 000 обращений к СУБД. Соберите ключи в массив и сделайте один запрос с В (&Список) или соединение.
  2. 🔗 Реквизиты через точку в коде. Выборка.Ссылка.Реквизит читает объект каждый раз. Получайте реквизит полем запроса.
  3. 🧱 ПолучитьОбъект() ради чтения. Объект грузится целиком, с табличными частями. Для чтения хватает полей запроса; объект нужен, только если его меняют и записывают.
  4. 📦 Лишние поля и строки. Отбирайте в ГДЕ и выбирайте только нужные колонки. ВЫБРАТЬ * в рабочем коде не нужен.
  5. 🗂️ Двойная копия. Выгрузить() создаёт таблицу значений в памяти сервера. Если данные нужны один раз по порядку — достаточно выборки.

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

Результаты = Запрос.ВыполнитьПакет();
ВыборкаИтог = Результаты[Результаты.Количество() - 1].Выбрать();

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

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

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

Диагностика: выберите симптом

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

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

Можно ли вернуть выборку с сервера на клиент?
Нет. Выборка существует только на сервере. Передайте на клиент массив, структуру или результат Выгрузить(), помещённый во временное хранилище.
Чем выборка из запроса отличается от Справочники.Номенклатура.Выбрать()?
Это разные объекты. Справочники.Имя.Выбрать() возвращает выборку справочника, у неё есть ПолучитьОбъект() и доступны все реквизиты элемента. Выборка из запроса содержит только поля, перечисленные в ВЫБРАТЬ, зато позволяет соединения, группировки и итоги.
Как узнать число записей, не перебирая выборку?
Вызовите Выборка.Количество(). Он возвращает число записей текущего уровня и курсор не сдвигает.
Как получить из выборки массив значений одной колонки?
Короче всего — Результат.Выгрузить().ВыгрузитьКолонку("ИмяПоля"). Вручную — создать массив и в цикле Пока Выборка.Следующий() добавлять Выборка.ИмяПоля.
Можно ли изменить значение поля в выборке?
Нет, выборка только для чтения. Если данные нужно поправить перед использованием, выгрузите их в таблицу значений и меняйте строки таблицы.