Aprende SQL · lección gratuita
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.
... WHERE e2.departamento_id = e.departamento_id.EXISTS (subq): verdadero si la subconsulta devuelve alguna fila. Por convención se escribe SELECT 1 porque las columnas no importan.NOT EXISTS (subq): verdadero si la subconsulta no devuelve filas. Es el anti-join robusto frente a NOT IN (no falla con NULLs).EXISTS corta pronto: en cuanto encuentra una fila deja de buscar; es eficiente con un índice sobre la columna correlacionada.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 JOINEXISTS solo pregunta "¿hay o no hay?": no le importa cuántas filas ni qué valores, por eso SELECT 1 es idiomático.EXISTS no duplica filas de la consulta externa (a diferencia de un JOIN, que puede multiplicarlas si hay varias coincidencias).NOT EXISTS es la forma más segura de expresar un anti-join: a diferencia de NOT IN, no se rompe si aparecen valores NULL en la subconsulta.SELECT empresa FROM clientes WHERE EXISTS (SELECT 1 FROM pedidos WHERE cliente_id = clientes.id AND estado = 'Completado') LIMIT 5
-- 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;
💡 PrefiereNOT EXISTSsobreNOT INcuando la subconsulta pueda contenerNULL:NOT IN (… NULL …)devuelve cero filas inesperadamente, mientras queNOT EXISTSse comporta correctamente. Y recuerda: dentro deEXISTS,SELECT 1oSELECT *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 yEXISTScorta en cuanto encuentra la primera coincidencia. Además, muchos optimizadores reescribenEXISTScomo un semi-join eficiente. Conclusión: no evitesEXISTSpor miedo a que sea lento — con el índice adecuado suele ser tan rápido (o más) que las alternativas.
| Construcción | Qué 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 escalar | Compara cada fila contra un agregado propio (su grupo) |
EXISTS vs JOIN | EXISTS no duplica filas externas |
---
← Subconsultas con IN, ANY y ALLTablas derivadas (subconsultas en FROM) →