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