При написании запросов в платформе 1С:Предприятие разработчики регулярно сталкиваются с необходимостью обработки значений NULL при расчете агрегатных функций. Классический пример — получение максимального значения (например, даты, числа или номера документа), когда в базе данных могут отсутствовать записи или само поле может быть не заполнено.
В этой статье мы подробно проанализируем две популярные конструкции:
МАКСИМУМ(ЕСТЬNULL(Поле, 0)) (функция замены значения находится внутри агрегатора)ЕСТЬNULL(МАКСИМУМ(Поле), 0) (функция замены значения находится снаружи)Мы разберем их поведение с трех важнейших точек зрения: оптимизации СУБД (производительности), риска падения кода 1С (безопасности) и математического смысла (бизнес-логики). Давайте вместе разберемся, какой вариант и почему следует использовать в вашей практике.
---
Начнем с самого важного аспекта для высоконагруженных систем — скорости выполнения запроса СУБД (например, MS SQL Server или PostgreSQL). Между нашими вариантами существует колоссальная разница в производительности, которая напрямую связана со способностью СУБД эффективно использовать индексы таблицы (понятие SARGability).
Рассмотрим подробнее первый вариант: МАКСИМУМ(ЕСТЬNULL(Поле, 0)).
Когда функция ЕСТЬNULL (в терминах SQL — ISNULL или COALESCE) помещается внутрь агрегатной функции МАКСИМУМ, СУБД лишается возможности применить индекс по этому полю для быстрого поиска. СУБД не может "знать" заранее, какими станут значения после вычисления функции для каждой строки. В результате СУБД вынуждена выполнить полный обход таблицы или индекса (Table Scan или Index Scan), рассчитать функцию ЕСТЬNULL для каждой записи в базе данных и только после этого вычислить максимальное значение. На таблицах с миллионами строк такой запрос приведет к катастрофическому падению производительности и избыточной нагрузке на дисковую подсистему и процессор.
Теперь проанализируем второй вариант: ЕСТЬNULL(МАКСИМУМ(Поле), 0).
В этом сценарии агрегатная функция МАКСИМУМ(Поле) вычисляется "в чистом виде". Если по данному полю в таблице построен индекс, СУБД мгновенно получит максимальное значение за минимальное время (сложность операции составит $O(1)$ или $O(\log N)$), просто обратившись к крайней правой ветке индексного дерева. И только к одному единственному полученному результату СУБД применит внешнюю функцию ЕСТЬNULL. На больших объемах данных разница в скорости выполнения между первым и вторым вариантом может достигать сотен и тысяч раз.
---
В практике разработки на платформе 1С существует специфическое поведение запросов при работе с пустыми выборками данных. Это происходит, например, когда мы выбираем данные из временной таблицы, в которой нет строк из-за жесткого отбора, или при использовании ЛЕВОЕ СОЕДИНЕНИЕ, когда для левой таблицы не нашлось соответствий в правой.
Выясним причину возможного падения программы. Если в запросе отсутствует группировка (нет предложения СГРУППИРОВАТЬ ПО), но при этом используются агрегатные функции, то СУБД при любом состоянии источника данных вернет ровно одну строку результатов. Но содержимое этой строки будет отличаться в зависимости от выбранного синтаксиса.
Разберем по шагам поведение первого варианта МАКСИМУМ(ЕСТЬNULL(Поле, ДАТАВРЕМЯ(1, 1, 1))) на пустой таблице:
ЕСТЬNULL даже не вызывается (его не к чему применить).МАКСИМУМ на пустом наборе данных по правилам SQL возвращает значение NULL.NULL.Если разработчик попытается обработать результат такого запроса во встроенном языке 1С, программа завершится аварийно. Посмотрим на пример кода:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
// Если в Выборка.МаксДата пришел NULL, следующая строка вызовет ошибку:
// "Несоответствие типов (параметр номер '1')"
НачалоДня = НачалоДня(Выборка.МаксДата);
КонецЕсли;
Теперь разберем поведение второго варианта ЕСТЬNULL(МАКСИМУМ(Поле), ДАТАВРЕМЯ(1, 1, 1)) на той же пустой таблице:
МАКСИМУМ(Поле) на пустом наборе данных и предсказуемо возвращает NULL.NULL перехватывается внешней функцией ЕСТЬNULL.ЕСТЬNULL успешно заменяет его на пустую дату 01.01.0001.При таком подходе последующий код на встроенном языке 1С отработает абсолютно безопасно и стабильно.
---
Помимо вопросов производительности и надежности, эти две конструкции имеют принципиально разную математическую логику. В зависимости от решаемой задачи они могут возвращать совершенно разные результаты. Это связано с тем, что функция МАКСИМУМ по умолчанию полностью игнорирует значения NULL при расчете агрегатов.
Давайте посмотрим на наглядный пример. Предположим, у нас есть временная таблица ВложенныйЗапрос со следующим набором чисел в поле Поле:
-2-1NULLВычислим результат для первого варианта:
ВЫБРАТЬ
МАКСИМУМ(ЕСТЬNULL(ВложенныйЗапрос.Поле, 0)) КАК Результат
ИЗ
ВложенныйЗапрос КАК ВложенныйЗапрос
В данном случае сначала для каждой строки выполнится преобразование ЕСТЬNULL. Наш набор данных превратится в: -2, -1, 0. Максимумом среди этих трех чисел является 0. Запрос вернет 0.
Теперь вычислим результат для второго варианта:
ВЫБРАТЬ
ЕСТЬNULL(МАКСИМУМ(ВложенныйЗапрос.Поле), 0) КАК Результат
ИЗ
ВложенныйЗапрос КАК ВложенныйЗапрос
Здесь сначала вычисляется максимум среди существующих значений, при этом NULL игнорируется агрегатором. Максимальным значением среди -2 и -1 является число -1. После этого внешняя функция ЕСТЬNULL проверяет результат (число -1) и, так как оно не является NULL, оставляет его без изменений. Запрос вернет -1.
Резюме по логике работы:
МАКСИМУМ(ЕСТЬNULL(Поле, 0)) приравнивает отсутствие данных к нулю ДО вычисления максимума. Это может исказить логику, если в базе хранятся отрицательные показатели.ЕСТЬNULL(МАКСИМУМ(Поле), 0) ищет максимум только среди реально существующих в базе данных значений. И только если записей вообще не нашлось (или во всех строках был строго NULL), она подставит значение по умолчанию (0).---
Для решения 99% стандартных задач в 1С (таких как получение максимальной даты документа, последнего номера, максимальной суммы или остатка товара) единственно правильным, безопасным и быстрым выбором является второй вариант:
ЕСТЬNULL(МАКСИМУМ(Поле), ЗначениеПоУмолчанию)
Используйте этот вариант по умолчанию при проектировании любых запросов. Первый вариант МАКСИМУМ(ЕСТЬNULL(Поле, 0)) применим исключительно в редких специфических задачах бизнес-логики, когда вам осознанно необходимо приравнять неопределенные значения к нулю еще до момента сравнения чисел, и вы сознательно готовы пойти на снижение производительности ради этой логики.