Пустой результат запроса в 1С: как поймать его в коде и не получить ошибку

· 9 мин

Содержание

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

Нюанс в том, что «пустой результат» и «пустое значение поля» — разные вещи. Результат может содержать строки, но с NULL в колонке после левого соединения. Ниже — все рабочие методы проверки, перебор выборки без ошибок, разбор NULL и то, как не просадить производительность на существовании данных.

Метод Пустой() у результата запроса

Основной инструмент — метод Пустой() объекта РезультатЗапроса. Его возвращает Выполнить(), и вызывать Пустой() нужно именно у этого объекта, а не у выборки. Сам запрос всегда выполняется на сервере, поэтому в клиент-серверной базе проверку размещают в серверном контексте модуля.

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

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

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

Почему этот способ лучше остальных для простой проверки «есть данные или нет»:

  • 📦 платформа не грузит все строки в память — проверяется только факт наличия записей;
  • ⚡ не нужно создавать выборку или выгружать таблицу значений ради одного Если;
  • 🧩 читается одной строкой, логика ветвления сразу видна.

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

Проверка через выборку: Количество() и Следующий()

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

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

Через количество строк:

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

Если Выборка.Количество() = 0 Тогда
    Возврат;
КонецЕсли;

Через первый вызов Следующий():

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

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

// первая строка уже доступна — её можно сразу обработать
ЗаполнитьПоДанным(Выборка);

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

Обработка пустой выборки в цикле

Чаще всего выборку не проверяют отдельно, а сразу перебирают циклом Пока. Здесь проверка на пустоту уже встроена в сам цикл — отдельный Если не нужен.

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

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

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

Отдельная проверка Пустой() перед циклом нужна только в одном случае — когда на пустом результате надо выполнить своё действие:

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

Если Результат.Пустой() Тогда
    ЗаписатьВЖурнал("Нет остатков для списания");
Иначе
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
        Списать(Выборка.Номенклатура);
    КонецЦикла;
КонецЕсли;

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

Проверка полей на NULL внутри запроса

Отдельная задача — поле в строке пустое, хотя строка есть. Так бывает после левого соединения: справа совпадения нет, и платформа подставляет NULL. Для результата Пустой() вернёт Ложь — строки-то есть.

NULL обрабатывают прямо в тексте запроса, а не в коде. Это надёжнее: число после замены всегда корректно участвует в сравнениях и арифметике.

Функция ЕСТЬNULL (аналог ISNULL):

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

Предикат ЕСТЬ NULL в условии:

ГДЕ
    Остатки.Номенклатура ЕСТЬ NULL

Так отбирают именно те строки, где соединение не нашло пару. Обратное условие — ЕСТЬ НЕ NULL.

Для ссылочных полей пустота проверяется иначе — через пустую ссылку:

ГДЕ
    Продажи.Договор <> ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)

Когда по NULL нужно принять решение прямо в выборке, помогает конструкция ВЫБОР — результат приходит уже готовым, без разбора пустых значений в коде:

ВЫБОР
    КОГДА Остатки.КоличествоОстаток ЕСТЬ NULL
        ТОГДА "Нет на складе"
    ИНАЧЕ "В наличии"
КОНЕЦ КАК Статус

Не путайте три разных «пусто»: строк в результате нет (Пустой()), поле не заполнено ссылкой (сравнение с ПустаяСсылка), поля нет из-за соединения (NULL). Проверяются они по-разному.

Что быстрее и как не грузить сервер

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

СпособЧто делает платформаКогда уместно
Результат.Пустой()проверяет факт наличия строкнужен только ответ «есть / нет»
Выбрать() + Следующий()читает строки по мере обходаданные всё равно перебираются дальше
Выгрузить().Количество()грузит все строки в таблицу значенийнужна и таблица, и её размер

Главный принцип: не выгружайте весь результат в таблицу значений только ради проверки на пустоту. Выгрузить().Количество() = 0 работает, но тянет в память все записи — на регистре в сотни тысяч строк это лишняя нагрузка, когда достаточно Пустой().

Второй приём — ограничить сам запрос, если нужно лишь существование:

ВЫБРАТЬ ПЕРВЫЕ 1
    Продажи.Ссылка
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
    Продажи.Контрагент = &Контрагент

ВЫБРАТЬ ПЕРВЫЕ 1 подсказывает серверу СУБД, что дальше первой подходящей записи искать не нужно. В связке с Пустой() получается самый дешёвый способ ответить на вопрос «есть ли хоть одна продажа этому контрагенту».

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

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

Если Выборка.Следующий() Тогда
    ПоследняяДата = Выборка.Дата;
