sqlpostgresqlnullthree-valued-logic

NULL e IS DISTINCT FROM: comparacion segura con NULL en SQL

Por que = NULL nunca es verdadero y como IS DISTINCT FROM ofrece igualdad segura con NULL.

2 min de lecturaReferencesql · postgresql · null · three-valued-logic · data-quality

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;      -- NULL (not TRUE)
SELECT NULL <> 1;        -- NULL
SELECT NULL = 1;         -- NULL

Por 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 = 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.

-- 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 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.

-- 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 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;      -- false
SELECT NULL IS DISTINCT FROM 1;         -- true
SELECT 1 IS NOT DISTINCT FROM 1;        -- true
SELECT NULL IS NOT DISTINCT FROM NULL;  -- true

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.

-- 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:

  • 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.

Practica con ejercicios reales

Resuelve ejercicios en el entrenador de SQL con corrección instantánea y pistas.

Abrir el entrenador