En SQL, NULL no significa "vacio" ni "cero", sino "valor desconocido". Ese matiz hace que las comparaciones habituales se comporten de forma inesperada, y es justo ahi donde IS DISTINCT FROM resulta util. Veamos por que = NULL nunca es verdadero y como escribir comparaciones que resistan al NULL.
Logica trivaluada y por que = NULL falla
La mayoria de los motores SQL usan logica de tres valores: una expresion puede ser TRUE, FALSE o UNKNOWN. Cualquier comparacion que toque NULL se evalua como UNKNOWN, y las filas con UNKNOWN en un WHERE simplemente se descartan.
SELECT NULL = NULL;
SELECT NULL <> 1;
SELECT NULL = 1;
Por eso este filtro siempre devuelve cero filas, aunque parezca que deberia funcionar:
SELECT id, email
FROM users
WHERE country = NULL;
Reglas clave:
NULL = NULL es UNKNOWN, no TRUE.
WHERE solo conserva las filas que evaluan a TRUE.
- La aritmetica con
NULL tambien da NULL: amount + NULL es NULL.
IS NULL e IS NOT NULL
Para comprobar especificamente la "ausencia de valor", usa los predicados dedicados IS NULL e IS NOT NULL. Siempre devuelven TRUE o FALSE, nunca el tercer estado.
SELECT id, email
FROM users
WHERE country IS NULL;
SELECT id, email
FROM users
WHERE country IS NOT NULL;
Cuidado con NOT IN contra una subconsulta: si un solo valor de la lista es NULL, todo el NOT IN colapsa a UNKNOWN y no obtienes ninguna fila.
SELECT id, name
FROM employees
WHERE id NOT IN (SELECT manager_id FROM employees);
Es mas seguro usar NOT EXISTS, o filtrar de forma explicita los NULL dentro de la subconsulta.
IS DISTINCT FROM: igualdad segura con NULL
IS DISTINCT FROM compara dos valores como si NULL fuera un valor normal. NULL IS DISTINCT FROM NULL es FALSE (no "difieren"), mientras que NULL IS DISTINCT FROM 1 es TRUE. El operador espejo, IS NOT DISTINCT FROM, es la igualdad segura con NULL.
SELECT NULL IS DISTINCT FROM NULL;
SELECT NULL IS DISTINCT FROM 1;
SELECT 1 IS NOT DISTINCT FROM 1;
SELECT NULL IS NOT DISTINCT FROM NULL;
Compara el comportamiento de = y de IS NOT DISTINCT FROM lado a lado:
a = b: TRUE con no-NULL iguales, FALSE en otro caso, pero UNKNOWN siempre que aparece NULL.
a IS NOT DISTINCT FROM b: se comporta como =, pero trata NULL como igual a NULL y siempre devuelve TRUE/FALSE.
En la practica: deteccion de cambios y deduplicacion
Una tarea clasica: durante una carga, actualizar una fila solo cuando algo cambio de verdad. Con un <> normal, una fila cuyo name paso de NULL a un valor real (o al reves) no se marca como modificada.
SELECT u.id
FROM users AS u
JOIN staging_users AS s ON s.id = u.id
WHERE u.name IS DISTINCT FROM s.name
OR u.country IS DISTINCT FROM s.country
OR u.email IS DISTINCT FROM s.email;
Lo mismo aplica a la deduplicacion cuando la clave puede contener NULL:
SELECT a.id, b.id
FROM users AS a
JOIN users AS b
ON a.email IS NOT DISTINCT FROM b.email
AND a.country IS NOT DISTINCT FROM b.country
WHERE a.id < b.id;
Diferencias entre motores:
- PostgreSQL admite por completo
IS DISTINCT FROM e IS NOT DISTINCT FROM.
- MySQL/MariaDB usan en su lugar el operador
<=> (igualdad segura con NULL): a <=> b.
- ClickHouse ofrece
isNull y similares; para una comparacion consciente de NULL es comun una expresion basada en coalesce, y el soporte exacto varia segun la version.
En resumen: usa IS NULL/IS NOT NULL para comprobar la presencia, y recurre a IS DISTINCT FROM siempre que compares columnas donde NULL es un estado legitimo.
En SQL,
NULLno significa "vacio" ni "cero", sino "valor desconocido". Ese matiz hace que las comparaciones habituales se comporten de forma inesperada, y es justo ahi dondeIS DISTINCT FROMresulta util. Veamos por que= NULLnunca es verdadero y como escribir comparaciones que resistan alNULL.Logica trivaluada y por que = NULL falla
La mayoria de los motores SQL usan logica de tres valores: una expresion puede ser
TRUE,FALSEoUNKNOWN. Cualquier comparacion que toqueNULLse evalua comoUNKNOWN, y las filas conUNKNOWNen unWHEREsimplemente se descartan.SELECT NULL = NULL; -- NULL (not TRUE) SELECT NULL <> 1; -- NULL SELECT NULL = 1; -- NULLPor eso este filtro siempre devuelve cero filas, aunque parezca que deberia funcionar:
-- Wrong: never returns rows, even if country really is NULL SELECT id, email FROM users WHERE country = NULL;Reglas clave:
NULL = NULLesUNKNOWN, noTRUE.WHEREsolo conserva las filas que evaluan aTRUE.NULLtambien daNULL:amount + NULLesNULL.IS NULL e IS NOT NULL
Para comprobar especificamente la "ausencia de valor", usa los predicados dedicados
IS NULLeIS NOT NULL. Siempre devuelvenTRUEoFALSE, nunca el tercer estado.-- Correct way to find users without a country SELECT id, email FROM users WHERE country IS NULL; -- Users that have a country set SELECT id, email FROM users WHERE country IS NOT NULL;Cuidado con
NOT INcontra una subconsulta: si un solo valor de la lista esNULL, todo elNOT INcolapsa aUNKNOWNy no obtienes ninguna fila.-- Gotcha: if any manager_id is NULL, this returns nothing SELECT id, name FROM employees WHERE id NOT IN (SELECT manager_id FROM employees);Es mas seguro usar
NOT EXISTS, o filtrar de forma explicita losNULLdentro de la subconsulta.IS DISTINCT FROM: igualdad segura con NULL
IS DISTINCT FROMcompara dos valores como siNULLfuera un valor normal.NULL IS DISTINCT FROM NULLesFALSE(no "difieren"), mientras queNULL IS DISTINCT FROM 1esTRUE. El operador espejo,IS NOT DISTINCT FROM, es la igualdad segura con NULL.SELECT NULL IS DISTINCT FROM NULL; -- false SELECT NULL IS DISTINCT FROM 1; -- true SELECT 1 IS NOT DISTINCT FROM 1; -- true SELECT NULL IS NOT DISTINCT FROM NULL; -- trueCompara el comportamiento de
=y deIS NOT DISTINCT FROMlado a lado:a = b:TRUEcon no-NULL iguales,FALSEen otro caso, peroUNKNOWNsiempre que apareceNULL.a IS NOT DISTINCT FROM b: se comporta como=, pero trataNULLcomo igual aNULLy siempre devuelveTRUE/FALSE.En la practica: deteccion de cambios y deduplicacion
Una tarea clasica: durante una carga, actualizar una fila solo cuando algo cambio de verdad. Con un
<>normal, una fila cuyonamepaso deNULLa un valor real (o al reves) no se marca como modificada.-- Detect real changes, NULL-safe SELECT u.id FROM users AS u JOIN staging_users AS s ON s.id = u.id WHERE u.name IS DISTINCT FROM s.name OR u.country IS DISTINCT FROM s.country OR u.email IS DISTINCT FROM s.email;Lo mismo aplica a la deduplicacion cuando la clave puede contener
NULL:-- Two rows are duplicates even when country is NULL in both SELECT a.id, b.id FROM users AS a JOIN users AS b ON a.email IS NOT DISTINCT FROM b.email AND a.country IS NOT DISTINCT FROM b.country WHERE a.id < b.id;Diferencias entre motores:
IS DISTINCT FROMeIS NOT DISTINCT FROM.<=>(igualdad segura con NULL):a <=> b.isNully similares; para una comparacion consciente deNULLes comun una expresion basada encoalesce, y el soporte exacto varia segun la version.En resumen: usa
IS NULL/IS NOT NULLpara comprobar la presencia, y recurre aIS DISTINCT FROMsiempre que compares columnas dondeNULLes un estado legitimo.