Рассмотрим распространенную ситуацию: разработчику необходимо написать запрос для получения максимального номера паллеты из справочника, а в случае отсутствия записей вернуть 0. На первый взгляд кажется логичным обернуть проверяемый реквизит в функцию ЕСТЬNULL непосредственно внутри агрегата МАКСИМУМ:
ВЫБРАТЬ
МАКСИМУМ(ЕСТЬNULL(яШтрихКодыПаллет.НомерПаллеты, 0)) КАК НомерПаллеты
ИЗ
Справочник.яШтрихКодыПаллет КАК яШтрихКодыПаллет
ГДЕ
яШтрихКодыПаллет.Дата = &Дата
И яШтрихКодыПаллет.НомерСмены = &НомерСмены
И яШтрихКодыПаллет.Владелец = &Владелец
Однако при выполнении такого запроса, если под условия в секции ГДЕ не подпадает ни одна запись, результат все равно возвращает значение NULL.
Выясним причину такого поведения СУБД и платформы. Порядок обработки функций в запросе следующий: функция ЕСТЬNULL проверяет значения в каждой конкретной строке таблицы до выполнения агрегации. Если условия отбора не отфильтровали ни одной записи, исходная выборка пуста, и внутренняя функция ЕСТЬNULL не вызывается ни разу.
В свою очередь, агрегатная функция МАКСИМУМ (без секции СГРУППИРОВАТЬ ПО), примененная к пустой выборке, по стандартам SQL и платформы 1С всегда возвращает одну строку со значением NULL. При этом метод РезультатЗапроса.Пустой() вернет значение Ложь, так как итоговая выборка фактически содержит одну строку с результатом NULL.
Чтобы корректно обработать отсутствие записей на уровне базы данных, необходимо изменить порядок вызова функций: обернуть сам результат работы агрегатной функции МАКСИМУМ в функцию ЕСТЬNULL.
Разберем верный вариант написания запроса:
ВЫБРАТЬ
ЕСТЬNULL(МАКСИМУМ(яШтрихКодыПаллет.НомерПаллеты), 0) КАК НомерПаллеты
ИЗ
Справочник.яШтрихКодыПаллет КАК яШтрихКодыПаллет
ГДЕ
яШтрихКодыПаллет.Дата = &Дата
И яШтрихКодыПаллет.НомерСмены = &НомерСмены
И яШтрихКодыПаллет.Владелец = &Владелец
В этом случае при отсутствии записей функция МАКСИМУМ возвращает NULL, после чего внешняя функция ЕСТЬNULL перехватывает его и заменяет на указанное значение — 0. Этот подход является самым оптимальным по производительности и стандартным при разработке конфигураций и сдаче экзаменов «1С:Специалист».
Если требуется применить более развернутое условие или явно описать логику проверки в запросе, можно использовать конструкцию ВЫБОР КОГДА:
ВЫБРАТЬ
ВЫБОР
КОГДА МАКСИМУМ(яШтрихКодыПаллет.НомерПаллеты) ЕСТЬ NULL
ТОГДА 0
ИНАЧЕ МАКСИМУМ(яШтрихКодыПаллет.НомерПаллеты)
КОНЕЦ КАК НомерПаллеты
ИЗ
Справочник.яШтрихКодыПаллет КАК яШтрихКодыПаллет
ГДЕ
яШтрихКодыПаллет.Дата = &Дата
И яШтрихКодыПаллет.НомерСмены = &НомерСмены
И яШтрихКодыПаллет.Владелец = &Владелец
Обратите внимание: проверка на отсутствие значения в языке запросов выполняется только с помощью оператора ЕСТЬ NULL. Прямое сравнение со знаком равенства (= NULL) вернет неопределенный результат, так как любые операции сравнения с NULL дают неизвестное значение.
Альтернативный подход заключается в отказе от использования агрегатных функций. Для получения максимального значения можно отсортировать выборку по убыванию и ограничить количество записей с помощью ключевого слова ПЕРВЫЕ 1:
ВЫБРАТЬ ПЕРВЫЕ 1
яШтрихКодыПаллет.НомерПаллеты КАК НомерПаллеты
ИЗ
Справочник.яШтрихКодыПаллет КАК яШтрихКодыПаллет
ГДЕ
яШтрихКодыПаллет.Дата = &Дата
И яШтрихКодыПаллет.НомерСмены = &НомерСмены
И яШтрихКодыПаллет.Владелец = &Владелец
УПОРЯДОЧИТЬ ПО
яШтрихКодыПаллет.НомерПаллеты УБЫВ
Главные особенности этого подхода:
РезультатЗапроса.Пустой() в этом случае вернет Истина, что удобно проверять во встроенном языке 1С.Важное замечание по производительности: вариант с ПЕРВЫЕ 1 и УПОРЯДОЧИТЬ ПО эффективен только при наличии составного индекса в базе данных по полям фильтрации и полю сортировки. В противном случае СУБД выполнит сканирование всей таблицы с сортировкой в памяти.
Если в самом запросе обработка NULL не выполнялась, подстраховать получение числового значения можно на уровне встроенного языка 1С. Попытка выполнить математические операции с объектом типа Null вызовет ошибку платформы «Преобразование к типу число не может быть выполнено».
Проанализируем пример функции во встроенном языке с использованием системной функции ЗначениеЗаполнено:
Функция ПолучитьМаксимальныйНомерПаллеты(Дата, НомерСмены, Владелец) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(яШтрихКодыПаллет.НомерПаллеты) КАК НомерПаллеты
|ИЗ
| Справочник.яШтрихКодыПаллет КАК яШтрихКодыПаллет
|ГДЕ
| яШтрихКодыПаллет.Дата = &Дата
| И яШтрихКодыПаллет.НомерСмены = &НомерСмены
| И яШтрихКодыПаллет.Владелец = &Владелец";
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("НомерСмены", НомерСмены);
Запрос.УстановитьПараметр("Владелец", Владелец);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат ?(ЗначениеЗаполнено(Выборка.НомерПаллеты), Выборка.НомерПаллеты, 0);
КонецЕсли;
Возврат 0;
КонецФункции
Функция ЗначениеЗаполнено возвращает Ложь для значения типа Null, благодаря чему тернарный оператор ?() вернет безопасное числовое значение 0.
Подведем итоги работы с агрегатными функциями и незаполненными значениями:
ЕСТЬNULL(МАКСИМУМ(Поле), 0) для получения гарантированного значения по умолчанию на уровне базы данных.ЕСТЬNULL внутри агрегата обрабатывает поля отдельных строк, но не заменяет NULL, возникающий от пустой выборки.ЗначениеЗаполнено.