Чтение данных из результата запроса: Следующий(), поля выборки, итоги и выгрузка в таблицу
Содержание
Чтобы получить значение из выборки в 1С, вызовите Выборка.Следующий() и обратитесь к полю по псевдониму из запроса: Выборка.Сумма. Для перебора всех строк — цикл Пока Выборка.Следующий() Цикл, для одной строки — Если Выборка.Следующий() Тогда. Если нужна коллекция для Для каждого, сначала сделайте Результат.Выгрузить() — по самой выборке такой цикл не работает
.
Какой способ выбрать, зависит от задачи. Перебрать тысячи строк и сразу их обработать — выборка. Отсортировать, свернуть, передать в другую процедуру или на клиент — таблица значений. Нужно ровно одно число — запрос с ВЫБРАТЬ ПЕРВЫЕ 1 и проверка Если.
Как устроена выборка из результата запроса
Цепочка всегда одна и та же. Запрос → Выполнить() → объект РезультатЗапроса → Выбрать() → объект ВыборкаИзРезультатаЗапроса.
Выборка — это курсор. Сразу после Выбрать() он стоит перед первой записью, а не на ней. Метод Следующий() сдвигает курсор на одну строку и возвращает Истина, если строка есть, и Ложь, если записи кончились.
Что важно помнить про курсор:
- 👉 двигается только вперёд: вернуться на предыдущую строку нельзя;
- 🔄 начать сначала можно методом
Сбросить()— курсор снова встанет перед первой записью; - 📏
Количество()возвращает число записей на текущем уровне выборки, без сдвига курсора; - 🖥️ запросы выполняются на сервере, поэтому код с выборкой пишут в процедурах
&НаСервереили в серверных общих модулях.
Минимальный рабочий пример:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Имя поля после точки — это псевдоним из КАК. Переименовали поле в запросе — меняйте и обращение в коде.
Перебор всех строк циклом Пока Выборка.Следующий()
Классический цикл — самый частый способ получить значения из выборки. Условие цикла одновременно сдвигает курсор и проверяет, что запись есть. Внутри цикла вы читаете поля текущей строки.
Пример: собрать сумму продаж по контрагентам в соответствие.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Контрагент КАК Контрагент,
| СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &Начало И &Конец
|СГРУППИРОВАТЬ ПО
| Продажи.Контрагент";
Запрос.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДатаСеанса()));
ПродажиПоКлиентам = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ПродажиПоКлиентам.Вставить(Выборка.Контрагент, Выборка.Сумма);
КонецЦикла;
Имя регистра и его измерения в вашей конфигурации могут быть другими — пример показывает приём, а не конкретные метаданные.
Если тело цикла разрослось на сотню строк, вынесите обработку одной записи в отдельную процедуру и передавайте туда выборку. Читать такой код проще, а курсор остаётся один.
Как получить одно значение из выборки
Когда запрос возвращает одну строку (остаток, курс, последняя цена), цикл не нужен:
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Товар) КАК Цены";
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("Товар", Товар);
Цена = 0;
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Цена = Выборка.Цена;
КонецЕсли;
Значение по умолчанию задаётся до проверки. Если записей нет, переменная не останется неопределённой и код ниже не упадёт.
Проверить, пустой ли результат, можно и без выборки: Если Результат.Пустой() Тогда. Это удобно, когда сами данные не нужны — достаточно знать, есть ли хоть одна запись.
Цикл Для каждого: когда он работает, а когда нет
ВыборкаИзРезультатаЗапроса не поддерживает обход Для каждого. Написать Для каждого Строка Из Выборка Цикл нельзя: платформа сообщит, что для этого значения не определён итератор.
Цикл Для каждого работает по коллекциям. Чтобы получить коллекцию из результата запроса, вызовите Выгрузить():
Таблица = Запрос.Выполнить().Выгрузить();
Для каждого СтрокаТаблицы Из Таблица Цикл
Сообщить(СтрокаТаблицы.Наименование);
КонецЦикла;
Выгрузить() возвращает ТаблицаЗначений (или ДеревоЗначений, если выгружать по группировкам с иерархией). У таблицы есть то, чего нет у выборки: сортировка, свёртка, поиск по колонкам, индексы, обращение к строке по номеру.
Ещё два полезных приёма:
- 📋
Результат.Выгрузить().ВыгрузитьКолонку("Ссылка")— сразу массив значений одной колонки, удобно для параметраВ (&Список)следующего запроса; - 🔍
Таблица.Найти(Значение, "Колонка")— поиск строки по значению без ручного перебора.
| Задача | Чем пользоваться | Почему |
|---|---|---|
| Обработать строки по одной и забыть | Выбрать() + Пока Следующий() | нет лишней копии данных в памяти |
| Одно значение | ПЕРВЫЕ 1 + Если Следующий() | минимум кода и данных |
| Сортировать, сворачивать, искать | Выгрузить() + Для каждого | методы таблицы значений |
| Передать на клиент или в другую процедуру | Выгрузить() | выборка на клиенте недоступна |
| Список значений одной колонки | Выгрузить().ВыгрузитьКолонку() | сразу массив |
Поля выборки: NULL, ссылки и типы данных
Поле выборки возвращает значение того типа, который получился в запросе. Число — числом, дата — датой, ссылка — ссылкой. Сюрпризы начинаются в трёх местах.
NULL после левого соединения
Если в ЛЕВОЕ СОЕДИНЕНИЕ справа ничего не нашлось, поле вернёт NULL. Сложить NULL с числом нельзя — будет ошибка преобразования. Надёжнее убрать NULL прямо в запросе:
"ВЫБРАТЬ
| Товары.Ссылка КАК Товар,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
|ИЗ
| Справочник.Номенклатура КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
| ПО Товары.Ссылка = Остатки.Номенклатура"
Если заменить в запросе нельзя, проверяйте в коде: Если Выборка.Остаток = NULL Тогда.
Ссылка и её реквизиты
Обращение Выборка.Контрагент.ИНН формально работает, но для каждой строки платформа делает отдельное чтение объекта из базы. На 10 строках этого не видно, на 50 000 — отчёт ждёт минуты.
Правильно — получить реквизит в самом запросе:
"ВЫБРАТЬ
| Заказы.Контрагент КАК Контрагент,
| Заказы.Контрагент.ИНН КАК ИНН,
| ПРЕДСТАВЛЕНИЕ(Заказы.Контрагент) КАК КонтрагентПредставление
|ИЗ
| Документ.ЗаказКлиента КАК Заказы"
В запросе разыменование через точку превращается в соединение и выполняется одним обращением к СУБД, а не тысячей.
Поле по имени из переменной и по номеру
Иногда имя поля заранее неизвестно — например, в универсальной выгрузке. Тогда пригодятся:
- 🧩
Результат.Колонки— коллекция колонок результата, у каждой естьИмяиТипЗначения; - 🔢
Выборка.Получить(Индекс)— значение поля по номеру колонки, нумерация с нуля.
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Для Индекс = 0 По Результат.Колонки.Количество() - 1 Цикл
ИмяКолонки = Результат.Колонки[Индекс].Имя;
Значение = Выборка.Получить(Индекс);
Сообщить(ИмяКолонки + ": " + Строка(Значение));
КонецЦикла;
КонецЦикла;
Ещё вариант — ЗаполнитьЗначенияСвойств(Приёмник, Выборка): он копирует поля выборки в одноимённые свойства структуры, строки табличной части или записи регистра. Так из выборки удобно заполнять строки документа без перечисления каждого поля.
Итоги и вложенные выборки
Если в запросе есть ИТОГИ ПО, обычный Выбрать() вернёт все записи подряд: итоговые и детальные вперемешку. Чтобы обойти их по уровням, передайте в Выбрать() тип обхода:
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Контрагент КАК Контрагент,
| Продажи.Номенклатура КАК Номенклатура,
| СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|СГРУППИРОВАТЬ ПО
| Продажи.Контрагент,
| Продажи.Номенклатура
|ИТОГИ
| СУММА(Сумма)
|ПО
| Контрагент";
ВыборкаКлиенты = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаКлиенты.Следующий() Цикл
Сообщить("Клиент: " + ВыборкаКлиенты.Контрагент + ", итого " + Формат(ВыборкаКлиенты.Сумма, "ЧДЦ=2"));
ВыборкаТовары = ВыборкаКлиенты.Выбрать();
Пока ВыборкаТовары.Следующий() Цикл
Сообщить(" " + ВыборкаТовары.Номенклатура + ": " + Формат(ВыборкаТовары.Сумма, "ЧДЦ=2"));
КонецЦикла;
КонецЦикла;
Внешняя выборка ходит по строкам итогов по контрагенту. Выбрать() у текущей строки отдаёт вложенную выборку — детальные записи этого контрагента.
Три варианта обхода:
Значение ОбходРезультатаЗапроса | Что получите |
|---|---|
Прямой | все записи подряд (по умолчанию) |
ПоГруппировкам | обход по уровням группировок, без учёта иерархии |
ПоГруппировкамСИерархией | по уровням с учётом иерархии справочника |
Понять, какая перед вами запись, помогает Выборка.ТипЗаписи(). Он возвращает значение ТипЗаписиЗапроса: ДетальнаяЗапись, ИтогПоГруппировке, ИтогПоИерархии или ОбщийИтог. Уровень вложенности возвращает Выборка.Уровень().
Поиск записи без ручного перебора
НайтиСледующий() двигает курсор вперёд до первой записи с нужным значением и возвращает Истина, если нашёл:
Отбор = Новый Структура("Контрагент", Контрагент);
Выборка = Результат.Выбрать();
Если Выборка.НайтиСледующий(Отбор) Тогда
Сумма = Выборка.Сумма;
КонецЕсли;
Поиск идёт от текущей позиции. Чтобы искать второе значение с начала, вызовите Сбросить() перед вторым НайтиСледующий(). Если поисков много — быстрее выгрузить таблицу и проиндексировать колонку.
Скорость: как не тормозить на больших выборках
Медленный код с выборкой почти всегда тормозит из-за обращений к базе внутри цикла, а не из-за самого цикла.
Что проверить в первую очередь:
- ⛔ Запрос в цикле.
Запрос.Выполнить()внутриПока Выборка.Следующий()на 5 000 строк — это 5 000 обращений к СУБД. Соберите ключи в массив и сделайте один запрос сВ (&Список)или соединение. - 🔗 Реквизиты через точку в коде.
Выборка.Ссылка.Реквизитчитает объект каждый раз. Получайте реквизит полем запроса. - 🧱
ПолучитьОбъект()ради чтения. Объект грузится целиком, с табличными частями. Для чтения хватает полей запроса; объект нужен, только если его меняют и записывают. - 📦 Лишние поля и строки. Отбирайте в
ГДЕи выбирайте только нужные колонки.ВЫБРАТЬ *в рабочем коде не нужен. - 🗂️ Двойная копия.
Выгрузить()создаёт таблицу значений в памяти сервера. Если данные нужны один раз по порядку — достаточно выборки.
Несколько связанных запросов удобно отправлять одним пакетом через временные таблицы. Запрос.ВыполнитьПакет() возвращает массив результатов, по одному на каждый запрос пакета. Нужный результат берите по индексу и вызывайте у него Выбрать():
Результаты = Запрос.ВыполнитьПакет();
ВыборкаИтог = Результаты[Результаты.Количество() - 1].Выбрать();
Последний элемент массива — результат последнего запроса пакета. Запросы, создающие временные таблицы (ПОМЕСТИТЬ), тоже занимают места в массиве — индекс считайте с учётом всех запросов текста.
Если обработка с выборкой меняет данные (записывает объекты или наборы записей регистра), сначала прогоните её на копии базы. Ошибка в условии отбора в цикле изменит не одну запись, а все подходящие.
Почему значение из выборки не получается
Выборка. со словом после КАК. Поля, которые не попали в ВЫБРАТЬ, из выборки не прочитать.Выборка.Следующий() — например, в условии Если. Каждый вызов сдвигает курсор. Оставьте Следующий() только в условии цикла.Выборка.Сбросить() или получите новую выборку через Результат.Выбрать().Следующий(): курсор ещё стоит перед первой записью. Сначала сдвиньте курсор, потом читайте поле.NULL из левого соединения. Оберните поле в запросе в ЕСТЬNULL(..., 0) или проверяйте = NULL в коде.&НаСервере и верните на клиент простые значения или структуру.ИТОГИ, а выборка получена обходом по умолчанию. Передайте в Выбрать() ОбходРезультатаЗапроса.ПоГруппировкам и используйте вложенные выборки.Если ошибка не из списка, поставьте точку останова на строке после Выбрать() и посмотрите выборку в отладчике. В окне вычисления выражений видно, сколько записей вернул запрос и какие колонки есть у результата. Ещё один быстрый путь — открыть тот же текст в консоли запросов и убедиться, что запрос вообще возвращает строки при этих параметрах.
Частые вопросы
Можно ли вернуть выборку с сервера на клиент?
Выгрузить(), помещённый во временное хранилище.Чем выборка из запроса отличается от Справочники.Номенклатура.Выбрать()?
Справочники.Имя.Выбрать() возвращает выборку справочника, у неё есть ПолучитьОбъект() и доступны все реквизиты элемента. Выборка из запроса содержит только поля, перечисленные в ВЫБРАТЬ, зато позволяет соединения, группировки и итоги.Как узнать число записей, не перебирая выборку?
Выборка.Количество(). Он возвращает число записей текущего уровня и курсор не сдвигает.Как получить из выборки массив значений одной колонки?
Результат.Выгрузить().ВыгрузитьКолонку("ИмяПоля"). Вручную — создать массив и в цикле Пока Выборка.Следующий() добавлять Выборка.ИмяПоля.