Aprende SQL · lección gratuita

Lección 24 · Subconsultas correlacionadas y EXISTS

Resumen

Una subconsulta correlacionada es una subconsulta que referencia una columna de la consulta externa: por eso no se ejecuta una sola vez, sino una vez por cada fila de la consulta externa (conceptualmente). Es la herramienta para comparar cada fila contra un cálculo que depende de ella misma (p. ej. cada empleado contra el promedio de su departamento). El operador EXISTS es su compañero natural: comprueba si la subconsulta correlacionada devuelve al menos una fila, sin importar qué valores.

Sintaxis / Conceptos

Correlación

La clave es que el alias de la tabla externa es visible dentro de la subconsulta. En SELECT ... FROM empleados e WHERE e.salario > (SELECT AVG(e2.salario) FROM empleados e2 WHERE e2.departamento_id = e.departamento_id), la e del exterior se inyecta en el filtro interno. Para cada empleado e, el motor calcula el promedio salarial restringido a su departamento y compara. Sin la condición e2.departamento_id = e.departamento_id sería una subconsulta global no correlacionada (el promedio de toda la empresa).

EXISTS vs IN vs JOIN

SELECT empresa FROM clientes WHERE EXISTS (SELECT 1 FROM pedidos WHERE cliente_id = clientes.id AND estado = 'Completado') LIMIT 5

Ejemplos

-- EXISTS: clientes que tienen AL MENOS UN pedido completado.
-- La subconsulta se correlaciona por p.cliente_id = c.id; basta una fila para incluir al cliente.
SELECT empresa
FROM clientes c
WHERE EXISTS (
  SELECT 1 FROM pedidos p
  WHERE p.cliente_id = c.id AND p.estado = 'Completado'
)
ORDER BY empresa;

-- NOT EXISTS: productos que NUNCA se han vendido (anti-join robusto).
SELECT id, nombre
FROM productos p
WHERE NOT EXISTS (
  SELECT 1 FROM detalle_pedidos d WHERE d.producto_id = p.id
)
ORDER BY id;

-- NOT EXISTS: clientes SIN ningún pedido cancelado (la subconsulta no devuelve filas para ellos).
SELECT empresa
FROM clientes c
WHERE NOT EXISTS (
  SELECT 1 FROM pedidos p
  WHERE p.cliente_id = c.id AND p.estado = 'Cancelado'
)
ORDER BY empresa;

-- Subconsulta CORRELACIONADA escalar: empleados que ganan más que el promedio de SU departamento.
-- El promedio se recalcula por departamento gracias a e2.departamento_id = e.departamento_id.
SELECT e.nombre, e.departamento_id, e.salario
FROM empleados e
WHERE e.salario > (
  SELECT AVG(e2.salario) FROM empleados e2
  WHERE e2.departamento_id = e.departamento_id
)
ORDER BY e.departamento_id, e.salario DESC;
💡 Prefiere NOT EXISTS sobre NOT IN cuando la subconsulta pueda contener NULL: NOT IN (… NULL …) devuelve cero filas inesperadamente, mientras que NOT EXISTS se comporta correctamente. Y recuerda: dentro de EXISTS, SELECT 1 o SELECT * dan igual — solo cuenta si hay filas.
Rendimiento: "una vez por cada fila externa" es el modelo conceptual, no lo que realmente cuesta. Con un índice sobre la columna correlacionada (p. ej. pedidos.cliente_id), el motor no rastrea toda la tabla por cada fila: hace una búsqueda directa al índice y EXISTS corta en cuanto encuentra la primera coincidencia. Además, muchos optimizadores reescriben EXISTS como un semi-join eficiente. Conclusión: no evites EXISTS por miedo a que sea lento — con el índice adecuado suele ser tan rápido (o más) que las alternativas.

Cheatsheet

ConstrucciónQué hace
WHERE col = ext.col (en subq)Correlación: liga la subconsulta a la fila externa
EXISTS (SELECT 1 ...)Verdadero si la subconsulta tiene ≥ 1 fila
NOT EXISTS (SELECT 1 ...)Verdadero si la subconsulta no tiene filas (anti-join)
Subconsulta correlacionada escalarCompara cada fila contra un agregado propio (su grupo)
EXISTS vs JOINEXISTS no duplica filas externas

---

← Subconsultas con IN, ANY y ALLTablas derivadas (subconsultas en FROM) →

Ver todas las lecciones de Aprende SQL →