Какой стандартный SQL предикат выбрать для сравнения двух nullable значений, чтобы результат никогда не был...

Какой стандартный SQL-предикат выбрать для сравнения двух nullable-значений, чтобы результат никогда не был UNKNOWN?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Используйте предикат 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 работает по четырём основным случаям:

  • два ненулевых равных значения — FALSE;
  • два ненулевых разных значения — TRUE;
  • один NULL и одно ненулевое значение — TRUE;
  • два NULL — FALSE.

Минимальный пример:

SELECT old_value, new_value, old_value IS DISTINCT FROM new_value AS changed FROM (VALUES (NULL, NULL), (NULL, 10), (10, 10), (10, 20) ) AS v(old_value, new_value);

Для четырёх строк значение 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 к значению и обратно.

Что кандидаты часто упускают

  1. Чем IS DISTINCT FROM отличается от обычного <>?

    <> при наличии NULL может вернуть UNKNOWN. Например, сравнение NULL с числом не даёт TRUE, даже если интуитивно значения различаются. IS DISTINCT FROM в той же ситуации возвращает TRUE, поэтому его результат пригоден для надёжной фильтрации.

  2. Можно ли считать два NULL равными во всей SQL-логике после использования этого предиката?

    Нет. Предикат меняет результат только конкретного сравнения. В других операциях сохраняются обычные правила SQL: NULL = NULL остаётся UNKNOWN, арифметика с NULL обычно даёт NULL, а условия WHERE пропускают только TRUE.

  3. Почему нельзя бездумно заменить сравнение на COALESCE?

    COALESCE подставляет выбранное значение вместо NULL. Если это значение допустимо в данных, например пустая строка или специальное число, то пара «NULL и реальное значение-заменитель» будет ошибочно признана одинаковой. Кроме того, преобразования типов и функции над столбцами могут ухудшить использование индексов; явный null-safe-предикат обычно точнее выражает требуемую семантику.