Сравните обычное соединение по равенству с null-безопасным сравнением: как обрабатывается пара строк, у которых ключ соединения отсутствует?
При обычном сравнении через оператор равенства две строки с 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; в конкретных СУБД могут существовать собственные операторы с тем же смыслом.
В этом примере строки с одинаковыми известными ключами соединяются, как обычно, и дополнительно соединяются строки, где оба ключа равны NULL. Если null-безопасный оператор недоступен, его обычно выражают через проверку обычного равенства либо одновременного наличия NULL, но точный синтаксис зависит от СУБД.
Выбор зависит от смысла данных. Если NULL означает «значение неизвестно», обычное равенство обычно корректнее; если он означает «ключ отсутствует, и отсутствие должно совпадать», требуется null-безопасное сравнение.
Система объединяет записи клиентов из двух источников по необязательному внешнему идентификатору. У части записей идентификатор отсутствует в обоих источниках.
Первый вариант — соединять таблицы обычным равенством. Он безопасен от массового сопоставления пустых идентификаторов, но не объединяет пары записей, где бизнес-правило действительно считает отсутствие идентификатора совпадением.
Второй вариант — использовать null-безопасное сравнение. Он соответствует правилу сопоставления, но при наличии множества записей с NULL создаёт много пар между ними и может резко увеличить результат.
Выбранное решение — не считать один NULL достаточным ключом для сопоставления. Сначала записи сопоставляют по надёжному идентификатору, а для строк без него используют отдельное правило, например комбинацию подтверждённых атрибутов. Это предотвращает ложные совпадения и неконтролируемое умножение строк.
NULL означает неизвестное значение, поэтому утверждение NULL = 5 нельзя обоснованно считать ни истинным, ни ложным. В условиях JOIN, WHERE и HAVING проходят только предикаты с результатом TRUE; UNKNOWN отбрасывается.
Каждая строка с NULL слева может соединиться с каждой строкой с NULL справа. Например, две строки с NULL слева и три строки с NULL справа дадут шесть пар. Null-безопасность меняет правило сопоставления, но не отменяет обычную кардинальность соединения.
Нет. INNER JOIN возвращает только совпавшие пары, включая пары NULL с NULL, если используется null-безопасное сравнение. LEFT JOIN дополнительно сохраняет каждую строку левой таблицы, для которой совпадения не нашлось, заполняя столбцы правой таблицы NULL.