Subconsultas
Una subconsulta es una consulta SELECT dentro de otra consulta. Se escribe entre paréntesis y puede aparecer en casi cualquier parte de una sentencia SQL: en el WHERE, en el SELECT, en el FROM e incluso en el HAVING. Las subconsultas permiten resolver problemas que no se pueden expresar con una sola consulta, o que serían mucho más complejos sin ellas.
MySQL ejecuta la subconsulta primero y usa su resultado como entrada para la consulta exterior.
Tipos de subconsultas
Según lo que devuelven, las subconsultas se clasifican en tres tipos:
Subconsulta escalar: devuelve un solo valor (una fila, una columna). Puede usarse en cualquier lugar donde se espere un valor: en comparaciones, en el SELECT, etc.
Subconsulta de columna: devuelve una sola columna con múltiples filas. Se usa con operadores como IN, ANY o ALL.
Subconsulta de tabla: devuelve múltiples filas y columnas. Se usa en el FROM como una tabla temporal (tabla derivada).
Subconsulta escalar en WHERE
El uso más común: comparar un valor con el resultado de una subconsulta que devuelve un solo dato. Productos con precio superior a la media:
SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos)
ORDER BY precio DESC;| nombre | precio |
|---|---|
| ASUS ROG Zephyrus | 1899.99 |
| Lenovo ThinkPad X1 | 1549.00 |
| MacBook Air M3 | 1399.00 |
| iPhone 15 Pro | 1299.99 |
| Samsung Galaxy S24 | 899.99 |
| Google Pixel 8 | 699.00 |
| Xiaomi 14 | 599.99 |
| Sofá 3 plazas | 599.00 |
| Escritorio ajust. | 399.00 |
MySQL primero calcula AVG(precio) (que es aproximadamente 339.66), y después busca los productos cuyo precio supera ese valor. La subconsulta se ejecuta una sola vez y su resultado se reutiliza para todas las filas.
Subconsulta de columna con IN
Cuando la subconsulta devuelve múltiples valores, se combina con IN. Productos de categorías que tienen más de 3 productos:
SELECT nombre, precio, categoria_id
FROM productos
WHERE categoria_id IN (
SELECT categoria_id
FROM productos
GROUP BY categoria_id
HAVING COUNT(*) > 3
)
ORDER BY nombre;| nombre | precio | categoria_id |
|---|---|---|
| Google Pixel 8 | 699.00 | 6 |
| iPhone 15 Pro | 1299.99 | 6 |
| Samsung Galaxy S24 | 899.99 | 6 |
| Xiaomi 14 | 599.99 | 6 |
La subconsulta encuentra las categorías con más de 3 productos (solo la categoría 6, Smartphones, con 4 productos). La consulta exterior devuelve los productos de esas categorías.
Subconsulta en el SELECT
Puedes usar una subconsulta escalar como columna calculada en el SELECT. El precio de cada producto comparado con la media de su categoría:
SELECT
nombre,
precio,
(SELECT ROUND(AVG(p2.precio), 2)
FROM productos p2
WHERE p2.categoria_id = p.categoria_id) AS media_categoria,
ROUND(precio - (SELECT AVG(p3.precio)
FROM productos p3
WHERE p3.categoria_id = p.categoria_id), 2) AS diferencia
FROM productos p
WHERE categoria_id = 6
ORDER BY precio DESC;| nombre | precio | media_categoria | diferencia |
|---|---|---|---|
| iPhone 15 Pro | 1299.99 | 874.74 | 425.25 |
| Samsung Galaxy S24 | 899.99 | 874.74 | 25.25 |
| Google Pixel 8 | 699.00 | 874.74 | -175.74 |
| Xiaomi 14 | 599.99 | 874.74 | -274.75 |
Para cada producto, la subconsulta calcula la media de precios de su categoría. El iPhone está 425 euros por encima de la media de smartphones, mientras que el Xiaomi está 275 euros por debajo.
Las subconsultas en el SELECT se ejecutan una vez por cada fila del resultado exterior, lo que puede ser lento con muchas filas. Para este caso, un JOIN con una tabla derivada sería más eficiente.
Subconsulta en el FROM (tabla derivada)
Una subconsulta en el FROM crea una tabla temporal con un alias. Es útil para pre-agregar datos y después filtrar:
SELECT nombre, total_pedidos, gasto_total
FROM (
SELECT
cl.nombre,
COUNT(p.id) AS total_pedidos,
SUM(p.total) AS gasto_total
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
GROUP BY cl.id, cl.nombre
) AS resumen
WHERE gasto_total > 1000
ORDER BY gasto_total DESC;| nombre | total_pedidos | gasto_total |
|---|---|---|
| Pedro | 2 | 1979.97 |
| María | 3 | 1849.95 |
| Sara | 1 | 1549.00 |
| Carmen | 1 | 1399.00 |
| Carlos | 2 | 1029.98 |
La subconsulta calcula el gasto total por cliente. La consulta exterior filtra los que superan los 1000 euros. Esto no se podría hacer con un simple HAVING porque la columna gasto_total no existe hasta que se calcula.
La tabla derivada debe tener siempre un alias (AS resumen en este caso). Sin él, MySQL devuelve un error.
Subconsulta con operadores de comparación
Además de IN, puedes usar subconsultas escalares con =, >, <, >=, <= y !=:
-- El cliente que más ha gastado
SELECT nombre, apellidos
FROM clientes
WHERE id = (
SELECT cliente_id
FROM pedidos
GROUP BY cliente_id
ORDER BY SUM(total) DESC
LIMIT 1
);| nombre | apellidos |
|---|---|
| Pedro | Fernández Castro |
La subconsulta encuentra el cliente_id con mayor gasto total, y la consulta exterior devuelve sus datos. El operador = exige que la subconsulta devuelva exactamente una fila. Si devolviera más de una, MySQL daría error.
Subconsulta con NOT IN
Clientes que no tienen ningún pedido pendiente:
SELECT nombre, apellidos
FROM clientes
WHERE id NOT IN (
SELECT cliente_id
FROM pedidos
WHERE estado = 'pendiente'
)
ORDER BY nombre
LIMIT 8;| nombre | apellidos |
|---|---|
| Ana | Martínez Ruiz |
| Andrés | Vega Romero |
| Carlos | Rodríguez Martín |
| Carmen | Ruiz Jiménez |
| David | Sánchez Moreno |
| Diego | Castro Iglesias |
| Isabel | Gil Molina |
| Javier | Moreno Díaz |
Recuerda que NOT IN tiene un comportamiento problemático cuando la subconsulta puede devolver NULL. Si algún cliente_id fuera NULL, el resultado de NOT IN sería vacío. Como vimos en la sección de operadores, NOT EXISTS es generalmente más seguro.
Subconsultas anidadas
Puedes anidar subconsultas dentro de otras subconsultas, aunque la legibilidad se reduce rápidamente:
-- Productos de la categoría que tiene el producto más caro
SELECT nombre, precio
FROM productos
WHERE categoria_id = (
SELECT categoria_id
FROM productos
WHERE precio = (SELECT MAX(precio) FROM productos)
)
ORDER BY precio DESC;| nombre | precio |
|---|---|
| ASUS ROG Zephyrus | 1899.99 |
| Lenovo ThinkPad X1 | 1549.00 |
| MacBook Air M3 | 1399.00 |
La subconsulta más interna encuentra el precio máximo (1899.99). La intermedia busca la categoría de ese producto (categoría 7, Portátiles). La exterior devuelve todos los productos de esa categoría.
Cuando las subconsultas se anidan más de dos niveles, suele ser más legible reescribirlas como CTEs, que veremos más adelante.
Practica con subconsultas
Usa el editor para practicar subconsultas anidadas:
En el siguiente artículo profundizaremos en las subconsultas correlacionadas, donde la subconsulta hace referencia a la consulta exterior.
Escrito por Eduardo Lázaro
