Какой стандартный SQL-предикат выбрать для сравнения двух nullable-значений, чтобы результат никогда не был UNKNOWN?
Используйте предикат IS DISTINCT FROM. Он возвращает только TRUE или FALSE: два NULL считаются одинаковыми, NULL и обычное значение — различными, а равные ненулевые значения — одинаковыми.
Обратный предикат IS NOT DISTINCT FROM проверяет эквивалентность с таким же правилом обработки NULL. В отличие от обычного сравнения через = эти предикаты не дают результата UNKNOWN.
В реляционной модели SQL значение NULL обозначает отсутствие известного значения, а не обычное значение конкретного типа. Поэтому SQL использует трёхзначную логику: результат сравнения может быть TRUE, FALSE или UNKNOWN.
Обычный оператор равенства не определяет NULL как равный самому себе. Специальные предикаты IS DISTINCT FROM и IS NOT DISTINCT FROM нужны для случаев, где приложению требуется детерминированное сравнение, включая сравнение двух отсутствующих значений.
Проверка через = неудобна при сравнении старого и нового состояния записи. Если оба значения равны NULL, выражение old_value = new_value даёт UNKNOWN, хотя с точки зрения контроля изменений значения можно считать одинаковыми.
Если UNKNOWN используется в условии WHERE, строка не проходит фильтр. Это может привести к пропущенным изменениям, неверному аудиту или различию результатов в зависимости от того, являются ли значения NULL.
IS DISTINCT FROM работает по четырём основным случаям:
Минимальный пример:
Для четырёх строк значение changed будет соответственно FALSE, TRUE, FALSE и TRUE. Таким образом, предикат напрямую выражает смысл «значения различаются», не требуя ручного разбора NULL.
IS NOT DISTINCT FROM является логическим отрицанием этого отношения: он возвращает TRUE для равных ненулевых значений и для пары NULL. Название подчёркивает, что NULL рассматривается как сопоставимое состояние именно в рамках этого предиката, а не как обычное значение во всей SQL-логике.
Подмена такого сравнения конструкцией с COALESCE опасна: выбранный заменитель может реально встречаться в данных и тогда разные значения ошибочно станут одинаковыми. Явное сравнение через IS DISTINCT FROM также лучше передаёт намерение; при этом особенности поддержки и оптимизации нужно проверять для конкретной СУБД.
Сервис синхронизации сравнивает значения профиля в основной и резервной таблицах. Поле phone допускает NULL: отсутствие телефона не должно считаться изменением, если в обеих версиях поле осталось NULL.
Вариант с обычным равенством прост, но неверно обрабатывает пары NULL и требует дополнительной логики. Вариант с COALESCE короче, однако зависит от искусственного значения-заменителя и может столкнуться с конфликтом типов или реальными данными.
Выбран IS DISTINCT FROM: он точно описывает условие «поле изменилось», корректно обрабатывает все комбинации NULL и не вводит фиктивных значений. В результате аудит фиксирует только реальные изменения, включая переход от NULL к значению и обратно.
Чем IS DISTINCT FROM отличается от обычного <>?
<> при наличии NULL может вернуть UNKNOWN. Например, сравнение NULL с числом не даёт TRUE, даже если интуитивно значения различаются. IS DISTINCT FROM в той же ситуации возвращает TRUE, поэтому его результат пригоден для надёжной фильтрации.
Можно ли считать два NULL равными во всей SQL-логике после использования этого предиката?
Нет. Предикат меняет результат только конкретного сравнения. В других операциях сохраняются обычные правила SQL: NULL = NULL остаётся UNKNOWN, арифметика с NULL обычно даёт NULL, а условия WHERE пропускают только TRUE.
Почему нельзя бездумно заменить сравнение на COALESCE?
COALESCE подставляет выбранное значение вместо NULL. Если это значение допустимо в данных, например пустая строка или специальное число, то пара «NULL и реальное значение-заменитель» будет ошибочно признана одинаковой. Кроме того, преобразования типов и функции над столбцами могут ухудшить использование индексов; явный null-safe-предикат обычно точнее выражает требуемую семантику.