Contenido de esta guía
Respuesta rápida
Un anti-join conserva las filas de una tabla para las que no existe una coincidencia en otra. SQL no necesita una palabra clave ANTI JOIN: normalmente se escribe con una subconsulta correlacionada NOT EXISTS o con un LEFT JOIN seguido de una comprobación IS NULL sobre una columna no nula de la tabla derecha.
Opción recomendada: NOT EXISTS
Si la pregunta de negocio es «no existe un pedido de este cliente», NOT EXISTS expresa exactamente esa condición. La subconsulta se correlaciona con la fila exterior mediante la clave relacionada.
WITH clientes(id, nombre) AS (
VALUES (1, 'Ana'), (2, 'Bruno'), (3, 'Celia'), (4, 'Diego')
),
pedidos(id, cliente_id) AS (
VALUES (101, 1), (102, 1), (103, 3)
)
SELECT c.id, c.nombre
FROM clientes AS c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos AS p
WHERE p.cliente_id = c.id
)
ORDER BY c.id;| id | nombre |
|---|---|
| 2 | Bruno |
| 4 | Diego |
La lista del SELECT interno no es lo importante: EXISTS y NOT EXISTS se fijan en si la subconsulta devuelve alguna fila. Por convención suele verse SELECT 1.
Alternativa: LEFT JOIN + IS NULL
También puedes conservar todos los clientes, unir sus pedidos y quedarte con las filas donde no apareció una coincidencia derecha.
WITH clientes(id, nombre) AS (
VALUES (1, 'Ana'), (2, 'Bruno'), (3, 'Celia'), (4, 'Diego')
),
pedidos(id, cliente_id) AS (
VALUES (101, 1), (102, 1), (103, 3)
)
SELECT c.id, c.nombre
FROM clientes AS c
LEFT JOIN pedidos AS p
ON p.cliente_id = c.id
WHERE p.id IS NULL
ORDER BY c.id;Usa en el filtro una columna de la tabla derecha que sepas que no puede ser NULL en una fila real, como una clave primaria. Si filtras una columna derecha que sí admite nulos, podrías confundir «hubo coincidencia pero el dato es nulo» con «no hubo coincidencia».
¿Por qué no empezar por NOT IN?
NOT IN puede ser válido, pero tiene una trampa importante cuando la lista o subconsulta contiene NULL: la lógica de tres valores puede hacer que la exclusión no devuelva las filas que esperas. Para una pregunta de ausencia relacional, NOT EXISTS suele comunicar mejor la intención y evita depender de que el conjunto interior esté libre de nulos.
Regla práctica: «no existe una fila relacionada» → piensa primero en NOT EXISTS. «el valor no pertenece a una lista conocida y sin NULL» → NOT IN puede ser suficiente.
Rendimiento: escribe la lógica correcta y luego mide
Los optimizadores modernos pueden transformar estas formas en estrategias de semi-join o anti-join internas. No asumas que una sintaxis siempre será más rápida. Asegura índices razonables en las columnas de relación, revisa el plan con las herramientas del motor y mide con tus datos reales.
Práctica: productos sin ventas
Encuentra productos sin ninguna venta
Con las CTE siguientes, devuelve id y nombre de los productos que no aparecen en ventas. Usa NOT EXISTS.
productos en la consulta exterior.ventas.producto_id con el id exterior.Solución razonada
WITH productos(id, nombre) AS (
VALUES (1, 'Teclado'), (2, 'Ratón'), (3, 'Monitor'), (4, 'Webcam')
),
ventas(id, producto_id) AS (
VALUES (1, 1), (2, 3), (3, 1)
)
SELECT p.id, p.nombre
FROM productos AS p
WHERE NOT EXISTS (
SELECT 1
FROM ventas AS v
WHERE v.producto_id = p.id
)
ORDER BY p.id;El resultado es Ratón y Webcam. Para ellos la subconsulta correlacionada no encuentra ninguna venta, así que NOT EXISTS es verdadero.
Qué debes recordar
- Un anti-join busca filas sin correspondencia.
NOT EXISTSexpresa directamente la ausencia de una fila relacionada.LEFT JOIN ... IS NULLes equivalente si filtras una columna derecha que no pueda ser nula en una coincidencia real.NOT INexige especial cuidado conNULL.- El rendimiento depende del motor, los índices, la cardinalidad y el plan real.