Error SQL · filtros y lógica de tres valores

WHERE con <> no incluye filas con NULL: decide si deben entrar

Si filtras una columna nullable con <> o !=, las filas con NULL tampoco pasan el WHERE. No es que el motor las considere iguales al valor excluido: la comparación queda desconocida. La corrección depende de si tu regla quiere conservar esos valores desconocidos o descartarlos.

Lectura: 14–18 minEjemplo reproduciblePráctica con pistas

El síntoma: «distinto de Madrid» devuelve menos filas de las esperadas

Imagina que ciudad puede contener un nombre o NULL. Quieres excluir Madrid y escribes WHERE ciudad <> 'Madrid'. Las ciudades conocidas y diferentes sí pasan el filtro, pero las filas con ciudad desconocida no aparecen.

La causa es la lógica de tres valores de SQL. Una comparación como NULL <> 'Madrid' no es verdadera ni falsa: produce un resultado desconocido. WHERE conserva únicamente las filas cuya condición es verdadera, así que también descarta ese caso.

Ejemplo reproducible: <> excluye Madrid y también los NULL

Consulta problemática
WITH clientes(id, nombre, ciudad) AS (
  VALUES
    (1, 'Ana',   'Madrid'),
    (2, 'Luis',  'Valencia'),
    (3, 'Marta', NULL),
    (4, 'Pablo', 'Sevilla'),
    (5, 'Sara',  NULL)
)
SELECT id, nombre, ciudad
FROM clientes
WHERE ciudad <> 'Madrid'
ORDER BY id;
Resultado comprobado localmente con SQLite
idnombreciudad
2LuisValencia
4PabloSevilla

Marta y Sara no aparecen porque su ciudad es desconocida. SQL no puede afirmar que una ciudad desconocida sea distinta de Madrid; por eso la condición no llega a TRUE.

La pregunta importante no es «¿cómo fuerzo NULL?», sino «¿qué significa mi regla?»

Antes de corregir el SQL, define la intención. Hay al menos dos reglas válidas y producen resultados distintos:

IntenciónTratamiento de NULLPredicado portable
Quiero solo ciudades conocidas que no sean MadridNULL queda fueraciudad IS NOT NULL AND ciudad <> 'Madrid'
Quiero todas las filas salvo las confirmadas como MadridNULL entraciudad <> 'Madrid' OR ciudad IS NULL

La primera regla hace explícito que una ciudad desconocida no es suficiente para clasificar al cliente como «fuera de Madrid». La segunda expresa otra política: solo excluye las filas que sabemos que son Madrid y conserva las que todavía no tienen ciudad.

Corrección portable cuando quieres conservar los valores desconocidos

Excluir solo Madrid confirmado
WITH clientes(id, nombre, ciudad) AS (
  VALUES
    (1, 'Ana',   'Madrid'),
    (2, 'Luis',  'Valencia'),
    (3, 'Marta', NULL),
    (4, 'Pablo', 'Sevilla'),
    (5, 'Sara',  NULL)
)
SELECT id, nombre, ciudad
FROM clientes
WHERE ciudad <> 'Madrid'
   OR ciudad IS NULL
ORDER BY id;
Resultado comprobado
idnombreciudad
2LuisValencia
3MartaNULL
4PabloSevilla
5SaraNULL

Esta forma es portable y deja la política visible: «ciudad distinta de Madrid o ciudad desconocida». No uses COALESCE(ciudad, '...') con un valor inventado salvo que ese valor tenga una semántica real y no pueda colisionar con datos válidos.

Predicados null-safe: útiles, pero no tienen la misma sintaxis en todos los motores

Algunos motores ofrecen comparaciones que siempre devuelven verdadero o falso aunque intervenga NULL. PostgreSQL y SQLite admiten IS DISTINCT FROM; SQL Server lo admite en versiones modernas, y MySQL utiliza el operador null-safe <=> para igualdad. Si tu objetivo es portabilidad, <> ... OR ... IS NULL suele ser más explícito entre motores.

MotorRegla relevanteAlternativa null-safe
PostgreSQLLas comparaciones ordinarias con NULL producen resultado desconocido.ciudad IS DISTINCT FROM 'Madrid'
MySQL 8.4=, <> y otras comparaciones ordinarias con NULL producen NULL.NOT (ciudad <=> 'Madrid')
SQL ServerCon el comportamiento ANSI vigente, una comparación con NULL produce UNKNOWN.IS DISTINCT FROM en versiones que lo soportan; si buscas portabilidad, usa la condición explícita.
SQLiteLos operadores ordinarios suelen devolver NULL cuando un operando es NULL.IS DISTINCT FROM; también tiene semántica propia para IS/IS NOT.

Método de diagnóstico

  1. Comprueba si la columna admite NULL. Si nunca puede ser nula, este problema no explica las filas ausentes.
  2. Ejecuta el filtro positivo. Prueba ciudad = 'Madrid' para separar coincidencias conocidas de valores desconocidos.
  3. Cuenta o localiza los NULL. Usa WHERE ciudad IS NULL y verifica cuántas filas pertenecen a ese estado.
  4. Define la regla del negocio. Decide si «no es Madrid» significa «sé que es otra ciudad» o «no está confirmado como Madrid».
  5. Escribe esa regla de forma explícita. No añadas OR ... IS NULL por costumbre: añádelo solo cuando los desconocidos deban entrar.

Error frecuente: asumir que NOT convierte UNKNOWN en TRUE

Negar una comparación desconocida sigue dejando un resultado desconocido

NOT (ciudad = 'Madrid') no recupera automáticamente los NULL. Cuando ciudad es NULL, ciudad = 'Madrid' es desconocido y su negación también lo es. Si quieres incluir esos registros, expresa el caso con OR ciudad IS NULL o usa un predicado null-safe apropiado para tu motor.

Práctica correctiva

Básica–intermedia · filtros y NULL

Excluye solo los pedidos confirmados como cancelados

La columna estado puede ser 'enviado', 'cancelado', 'pendiente' o NULL. Devuelve todos los pedidos salvo los que estén confirmados como cancelados. Los estados desconocidos deben conservarse.

Qué debes recordar

  • columna <> valor no incluye automáticamente las filas donde columna es NULL.
  • Una comparación ordinaria con NULL suele producir UNKNOWN, y WHERE solo conserva TRUE.
  • Decide primero si un valor desconocido debe entrar o salir según la regla del dato.
  • Para conservarlo de forma portable, expresa la condición: columna <> valor OR columna IS NULL.
  • Los predicados null-safe cambian entre motores; no los presentes como sintaxis universal.

Conceptos relacionados

Fuentes técnicas

El comportamiento de comparaciones, lógica de tres valores y alternativas null-safe se contrastó con documentación primaria: