En SQL, EXISTS es un predicado que devuelve TRUE si la subconsulta que lo acompaña retorna al menos una fila, y FALSE si no retorna ninguna. No importa qué columnas devuelva la subconsulta; lo único relevante es si existe al menos un registro que cumpla la condición.
EXISTS (y su opuesto NOT EXISTS) es fundamental para escribir consultas expresivas y eficientes, sobre todo en validaciones, filtros dependientes de otras tablas y patrones de semi-join / anti-join.
¿Cuándo usar EXISTS?
- Cuando querés filtrar filas de una tabla A en función de si hay (o no) registros relacionados en una tabla B.
- Cuando necesitás condiciones correlacionadas: la subconsulta se “ata” a la fila actual de la consulta externa.
- Cuando querés evitar problemas de
NULLconINy lograr un plan de ejecución eficiente en muchos motores.
Sintaxis de EXISTS y NOT EXISTS
-- Forma general
SELECT ...
FROM tabla_externa t
WHERE EXISTS (
SELECT 1
FROM tabla_relacionada r
WHERE r.clave = t.clave
-- ... más condiciones
);
-- Negación (anti-join)
SELECT ...
FROM tabla_externa t
WHERE NOT EXISTS (
SELECT 1
FROM tabla_relacionada r
WHERE r.clave = t.clave
);
Notas clave:
- Dentro de la subconsulta suele usarse
SELECT 1oSELECT *: da igual para el resultado. - La subconsulta suele ser correlacionada: referencia columnas de la consulta externa (
t.clave). EXISTSse evalúa como verdadero apenas encuentra la primera fila que cumple; los SGBD optimizan para cortar temprano.
Ejemplos prácticos con EXISTS
1) Clientes que tienen pedidos
SELECT c.id, c.nombre
FROM clientes c
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
);
👉 Devuelve solo los clientes con al menos un pedido.
2) Clientes sin pedidos (NOT EXISTS)
SELECT c.id, c.nombre
FROM clientes c
WHERE NOT EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
);
👉 Lista de clientes sin pedidos (patrón anti-join).
3) Productos con al menos una devolución
SELECT pr.id, pr.nombre
FROM productos pr
WHERE EXISTS (
SELECT 1
FROM devoluciones d
WHERE d.producto_id = pr.id
AND d.fecha >= '2025-01-01'
);
👉 Productos con alguna devolución en 2025.
4) Empleados con certificaciones vigentes
SELECT e.id, e.nombre
FROM empleados e
WHERE EXISTS (
SELECT 1
FROM certificaciones c
WHERE c.empleado_id = e.id
AND c.vencimiento >= CURRENT_DATE
);
👉 Empleados con certificación al día.
5) Evitar duplicados en inserciones con INSERT
INSERT INTO usuarios (email, nombre)
SELECT 'lucia@example.com', 'Lucía'
WHERE NOT EXISTS (
SELECT 1 FROM usuarios u WHERE u.email = 'lucia@example.com'
);
👉 Inserta solo si no existe ya ese email.
EXISTS vs IN vs LEFT JOIN (y por qué importa)
EXISTS vs IN
INcompara un valor contra una lista/subconsulta de una sola columna.EXISTSevalúa si hay filas que cumplan la condición (pueden ser varias columnas).INpuede verse afectado porNULL:x NOT IN (subconsulta)puede devolver resultados inesperados si la subconsulta produceNULL.EXISTSno sufre este problema: solo revisa existencia de filas.
Equivalente típico:
-- IN
SELECT * FROM clientes
WHERE id IN (SELECT cliente_id FROM pedidos);
-- EXISTS (más robusto frente a NULL)
SELECT * FROM clientes c
WHERE EXISTS (
SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id
);
EXISTS vs LEFT JOIN … IS NULL (anti-join)
LEFT JOIN+WHERE r.clave IS NULLemula un NOT EXISTS (encontrar filas de A sin correspondencia en B).- Sin embargo,
NOT EXISTSsuele ser más claro y, con frecuencia, mejor optimizado por el planificador.
Equivalentes:
-- Anti-join con LEFT JOIN
SELECT a.*
FROM A
LEFT JOIN B ON B.clave = A.clave
WHERE B.clave IS NULL;
-- Anti-join más claro
SELECT a.*
FROM A
WHERE NOT EXISTS (
SELECT 1 FROM B WHERE B.clave = A.clave
);
Comportamiento con NULL y por qué EXISTS es seguro
EXISTSno compara valores directamente; solo pregunta si hay filas.- Si una columna en la subconsulta es
NULL, no afecta: mientras exista la fila,EXISTSes TRUE. - En cambio,
NOT INpuede volverse UNKNOWN si la lista contieneNULL, devolviendo cero filas por precaución.
Rendimiento: ¿EXISTS es rápido?
Depende del motor y de los índices. Pautas generales:
EXISTScorta al primer match: con un índice adecuado entabla_relacionada(clave)puede ser muy eficiente.- Para anti-joins (
NOT EXISTS), ayudá al optimizador con índices sobre las claves relacionadas. - En consultas complejas, probá alternativas (
IN,JOIN) y verificá planes de ejecución; no hay una regla única.
Consejo: indexá las columnas que usás en la correlación (p.cliente_id, d.producto_id, etc.).
Patrones útiles con EXISTS
1) Semi-join (mantener filas que tienen relación)
SELECT a.*
FROM A a
WHERE EXISTS (SELECT 1 FROM B b WHERE b.a_id = a.id);
2) Anti-join (descartar filas relacionadas)
SELECT a.*
FROM A a
WHERE NOT EXISTS (SELECT 1 FROM B b WHERE b.a_id = a.id);
3) Filtro condicional con columnas de la subconsulta
SELECT c.*
FROM clientes c
WHERE EXISTS (
SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
AND p.total > 100
);
4) Reglas de negocio en validaciones
-- Clientes con reclamos abiertos en los últimos 30 días
SELECT c.*
FROM clientes c
WHERE EXISTS (
SELECT 1 FROM reclamos r
WHERE r.cliente_id = c.id
AND r.estado = 'Abierto'
AND r.fecha >= CURRENT_DATE - INTERVAL '30 days'
);
Errores comunes con EXISTS
- Olvidar correlacionar la subconsulta
-- ❌ Siempre TRUE si la tabla tiene cualquier fila
WHERE EXISTS (SELECT 1 FROM pedidos);
Correlacioná con la fila externa:
WHERE EXISTS (SELECT 1 FROM pedidos p WHERE p.cliente_id = c.id);
- Suponer que SELECT * es más lento que SELECT 1
A nivel lógico, no importa; la mayoría de motores optimizan igual. UsáSELECT 1por claridad. - Reemplazar todo con EXISTS sin evaluar planes
En algunos escenariosINoJOINpueden rendir igual o mejor. Medí y elegí. - No indexar columnas de correlación
Sin índices,EXISTSpuede escanear demasiadas filas. IndexáB.clave(lado interno) y, si aplica,A.clave. - Esperar que EXISTS devuelva datos de la subconsulta
EXISTSes booleano: no devuelve columnas. Si necesitás datos de B, usáJOIN.
Buenas prácticas
- Índices en columnas usadas en la correlación de la subconsulta.
- Usá NOT EXISTS para anti-joins: es legible y evita trampas con
NULL. - Preferí EXISTS cuando la subconsulta puede producir
NULLy querés robustez frente a eso. - Limitá el trabajo dentro de la subconsulta: filtros concretos y sin columnas innecesarias.
- Probá variantes (
JOIN,IN) y compara planes/tiempos en tu SGBD.
Ejemplo avanzado: auditoría de integridad
Objetivo: detectar pedidos que refieren clientes inexistentes.
SELECT p.id, p.cliente_id
FROM pedidos p
WHERE NOT EXISTS (
SELECT 1
FROM clientes c
WHERE c.id = p.cliente_id
);
👉 Si esta consulta devuelve filas, hay inconsistencias (falta integridad referencial o claves foráneas).
Preguntas frecuentes (FAQ)
1) ¿EXISTS devuelve datos o solo TRUE/FALSE?
Solo evalúa si existen filas; es un predicado booleano. No devuelve columnas de la subconsulta.
2) ¿Qué diferencia práctica hay entre EXISTS y IN?EXISTS es robusto frente a NULL y útil con correlaciones; IN es cómodo para listas o subconsultas de una columna sin NULL.
3) ¿NOT EXISTS es equivalente a LEFT JOIN … IS NULL?
Sí, para anti-joins. Muchas veces NOT EXISTS es más claro y se optimiza bien.
4) ¿Importa usar SELECT 1 o SELECT * dentro de EXISTS?
No para el resultado. La mayoría de motores optimiza igual. SELECT 1 solo es una convención.
5) ¿EXISTS funciona en todos los SGBD?
Sí: MySQL/MariaDB, PostgreSQL, SQL Server, Oracle y otros. La sintaxis y semántica son estándar.
Conclusión
EXISTS es una herramienta poderosa para filtrar por existencia de filas relacionadas, escribir semi-joins y anti-joins claros, y evitar problemas con NULL que aparecen en otros enfoques. Con índices adecuados y condiciones bien acotadas, suele ofrecer excelente rendimiento y gran legibilidad.
Dominar EXISTS y NOT EXISTS te permitirá diseñar consultas profesionales para validaciones, auditorías, reportes y reglas de negocio complejas sin sacrificar claridad ni performance.
Mira patrones comunes con subconsultas en Subconsultas (Subqueries) y repasa definiciones en el Glosario SQL completo.