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

Сравните обычное соединение по равенству с null-безопасным сравнением: как обрабатывается пара строк, у которых ключ соединения отсутствует?

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

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

При обычном сравнении через оператор равенства две строки с NULL в ключе не считаются совпавшими: результат сравнения — UNKNOWN, а не TRUE. Null-безопасное сравнение трактует пару NULL как совпадение, поэтому такие строки соединяются.

Исторический контекст

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

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

Постановка проблемы

Если условие соединения использует обычное равенство, строки с NULL в ключе не попадут в совпадение. В INNER JOIN они будут исключены, а в LEFT JOIN строка левой таблицы сохранится, но правая сторона будет заполнена NULL.

Неверная замена обычного сравнения на null-безопасное может дать обратную проблему: если у нескольких строк ключ равен NULL, они начнут соединяться друг с другом. Это способно создать неожиданное умножение строк.

Подробное решение

В обычном сравнении NULL = NULL даёт UNKNOWN. Условие соединения пропускает только строки, для которых предикат равен TRUE, поэтому UNKNOWN не считается совпадением.

Null-безопасное сравнение явно задаёт другую семантику: два NULL считаются равными, а NULL и любое известное значение — разными. В стандартизованном SQL для этого используется IS NOT DISTINCT FROM; в конкретных СУБД могут существовать собственные операторы с тем же смыслом.

SELECT a.id, b.id FROM a JOIN b ON a.key IS NOT DISTINCT FROM b.key;

В этом примере строки с одинаковыми известными ключами соединяются, как обычно, и дополнительно соединяются строки, где оба ключа равны NULL. Если null-безопасный оператор недоступен, его обычно выражают через проверку обычного равенства либо одновременного наличия NULL, но точный синтаксис зависит от СУБД.

Выбор зависит от смысла данных. Если NULL означает «значение неизвестно», обычное равенство обычно корректнее; если он означает «ключ отсутствует, и отсутствие должно совпадать», требуется null-безопасное сравнение.

Ситуация из практики

Система объединяет записи клиентов из двух источников по необязательному внешнему идентификатору. У части записей идентификатор отсутствует в обоих источниках.

Первый вариант — соединять таблицы обычным равенством. Он безопасен от массового сопоставления пустых идентификаторов, но не объединяет пары записей, где бизнес-правило действительно считает отсутствие идентификатора совпадением.

Второй вариант — использовать null-безопасное сравнение. Он соответствует правилу сопоставления, но при наличии множества записей с NULL создаёт много пар между ними и может резко увеличить результат.

Выбранное решение — не считать один NULL достаточным ключом для сопоставления. Сначала записи сопоставляют по надёжному идентификатору, а для строк без него используют отдельное правило, например комбинацию подтверждённых атрибутов. Это предотвращает ложные совпадения и неконтролируемое умножение строк.

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

  1. Почему UNKNOWN не превращается в FALSE автоматически?

NULL означает неизвестное значение, поэтому утверждение NULL = 5 нельзя обоснованно считать ни истинным, ни ложным. В условиях JOIN, WHERE и HAVING проходят только предикаты с результатом TRUE; UNKNOWN отбрасывается.

  1. Что произойдёт, если null-безопасное сравнение используется при нескольких NULL-ключах?

Каждая строка с NULL слева может соединиться с каждой строкой с NULL справа. Например, две строки с NULL слева и три строки с NULL справа дадут шесть пар. Null-безопасность меняет правило сопоставления, но не отменяет обычную кардинальность соединения.

  1. Одинаково ли ведут себя INNER JOIN и LEFT JOIN при null-безопасном сравнении?

Нет. INNER JOIN возвращает только совпавшие пары, включая пары NULL с NULL, если используется null-безопасное сравнение. LEFT JOIN дополнительно сохраняет каждую строку левой таблицы, для которой совпадения не нашлось, заполняя столбцы правой таблицы NULL.