Проверка пустого результата запроса: методы и примеры

· 9 мин

Содержание

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

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

Метод Пустой(): проверка результата целиком

Пустой() — метод объекта РезультатЗапроса. Он не зависит от числа колонок и типов полей: важен только факт наличия строк.

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

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

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

Можно не заводить переменную под результат и писать цепочкой:

Если Запрос.Выполнить().Пустой() Тогда
    // строк нет
КонецЕсли;

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

Есть и альтернатива без Пустой() — попробовать получить первую строку:

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

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

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

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

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

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

Иногда нужно проверить не результат целиком, а отдельное поле — отсеять строки, где значение отсутствует, или подставить замену. Это делается в самом тексте запроса.

Главный источник NULL — левые и полные соединения. Если в правой таблице нет соответствия, её поля приходят как NULL:

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

Функция ЕСТЬNULL(Поле, Замена) возвращает второй аргумент, если первый равен NULL. Это аналог ISNULL из SQL. Оборачивайте в ЕСТЬNULL любое числовое поле из соединения, иначе арифметика по нему даст ошибку.

Отобрать строки по наличию или отсутствию значения помогают операторы ЕСТЬ NULL и ЕСТЬ НЕ NULL:

ГДЕ
    Остатки.КоличествоОстаток ЕСТЬ НЕ NULL   // только совпавшие строки

Проверить в запросе, что поле-ссылка заполнено, можно сравнением с пустой ссылкой:

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

Конструкция ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) — это литерал пустой ссылки прямо в запросе, без параметра. Удобно, когда нужно отфильтровать незаполненные реквизиты.

Если логику проверки надо вынести в отдельную колонку-флаг, используйте ВЫБОР:

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

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

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

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

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

Если Пакет[1].Пустой() Тогда
    Сообщить("По выбранному виду номенклатуры ничего нет");
КонецЕсли;

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

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

Какой способ выбрать

Способ зависит от того, что именно вы проверяете: весь результат, строку выборки или поле.

Нужно проверитьИнструментГде работает
Результат вернул хоть одну строкуРезультат.Пустой()встроенный язык
Есть ли следующая строка выборкиВыборка.Следующий()встроенный язык
Значение переменной заполненоЗначениеЗаполнено(Знач)встроенный язык
Ссылка пустаяСсылка.Пустая()встроенный язык
Поле отсутствует (нет совпадения)Поле ЕСТЬ NULLязык запросов
Подставить замену вместо NULLЕСТЬNULL(Поле, 0)язык запросов

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

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

NULL, пустая ссылка и Неопределено — в чём разница

Три разных «пустых» значения часто путают, а ведут они себя по-разному.

  • 🟦 NULL — тип Null. Означает «значения нет вовсе»: поле из несовпавшего соединения, несуществующий реквизит. Появляется в основном в результатах запросов.
  • 🟨 Пустая ссылка — ссылка с нулевым идентификатором, например Справочники.Номенклатура.ПустаяСсылка(). Это заполненный, но «ни на что не указывающий» реквизит типа ссылки.
  • 🟩 Неопределено (Undefined) — значение по умолчанию для необъявленной переменной или несуществующего свойства во встроенном языке.

Проверять каждое по отдельности неудобно, поэтому во встроенном языке есть универсальная функция:

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

ЗначениеЗаполнено() возвращает Ложь для пустой ссылки, пустой даты, нуля, пустой строки, NULL, Неопределено и пустых коллекций. Это самый надёжный способ проверить «есть ли здесь осмысленное значение», не разбираясь, какой именно тип пустоты пришёл.

Для чистой проверки ссылки подойдёт её собственный метод:

Если Выборка.Контрагент.Пустая() Тогда

Пустая() есть у ссылки, но не у NULL. Если поле в выборке может оказаться NULL (пришло из соединения), вызов Поле.Пустая() вызовет ошибку. В таких случаях либо оборачивайте поле в ЕСТЬNULL в запросе, либо проверяйте через ЗначениеЗаполнено().

Если нужно поймать именно NULL, его можно сравнить напрямую — значение этого типа сравнимо:

Если Выборка.Поле = NULL Тогда
    // пришёл NULL, а не пустая ссылка
КонецЕсли;

Точный тип значения определяется функцией ТипЗнч(): например, ТипЗнч(Выборка.Поле) = Тип("Null"). Такая проверка нужна редко — в большинстве случаев достаточно ЗначениеЗаполнено(), который снимает вопрос «ссылка это, NULL или Неопределено» одним вызовом. Разбирать типы по отдельности стоит, только когда поведение должно различаться для настоящего отсутствия данных и для пустой, но заполненной ссылки.

Ошибки при работе с пустым результатом

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

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

Чтобы такие ситуации не всплывали в проде, проверку пустоты ставьте сразу после выполнения запроса, а числовые поля из соединений закрывайте ЕСТЬNULL на этапе написания текста запроса, а не потом в коде.

Отдельный частый случай — обращение к единственной ожидаемой строке. Код вроде Выборка.Следующий(); Значение = Выборка.Поле; сломается, если строки не оказалось: Следующий() вернёт Ложь, а Выборка.Поле обратится к несуществующей позиции. Правильный порядок — сначала условие, потом чтение:

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

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

Производительность на больших выборках

Когда нужен только факт «есть строки или нет», не тяните из базы лишнее.

  1. Для проверки существования добавьте в запрос ВЫБРАТЬ ПЕРВЫЕ 1 — сервер остановится на первой подходящей строке и не будет собирать всю выборку.
ВЫБРАТЬ ПЕРВЫЕ 1
    ИСТИНА КАК Есть
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Контрагент = &Контрагент

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

  1. Выбирайте в такой проверке одно лёгкое поле или константу (ИСТИНА), а не десяток колонок и таблицы значений — они здесь не нужны.
  2. Не используйте Выгрузить().Количество() вместо Пустой(): выгрузка материализует всю выборку, а метод Пустой() до этого не доходит.
  3. Если проверка пустоты идёт в цикле по множеству объектов, подумайте, нельзя ли заменить N отдельных запросов одним пакетным с временной таблицей и соединением — так база отработает один раз вместо сотен обращений.

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

Эти приёмы ощутимы именно на больших регистрах и документах: на справочнике из десятка строк разница незаметна, а на миллионах записей ПЕРВЫЕ 1 и соединение по индексу экономят и время выполнения, и трафик между сервером и клиентом.

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

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