К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как работать с результатом запроса в 1С

Результат запроса в 1С: методы Выполнить, Выбрать, Выгрузить, обход по группировкам, проверка на пустоту, работа с таблицей значений и типичные ошибки разработчика.

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

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

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

Что возвращает метод Выполнить и как устроен результат

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

Типичный скелет кода выглядит так:

Запрос = Новый Запрос;

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

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

Если РезультатЗапроса.Пустой() Тогда

Возврат;

КонецЕсли;

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

Пока Выборка.Следующий() Цикл

// обработка строки

КонецЦикла;

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

Как проверить результат на пустоту и зачем это делать

Метод Пустой() возвращает Истина, если запрос не вернул ни одной строки. Это дешёвая проверка, которая позволяет не создавать выборку и не выгружать таблицу, когда данных нет. Проверять результат стоит всегда, если дальнейшая логика зависит от наличия данных: например, перед формированием документа или выводом сообщения пользователю.

Распространённая ошибка — проверять пустоту через выгрузку:

// Неоптимально

Если Запрос.Выполнить().Выгрузить().Количество() = 0 Тогда

...

КонецЕсли;

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

Отдельный случай — запросы с итогами. Запрос вида ВЫБРАТЬ СУММА(Количество) ... ИТОГИ ПО ОБЩИЕ может вернуть одну строку с NULL-значениями даже при отсутствии данных. Здесь Пустой() вернёт Ложь, и проверять нужно сами значения полей через ЗначениеЗаполнено() или сравнение с Неопределено/NULL.

Последовательный обход выборкой: когда и как

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

Базовый цикл:

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

Пока Выборка.Следующий() Цикл

Сообщить(Строка(Выборка.Номенклатура) + ": " + Выборка.Количество);

КонецЦикла;

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

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

Если Выборка.Следующий() Тогда

Остаток = Выборка.Количество;

Иначе

Остаток = 0;

КонецЕсли;

Выгрузка в таблицу значений: когда она оправдана

Метод Выгрузить() материализует весь результат в таблицу значений в памяти. Это даёт произвольный доступ: обращение по индексу ТЗ[0], поиск через Найти() и НайтиСтроки(), сортировку Сортировать(), свёртку Свернуть(), итоги по колонкам Итог(). Таблицу можно многократно передавать между процедурами, помещать в реквизит формы, использовать как источник для СКД или табличного документа.

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

Для Каждого СтрокаТЗ Из ТЗ Цикл

// многократные проходы, поиск, сортировка доступны

КонецЦикла;

Цена вопроса — память. Выгрузка миллионов строк создаёт полную копию данных на сервере. Если задача — один раз пройти по строкам, выгрузка избыточна. Правило простое: нужен повторный или произвольный доступ — выгружайте; нужен один проход — обходите выборкой.

Для ускорения поиска по таблице значений добавляйте индексы: ТЗ.Индексы.Добавить("Номенклатура"). Без индекса метод Найти() перебирает строки линейно, и на больших таблицах это становится узким местом.

Выборка или таблица значений

Выбрать() — выборка

Один последовательный проход, минимум памяти. Нет доступа по индексу, нельзя вернуться назад. Подходит для потоковой обработки больших данных.

Выгрузить() — таблица значений

Произвольный доступ, поиск, сортировка, многократные проходы. Требует памяти под весь набор данных. Подходит для небольших и средних результатов со сложной логикой.

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

Иерархический обход с итогами по уровням. Требует ИТОГИ ПО в тексте запроса. Подходит для древовидных отчётов.

Обход по группировкам и работа с итогами

Когда в запросе есть секция ИТОГИ ПО, результат становится иерархическим. Для его обработки выборка создаётся с параметром обхода:

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

Пока ВыборкаПоСкладам.Следующий() Цикл

// уровень склада — здесь доступны итоговые значения

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

Пока ВыборкаПоТоварам.Следующий() Цикл

// детальные строки

КонецЦикла;

КонецЦикла;

