Subconsultas en SQL: IN, EXISTS y subconsultas correlacionadas
Actualizado el 29 de septiembre de 2026
Una subconsulta es una consulta dentro de otra, entre paréntesis. Sirve cuando para responder una pregunta primero hay que calcular algo: "los productos más caros que el promedio" necesita conocer el promedio, y "los productos que nunca se vendieron" necesita mirar otra tabla.
Los ejemplos usan la tabla de productos de las guías anteriores y una tabla de ventas, donde producto_id indica qué producto se vendió:
CREATE TABLE productos (
id INTEGER PRIMARY KEY,
nombre TEXT,
categoria TEXT,
precio INTEGER,
stock INTEGER
);| id | nombre | categoria | precio | stock |
|---|---|---|---|---|
| 1 | Teclado | Computación | 25990 | 12 |
| 2 | Mouse | Computación | 12990 | 30 |
| 3 | Monitor | Computación | 159990 | 4 |
| 4 | Audífonos | Audio | 34990 | 0 |
| 5 | Parlante | Audio | 49990 | 7 |
| 6 | Silla | Muebles | 89990 | 3 |
| 7 | Escritorio | Muebles | 129990 | NULL |
| id | producto_id | cantidad |
|---|---|---|
| 1 | 1 | 2 |
| 2 | 2 | 5 |
| 3 | 1 | 1 |
| 4 | 5 | 1 |
| 5 | 3 | 1 |
Una subconsulta que devuelve un valor
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos)
ORDER BY precio DESC;| nombre | precio |
|---|---|
| Monitor | 159990 |
| Escritorio | 129990 |
| Silla | 89990 |
La subconsulta calcula el promedio (71.990) y la consulta externa lo usa como si fuera un número escrito a mano. No se puede escribir WHERE precio > AVG(precio) directamente, porque en el WHERE no se permiten agregaciones.
IN: comparar contra una lista de valores
SELECT nombre
FROM productos
WHERE id IN (SELECT producto_id FROM ventas);| nombre |
|---|
| Teclado |
| Mouse |
| Monitor |
| Parlante |
La subconsulta devuelve una columna con los producto_id que aparecen en ventas, y IN se queda con los productos cuyo id está en esa lista. El teclado aparece una sola vez aunque tenga dos ventas: IN solo pregunta si está, no cuántas veces.
EXISTS y NOT EXISTS
SELECT p.nombre
FROM productos p
WHERE NOT EXISTS (
SELECT 1 FROM ventas v WHERE v.producto_id = p.id
);| nombre |
|---|
| Audífonos |
| Silla |
| Escritorio |
EXISTS es verdadero si la subconsulta devuelve al menos una fila, y NOT EXISTS si no devuelve ninguna. Lo que se selecciona adentro da igual (por convención, SELECT 1). Esta subconsulta usa p.id, una columna de la consulta externa: se evalúa una vez por cada producto. Es una subconsulta correlacionada.
Subconsultas correlacionadas
Una subconsulta correlacionada puede calcular algo distinto para cada fila. Por ejemplo, el producto más caro de cada categoría:
SELECT p.categoria, p.nombre, p.precio
FROM productos p
WHERE p.precio = (
SELECT MAX(p2.precio)
FROM productos p2
WHERE p2.categoria = p.categoria
)
ORDER BY p.categoria;| categoria | nombre | precio |
|---|---|---|
| Audio | Parlante | 49990 |
| Computación | Monitor | 159990 |
| Muebles | Escritorio | 129990 |
Para cada producto p, la subconsulta busca el precio máximo de su categoría, y el producto se queda si su precio coincide. Los alias p y p2 son necesarios porque es la misma tabla dos veces. Si dos productos empataran en el máximo, aparecerían ambos.
Subconsultas en el FROM
El resultado de una consulta es una tabla, así que se puede usar en el FROM como si fuera una. Es útil para filtrar o volver a agrupar un resultado ya agrupado:
SELECT categoria, total_unidades
FROM (
SELECT p.categoria, SUM(v.cantidad) AS total_unidades
FROM ventas v
JOIN productos p ON p.id = v.producto_id
GROUP BY p.categoria
) AS resumen
WHERE total_unidades >= 2;| categoria | total_unidades |
|---|---|
| Computación | 9 |
La subconsulta calcula las unidades vendidas por categoría (Computación 9, Audio 1) y la consulta externa filtra ese resultado. Aquí daría lo mismo usar HAVING, pero cuando hay que combinar varios resúmenes, una subconsulta en el FROM (o un WITH, que es lo mismo con nombre) ordena mucho el razonamiento. Dale siempre un alias: en varias bases de datos es obligatorio.
Errores comunes
NOT IN con valores NULL
WHERE id NOT IN (SELECT producto_id FROM ventas) funciona con estos datos, pero si alguna venta tuviera producto_id en NULL, la consulta devolvería cero filas: comparar con un NULL da un resultado desconocido, y NOT IN necesita que todas las comparaciones sean falsas. NOT EXISTS no tiene este problema, y por eso es la opción más segura para buscar lo que falta.
Usar = con una subconsulta que devuelve varias filas
WHERE id = (SELECT producto_id FROM ventas) falla en PostgreSQL ("more than one row returned by a subquery"). SQLite no da error: usa la primera fila y devuelve solo el teclado, un resultado que parece válido y no lo es. Si la subconsulta puede devolver varias filas, usa IN.
Subconsultas correlacionadas sobre tablas grandes
Una subconsulta correlacionada se evalúa, en principio, una vez por fila de la consulta externa. Con tablas grandes puede ser lenta. Muchas veces se puede reescribir con un JOIN y GROUP BY; conviene conocer ambas formas.
Ejercicios resueltos
Intenta resolver cada ejercicio antes de abrir la solución: equivocarse y corregir es la parte que más enseña.
1.Productos más caros que el promedio
Escribe una consulta que muestre nombre y precio de los productos cuyo precio supera el promedio de todos los productos.
Ver soluciónOcultar solución
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos);El resultado son el monitor, el escritorio y la silla (sin el ORDER BY, en el orden que devuelva la base). La subconsulta devuelve un solo valor, así que se puede comparar con >. Si el promedio cambia porque se agregan productos, la consulta sigue siendo correcta: por eso es mejor que escribir 71990 a mano.
2.Productos que nunca se vendieron
Escribe una consulta que devuelva el nombre y el stock de los productos que no tienen ninguna venta.
Ver soluciónOcultar solución
SELECT p.nombre, p.stock
FROM productos p
WHERE NOT EXISTS (
SELECT 1 FROM ventas v WHERE v.producto_id = p.id
);| nombre | stock |
|---|---|
| Audífonos | 0 |
| Silla | 3 |
| Escritorio | NULL |
NOT EXISTS es la forma más segura de buscar lo que falta. El mismo resultado se obtiene con un LEFT JOIN y WHERE v.id IS NULL, como en la guía de JOINs. Con NOT IN también funcionaría con estos datos, pero se rompe si aparece un NULL en la columna.
3.El producto más barato de cada categoría
Escribe una consulta que muestre, para cada categoría, el nombre y el precio de su producto más barato.
Ver soluciónOcultar solución
SELECT p.categoria, p.nombre, p.precio
FROM productos p
WHERE p.precio = (
SELECT MIN(p2.precio)
FROM productos p2
WHERE p2.categoria = p.categoria
)
ORDER BY p.categoria;| categoria | nombre | precio |
|---|---|---|
| Audio | Audífonos | 34990 |
| Computación | Mouse | 12990 |
| Muebles | Silla | 89990 |
Es la subconsulta correlacionada de la guía con MIN en vez de MAX. La condición p2.categoria = p.categoria es lo que hace que el mínimo se calcule por categoría: sin ella, se compararía contra el mínimo de toda la tabla y solo aparecería el mouse.