NOT IN

El operador NOT IN es la negación de IN. Devuelve las filas cuyo valor no coincide con ninguno de los valores de una lista. Es útil cuando es más natural definir lo que quieres excluir que lo que quieres incluir.

Sintaxis

SELECT columnas
FROM tabla
WHERE columna NOT IN (valor1, valor2, valor3, ...);

Si el valor de la columna es diferente de todos los valores de la lista, la condición es verdadera y la fila aparece en el resultado.

Excluir valores específicos

Para obtener todos los pedidos excepto los cancelados y los entregados:

SELECT id, fecha_pedido, estado, total
FROM pedidos
WHERE estado NOT IN ('cancelado', 'entregado');
idfecha_pedidoestadototal
72025-11-10 09:00:00enviado259.98
82025-11-15 17:20:00enviado1399.00
92025-11-18 12:00:00enviado129.99
102025-11-22 08:45:00procesando599.99
112025-11-25 15:10:00procesando84.97
122025-12-01 10:30:00procesando199.99
132025-12-03 14:00:00pendiente39.99
142025-12-05 11:15:00pendiente79.98
152025-12-08 09:30:00pendiente1549.00
182025-12-15 10:00:00pendiente329.98
192025-12-18 14:30:00pendiente59.99
212025-12-22 09:15:00enviado249.99
222025-12-28 15:30:00procesando399.00
232026-01-03 10:45:00pendiente64.98
242026-01-05 12:00:00pendiente29.99

Solo aparecen pedidos en estado "enviado", "procesando" o "pendiente". Los 7 entregados y los 3 cancelados quedan excluidos.

NOT IN con valores numéricos

Productos que no son de electrónica (categorías 6, 7 y 8):

SELECT nombre, precio, categoria_id
FROM productos
WHERE categoria_id NOT IN (6, 7, 8)
ORDER BY categoria_id;
nombrepreciocategoria_id
Camiseta algodón básica24.999
Camiseta técnica running34.999
Vaqueros slim fit59.9910
Pantalón chino49.9910
Sofá 3 plazas599.0011
Estantería modular149.9911
Escritorio ajustable399.0011
.........

De los 30 productos, 10 son de electrónica y los 20 restantes aparecen en el resultado.

La trampa de NULL con NOT IN

Aquí está el aspecto más peligroso de NOT IN: si la lista contiene un valor NULL, el resultado siempre está vacío. Esto sucede porque NOT IN internamente se traduce en una serie de !=, y cualquier comparación con NULL devuelve NULL:

SELECT nombre
FROM clientes
WHERE ciudad NOT IN ('Madrid', 'Barcelona', NULL);
Empty set (0.00 sec)

El resultado está vacío aunque hay clientes en Sevilla, Valencia y muchas otras ciudades. La razón es que MySQL evalúa NOT IN ('Madrid', 'Barcelona', NULL) como:

ciudad != 'Madrid' AND ciudad != 'Barcelona' AND ciudad != NULL

La última comparación ciudad != NULL devuelve NULL para cualquier valor, y NULL AND cualquier_cosa es NULL o FALSE. Por lo tanto, ninguna fila pasa el filtro.

Este problema aparece con frecuencia cuando usas NOT IN con subconsultas que pueden devolver valores nulos:

-- PELIGROSO: si categoria_padre_id tiene NULL, no devuelve nada
SELECT nombre
FROM categorias
WHERE id NOT IN (
    SELECT categoria_padre_id
    FROM categorias
);

Como hay categorías cuyo categoria_padre_id es NULL, la subconsulta incluye NULL en la lista y el resultado queda vacío. La solución es filtrar los nulos en la subconsulta:

SELECT nombre
FROM categorias
WHERE id NOT IN (
    SELECT categoria_padre_id
    FROM categorias
    WHERE categoria_padre_id IS NOT NULL
);
nombre
Ropa
Hogar
Deportes
Libros
Smartphones
Portátiles
Accesorios electrónicos
Camisetas
Pantalones
Cocina
Running
Fitness
Programación
Novelas

Ahora obtenemos las categorías que nadie usa como categoría padre (las categorías hoja del árbol). Electrónica y Muebles no aparecen porque sí son categoría padre de otras.

NOT IN vs NOT EXISTS

Debido al problema de NULL, muchos desarrolladores prefieren usar NOT EXISTS en lugar de NOT IN con subconsultas. NOT EXISTS no tiene el problema de los nulos porque no devuelve una lista de valores, sino que evalúa una condición de existencia:

-- Con NOT IN (requiere filtrar NULL)
SELECT nombre
FROM categorias c
WHERE c.id NOT IN (
    SELECT categoria_padre_id
    FROM categorias
    WHERE categoria_padre_id IS NOT NULL
);
 
-- Con NOT EXISTS (no tiene el problema de NULL)
SELECT nombre
FROM categorias c
WHERE NOT EXISTS (
    SELECT 1
    FROM categorias sub
    WHERE sub.categoria_padre_id = c.id
);

Ambas devuelven el mismo resultado, pero NOT EXISTS es más segura porque no se ve afectada por valores nulos en la subconsulta.

NOT IN con WHERE adicional

Puedes combinar NOT IN con otras condiciones:

SELECT nombre, precio, stock
FROM productos
WHERE categoria_id NOT IN (6, 7)
  AND precio > 100
  AND stock > 20
ORDER BY precio DESC;
nombrepreciostock
Sofá 3 plazas599.008
Escritorio ajustable399.0015
Robot de cocina249.9930
Mancuernas ajustables199.9920
Estantería modular149.9922
Zapatillas trail149.9940
Zapatillas running pro129.9965

Productos que no son smartphones ni portátiles, cuestan más de 100 euros y tienen stock disponible.

Practica con NOT IN

Usa el editor para excluir valores con NOT IN:

Simulador SQL
Ctrl+Enter para ejecutar

En el siguiente artículo veremos BETWEEN, el operador que simplifica las consultas con rangos.

Escrito por Eduardo Lázaro