El síntoma: NOT IN excluye mucho más de lo esperado
La consulta parece decir «devuelve clientes cuyo id no esté en la lista». Sin embargo, una sola fila NULL dentro del conjunto puede impedir que la condición llegue a ser verdadera para los ids que no coinciden.
WITH lista_exclusiones(cliente_id) AS (
VALUES (1), (3), (NULL)
)
SELECT id, nombre
FROM clientes
WHERE id NOT IN (
SELECT cliente_id
FROM lista_exclusiones
)
ORDER BY id;| filas devueltas |
|---|
| 0 |
Los ids 2, 4, 5… no están en la lista, pero la comparación contra el elemento desconocido impide concluir que sean «distintos de todos».
La causa: SQL usa lógica de tres valores
En SQL una comparación con NULL normalmente no produce verdadero ni falso, sino desconocido. Una forma útil de razonar sobre x NOT IN (1, 3, NULL) es imaginar una cadena de desigualdades conectadas con AND. Para un valor como 2, las comparaciones con 1 y 3 son verdaderas, pero 2 <> NULL es desconocida; el conjunto no puede demostrar que 2 sea distinto de todos los valores.
WHERE conserva únicamente condiciones verdaderas. Tanto falso como desconocido quedan fuera del resultado. Por eso el problema puede parecer «NOT IN devuelve cero filas» aunque la consulta sea sintácticamente válida.
Corrección 1: elimina NULL del conjunto si no representa una exclusión válida
WITH lista_exclusiones(cliente_id) AS (
VALUES (1), (3), (NULL)
)
SELECT id, nombre
FROM clientes
WHERE id NOT IN (
SELECT cliente_id
FROM lista_exclusiones
WHERE cliente_id IS NOT NULL
)
ORDER BY id;| id | nombre |
|---|---|
| 2 | Luis Martín |
| 4 | Diego López |
| 5 | Sara Gómez |
| 6 | Pablo Sanz |
| 7 | Lucía Vega |
| 8 | Hugo Castro |
| 9 | Noa Vidal |
| 10 | Iván Mora |
| 11 | Elena Rey |
| 12 | Mario Gil |
Esta solución es correcta cuando NULL en la fuente significa «no hay id de exclusión» y, por tanto, debe ignorarse.
Corrección 2: expresa la ausencia con NOT EXISTS
Si la intención real es «no existe una fila de exclusión que coincida con este cliente», NOT EXISTS expresa directamente esa pregunta. El NULL de otra fila no crea una comparación global contra todos los valores.
WITH lista_exclusiones(cliente_id) AS (
VALUES (1), (3), (NULL)
)
SELECT c.id, c.nombre
FROM clientes AS c
WHERE NOT EXISTS (
SELECT 1
FROM lista_exclusiones AS e
WHERE e.cliente_id = c.id
)
ORDER BY c.id;Con estos datos devuelve las mismas diez filas que la versión de NOT IN con IS NOT NULL. No elijas una forma por costumbre: elige la que represente con mayor claridad la regla del dato.
Método de diagnóstico
- Ejecuta la subconsulta sola. Comprueba si devuelve
NULL. - Cuenta nulos. Usa una comprobación como
WHERE clave IS NULLpara entender su origen. - Define la semántica. Decide si esos nulos deben ignorarse o si revelan un problema de modelado/datos.
- Prueba la exclusión con pocos valores conocidos. Verifica una fila que debe entrar y otra que debe quedar fuera.
- Solo entonces optimiza. Primero corrige la lógica; después estudia índices o planes si hace falta.
Cómo prevenir el problema
- Si una clave de exclusión no debería ser nula, protégela con una restricción adecuada en el modelo.
- Si
NULLes válido, trata esa posibilidad de forma explícita en consultas de pertenencia. - Cuando la pregunta sea de existencia por relación, considera
EXISTS/NOT EXISTS. - No sustituyas el diagnóstico por
COALESCEa un valor inventado salvo que ese valor tenga una semántica real y no pueda colisionar con datos válidos.
Compatibilidad y matiz entre motores
PostgreSQL documenta que NOT IN devuelve NULL, no verdadero, cuando no hay coincidencia pero el conjunto derecho contiene al menos un NULL. SQLite publica la misma matriz de resultados para IN/NOT IN. La solución conceptual —comprender la lógica de NULL y expresar correctamente la ausencia— no depende de una extensión concreta.
Práctica correctiva
Excluye dos clientes sin dejar que NULL vacíe el resultado
La lista temporal contiene los ids 2, 6 y un NULL. Devuelve todos los clientes que no tengan una fila de exclusión coincidente usando NOT EXISTS.
cliente_id sea igual al id exterior.NULL no coincide mediante = con ningún id concreto.NOT EXISTS alrededor de la subconsulta correlacionada.Solución razonada
WITH lista_exclusiones(cliente_id) AS (
VALUES (2), (6), (NULL)
)
SELECT c.id, c.nombre
FROM clientes AS c
WHERE NOT EXISTS (
SELECT 1
FROM lista_exclusiones AS e
WHERE e.cliente_id = c.id
)
ORDER BY c.id;La consulta devuelve 10 clientes: todos excepto Luis Martín (2) y Pablo Sanz (6). La fila NULL no excluye a los demás porque la pregunta es si existe una coincidencia concreta para cada cliente.
Qué debes recordar
NOT INpuede producir desconocido si el conjunto contieneNULL.WHEREno conserva condiciones desconocidas.- Filtra
NULLsi no representa una exclusión válida. - Usa
NOT EXISTScuando la intención sea «no existe una fila relacionada».
Conceptos relacionados
Fuentes técnicas consultadas
Las fuentes oficiales se usaron para verificar la semántica de NOT IN cuando interviene NULL.