Blog · 20 de julio de 2026
Guía práctica de subconsultas SQL: escalares, IN/ANY/ALL, correlacionadas con EXISTS y tablas derivadas. Con ejemplos y ejercicios interactivos gratis.
Si llevas un tiempo escribiendo consultas SQL, tarde o temprano te topas con una pregunta que un SELECT simple no puede responder: "¿qué empleados ganan más que el promedio?", "¿qué clientes nunca han comprado?". Para responderlas necesitas que una consulta use el resultado de otra consulta. Eso es, exactamente, una subconsulta.
En esta guía vas a ver qué son, los cuatro tipos que existen, cuándo conviene usarlas (y cuándo conviene un JOIN), con ejemplos que puedes ejecutar gratis en tu navegador.
Una subconsulta (también llamada consulta anidada o subquery) es un SELECT escrito dentro de otra sentencia SQL, siempre entre paréntesis. El motor ejecuta primero la consulta interior y usa su resultado en la exterior.
SELECT nombre, salario
FROM empleados
WHERE salario > (SELECT AVG(salario) FROM empleados);
Aquí la subconsulta (SELECT AVG(salario) FROM empleados) calcula el salario promedio, y la consulta exterior lo usa como si fuera un número: "dame los empleados que ganan más que ese valor". Intentar esto sin subconsulta (WHERE salario > AVG(salario)) da error — las funciones de agregación no se pueden usar directo en el WHERE.
La forma más útil de clasificarlas es por lo que devuelven, porque eso determina dónde puedes usarlas:
| Tipo | Devuelve | Se usa típicamente con |
|---|---|---|
| Escalar | Un solo valor (1 fila, 1 columna) | =, >, <, operaciones |
| De lista | Una columna con varias filas | IN, NOT IN, ANY, ALL |
| Correlacionada | Depende de cada fila exterior | EXISTS, NOT EXISTS |
| Tabla derivada | Una tabla completa | En el FROM |
Veamos cada una con un ejemplo real.
Devuelve exactamente una fila con una columna, así que puedes usarla en cualquier lugar donde iría un número o un texto: en el WHERE, en el SELECT, hasta en un cálculo.
-- ¿Qué productos cuestan más que el precio promedio?
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos);
-- Mostrar cada salario junto con la diferencia contra el máximo
SELECT nombre,
salario,
(SELECT MAX(salario) FROM empleados) - salario AS diferencia_con_el_top
FROM empleados;
Ojo: si la subconsulta escalar devuelve más de una fila, el motor lanza un error. Si eso te pasa, probablemente necesitabas IN en vez de =.
Cuando la subconsulta devuelve una lista de valores, se combina con operadores de conjunto:
-- Clientes que SÍ han hecho pedidos
SELECT nombre
FROM clientes
WHERE id IN (SELECT DISTINCT cliente_id FROM pedidos);
-- Clientes que NUNCA han comprado (el clásico de las entrevistas)
SELECT nombre
FROM clientes
WHERE id NOT IN (SELECT cliente_id FROM pedidos WHERE cliente_id IS NOT NULL);
El
IS NOT NULLde la segunda consulta no es decorativo: si la lista de unNOT INcontiene unNULL, el resultado completo queda vacío. Es uno de los errores más traicioneros de SQL.
ANY y ALL comparan contra la lista con un operador:
-- Productos más caros que TODOS los de la categoría 'Accesorios'
SELECT nombre, precio
FROM productos
WHERE precio > ALL (SELECT precio FROM productos WHERE categoria = 'Accesorios');
> ANY significa "mayor que al menos uno" (equivale a > MIN(...)), y > ALL significa "mayor que todos" (equivale a > MAX(...)).
Las anteriores se ejecutan una sola vez. Una subconsulta correlacionada hace referencia a la fila de la consulta exterior, así que se evalúa para cada fila:
-- Empleados que ganan más que el promedio DE SU PROPIO departamento
SELECT nombre, departamento, salario
FROM empleados e
WHERE salario > (SELECT AVG(salario)
FROM empleados
WHERE departamento = e.departamento);
La pista para reconocerlas: la consulta interior usa el alias de la exterior (e.departamento). Su pareja natural es EXISTS, que solo pregunta "¿hay al menos una fila que cumpla?" sin traer datos:
-- Clientes con al menos un pedido completado
SELECT nombre
FROM clientes c
WHERE EXISTS (SELECT 1
FROM pedidos p
WHERE p.cliente_id = c.id
AND p.estado = 'Completado');
NOT EXISTS es además la forma más segura de expresar "que no tengan...": no sufre el problema del NULL que vimos con NOT IN.
Una subconsulta en el FROM crea una tabla temporal con nombre que puedes consultar como cualquier otra:
-- Promedio de ventas por cliente, calculado en dos pasos
SELECT AVG(total_por_cliente) AS promedio_general
FROM (SELECT cliente_id, SUM(total) AS total_por_cliente
FROM pedidos
GROUP BY cliente_id) AS ventas;
El alias (AS ventas) es obligatorio. Cuando estas tablas derivadas se anidan demasiado y la consulta se vuelve ilegible, la evolución natural son los CTE (WITH ... AS), que hacen lo mismo con nombres declarados arriba — pero ese tema merece su propio artículo.
Casi toda subconsulta puede reescribirse como JOIN y viceversa. Una guía práctica para decidir:
EXISTS), o cuando la versión anidada se lee más natural.= con una subconsulta que devuelve varias filas → cámbialo por IN.NOT IN con una lista que contiene NULL → devuelve vacío; usa NOT EXISTS o filtra los NULL.FROM → error de sintaxis.La teoría se fija escribiendo consultas. En mi curso interactivo Aprende SQL hay un módulo completo de subconsultas con ejercicios autocorregidos que corren en tu navegador (SQLite real, sin registro):
Cada lección trae 3 ejercicios (básico, intermedio y reto) sobre un dataset de empresa realista, y un copiloto de IA te da pistas si te trabas. Empieza por la 22 y en una tarde las subconsultas dejan de ser un misterio.