Вложенные выборки получаются из родительской выборки, а не из результата запроса — это частая ошибка. Уровни вложенности должны соответствовать порядку полей в секции ИТОГИ ПО.

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

Порядок работы с результатом запроса
  1. 1
    Сформируйте текст запроса, задайте параметры через УстановитьПараметр()
  2. 2
    Выполните запрос: РезультатЗапроса = Запрос.Выполнить()
  3. 3
    Проверьте РезультатЗапроса.Пустой(), если дальнейшая логика зависит от наличия данных
  4. 4
    Выберите способ доступа: Выбрать() для однократного чтения, Выгрузить() для произвольного доступа
  5. 5
    При иерархии используйте обход ПоГруппировкам и вложенные выборки из родительской
  6. 6
    Проверьте результат на тестовых данных: количество строк, итоги, значения полей

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

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

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

ТЗТовары = МассивРезультатов[0].Выгрузить();

ТЗЦены = МассивРезультатов[1].Выгрузить();

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

Типичные ошибки при обработке результата

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

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

Типичные ошибки
01
Чтение полей выборки без вызова Следующий() — поля пустые, логика молча работает неверно
02
Проверка пустоты через Выгрузить().Количество() — лишняя выгрузка всего набора в память
03
Запрос внутри цикла по выборке другого запроса — кратный рост обращений к СУБД
04
Получение вложенной выборки из РезультатЗапроса вместо родительской выборки — ошибка или неверные данные
05
Неверный индекс в массиве ВыполнитьПакет() из-за неучтённых временных таблиц
06
Поиск по таблице значений без индексов — линейный перебор на больших объёмах
Контрольные точки

Как проверить корректность обработки

Количество обработанных строк
Норма

Совпадает с числом строк, которое возвращает тот же запрос в консоли запросов

Тревожный признак

Строк меньше — возможно, обход прерван или выборка исчерпана повторным использованием

Значения полей в первой строке
Норма

Поля заполнены данными из базы

Тревожный признак

Поля пустые — вероятно, чтение идёт до вызова Следующий()

Итоги по группировкам
Норма

Суммы по уровням сходятся с детальными строками

Тревожный признак

Итоги нулевые или задвоенные — неверный порядок обхода или дублирование уровней

Итоговая проверка проста: выполните тот же запрос в консоли запросов и сравните число строк и контрольные суммы с тем, что обработал ваш код. Расхождение почти всегда означает ошибку в логике обхода, а не в запросе.

Итог

Что важно запомнить

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

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

Можно ли обойти выборку дважды?

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

Что возвращает запрос с ИТОГИ ПО ОБЩИЕ, если данных нет?

Такой запрос может вернуть одну строку со значениями NULL в полях-итогах, поэтому Пустой() вернёт Ложь. Проверяйте сами значения полей, например через ЗначениеЗаполнено(), или добавьте в запрос агрегатную функцию ЕСТЬNULL для подмены NULL нулём.

Как получить только первую строку результата?

Ограничьте запрос конструкцией ВЫБРАТЬ ПЕРВЫЕ 1, затем получите выборку и вызовите Следующий() один раз. Если метод вернул Истина — поля доступны для чтения. Это дешевле, чем выгружать весь результат и брать строку по индексу.

Чем отличается Выгрузить() от Выгрузить(ПоГруппировкамСИерархией)?

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

Почему обращение к полю выборки выдаёт ошибку «Поле не найдено»?

Имя поля в коде не совпадает с именем в запросе. Проверьте псевдонимы: если поле задано выражением без КАК, платформа присваивает ему сгенерированное имя. Задавайте явные псевдонимы для всех вычисляемых полей и обращайтесь к ним по этим именам.

Когда нужен менеджер временных таблиц?

Когда одна и та же временная таблица должна использоваться в нескольких отдельных выполнениях запроса. Создайте Новый МенеджерВременныхТаблиц, присвойте его свойству МенеджерВременныхТаблиц каждого запроса — тогда ВТ, созданная первым запросом, будет видна последующим. Внутри одного пакетного запроса менеджер не обязателен.