Иначе
    ПоследняяДата = '00010101'; // значения нет, берём пустую дату
КонецЕсли;

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

Пустой результат и временные таблицы

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

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

МВТ = Новый МенеджерВременныхТаблиц;

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

// проверяем, пустая ли получилась временная таблица
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Номенклатура ИЗ ВремТовары";
Если Запрос.Выполнить().Пустой() Тогда
    Сообщить("Во временную таблицу не попало ни одной строки");
КонецЕсли;

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

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

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

Проверьте себя
Что возвращает `Результат.Пустой()`, если в запросе есть строки с `NULL` в поле?
У какого объекта есть метод `Пустая()`?
Сколько раз выполнится `Пока Выборка.Следующий() Цикл` на пустой выборке?

Агрегатные запросы: почему Пустой() даёт Ложь

Отдельный случай, на котором спотыкаются даже опытные разработчики. Запрос с агрегатной функцией (СУММА, КОЛИЧЕСТВО, МАКСИМУМ) без группировки всегда возвращает ровно одну строку — даже если исходных данных нет.

ВЫБРАТЬ
    СУММА(Продажи.Сумма) КАК Сумма
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Продажи
ГДЕ
    Продажи.Ссылка.Контрагент = &Контрагент

Если продаж нет, запрос вернёт одну строку, где в поле Сумма будет NULL (сумма пустого набора). Результат.Пустой() для такого запроса всегда равен Ложь — строка-то есть. Проверять надо не наличие строки, а само значение:

Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();

Если ЗначениеЗаполнено(Выборка.Сумма) Тогда
    // продажи были
КонецЕсли;

Либо обернуть агрегат в ЕСТЬNULL(СУММА(...), 0) прямо в запросе и сравнить с нулём.

Как только в запросе появляется группировка (СГРУППИРОВАТЬ ПО), поведение меняется: нет исходных строк — нет и групп, результат становится пустым, и Пустой() снова работает как обычно. Разница именно в наличии СГРУППИРОВАТЬ ПО.

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

Ошибки здесь редко вызывают явный сбой — чаще код тихо работает не так. Вот что встречается постоянно:

  • 🔁 Проверяют Результат = Неопределено. Выполнить() всегда возвращает объект РезультатЗапроса, даже когда строк нет. Сравнение с Неопределено бессмысленно — используйте Пустой().
  • 🧯 Обращаются к полю выборки без успешного Следующий(). Выборка.Ссылка до первого Следующий() или после его Ложь даёт ошибку. Сначала проверка, потом поле.
  • 🧮 Путают Пустой() и Пустая(). Первый — у результата запроса (есть ли строки), второй — у ссылки (заполнена ли она). Подставив один вместо другого, получите ошибку компиляции или неверную логику.
  • 🧷 Считают пустое поле отсутствием строки. После левого соединения строка есть, а поле — NULL. Пустой() этого не поймает, нужен ЕСТЬNULL или предикат ЕСТЬ NULL.
  • 🐌 Выгружают весь результат ради Количество() = 0. На больших регистрах это лишняя память и время. Для факта наличия достаточно Пустой().

Отдельный сценарий — запрос неожиданно вернул пусто, хотя данные в базе точно есть. Пройдите по частым причинам:

Запрос вернул пусто, хотя данные должны быть
Выполнено: 0 / 5

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

Чем `Пустой()` отличается от `Пустая()`?
Пустой() — метод результата запроса, отвечает, есть ли в нём строки. Пустая() — метод ссылки, отвечает, заполнена ли сама ссылка. Это разные объекты и разные вопросы.
Может ли `Выполнить()` вернуть `Неопределено`?
Нет. Метод всегда возвращает объект РезультатЗапроса. Для пустого запроса это корректный объект, у которого Пустой() вернёт Истина. Проверять результат на Неопределено не нужно.
Как узнать число строк, не перебирая выборку вручную?
Через Результат.Выбрать().Количество() или Результат.Выгрузить().Количество(). Но если нужен только факт наличия строк, а не точное число, быстрее Пустой() — он не материализует данные.
Нужно ли проверять `Пустой()` перед циклом `Пока Выборка.Следующий()`?
Нет. На пустой выборке цикл просто не выполнится. Отдельная проверка нужна, только если на пустом результате требуется своё действие — например, запись в журнал.
Работает ли `Пустой()` с запросом, в котором есть ИТОГИ?
С итогами в результате появляются служебные строки группировок, и проверка факта «есть ли данные» становится неоднозначной. Для таких запросов надёжнее проверять выборку детальных записей (ТипЗаписи() = ДетальнаяЗапись) или делать отдельный лёгкий запрос без ИТОГИ.