EXISTS en SQL: qué es, cómo se usa y ejemplos prácticos

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 NULL con IN y 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 1 o SELECT *: da igual para el resultado.
  • La subconsulta suele ser correlacionada: referencia columnas de la consulta externa (t.clave).
  • EXISTS se 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

  • IN compara un valor contra una lista/subconsulta de una sola columna.
  • EXISTS evalúa si hay filas que cumplan la condición (pueden ser varias columnas).
  • IN puede verse afectado por NULL: x NOT IN (subconsulta) puede devolver resultados inesperados si la subconsulta produce NULL.
  • EXISTS no 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 NULL emula un NOT EXISTS (encontrar filas de A sin correspondencia en B).
  • Sin embargo, NOT EXISTS suele 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

  • EXISTS no compara valores directamente; solo pregunta si hay filas.
  • Si una columna en la subconsulta es NULL, no afecta: mientras exista la fila, EXISTS es TRUE.
  • En cambio, NOT IN puede volverse UNKNOWN si la lista contiene NULL, devolviendo cero filas por precaución.

Rendimiento: ¿EXISTS es rápido?

Depende del motor y de los índices. Pautas generales:

  • EXISTS corta al primer match: con un índice adecuado en tabla_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

  1. 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);
  1. 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 1 por claridad.
  2. Reemplazar todo con EXISTS sin evaluar planes
    En algunos escenarios IN o JOIN pueden rendir igual o mejor. Medí y elegí.
  3. No indexar columnas de correlación
    Sin índices, EXISTS puede escanear demasiadas filas. Indexá B.clave (lado interno) y, si aplica, A.clave.
  4. Esperar que EXISTS devuelva datos de la subconsulta
    EXISTS es 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 NULL y 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.

Scroll al inicio