Subconsultas en MySQL

Una subconsulta es un SELECT dentro de otro SELECT. Permiten usar el resultado de una query como dato de entrada para otra, resolviendo problemas que serían imposibles o muy incómodos de otra forma.

¿Cuándo necesitás una subconsulta?

El caso más claro es cuando la condición del WHERE depende de un valor que tenés que calcular primero. Por ejemplo: "mostrar los productos más caros que el precio promedio".

Sin subconsulta, necesitarías dos queries separadas:

-- Paso 1: calculás el promedio
SELECT AVG(precio) FROM productos;
-- Resultado: 285.50

-- Paso 2: usás ese número manualmente
SELECT nombre, precio FROM productos WHERE precio > 285.50;

Con una subconsulta, lo hacés en una sola query. MySQL ejecuta el SELECT interior primero, obtiene el promedio, y lo usa como valor del WHERE:

SELECT nombre, precio
FROM productos
WHERE precio > (SELECT AVG(precio) FROM productos)
ORDER BY precio DESC;
La subconsulta se ejecuta primero

MySQL siempre evalúa la parte entre paréntesis antes de la consulta exterior. Podés pensar en ella como una variable temporal: primero se calcula su valor, luego ese valor se usa en el resto.

Probá el Ejemplo 1: Subconsulta básica

Subconsultas con IN

Cuando la subconsulta devuelve múltiples valores en vez de uno solo, usás IN para comparar. IN chequea si el valor está dentro de la lista que devuelve la subconsulta.

-- Clientes que hicieron al menos una orden
-- La subconsulta devuelve una lista de IDs: (1, 3, 5, 7, ...)
SELECT nombre, ciudad
FROM clientes
WHERE id IN (
    SELECT DISTINCT id_cliente
    FROM ordenes
)
ORDER BY nombre;

Y con NOT IN, la lógica es inversa: traer los que NO están en esa lista.

-- Clientes que NUNCA hicieron una orden
SELECT nombre, email
FROM clientes
WHERE id NOT IN (
    SELECT DISTINCT id_cliente FROM ordenes
);
Cuidado con NOT IN y NULLs

Si la subconsulta de un NOT IN puede devolver valores NULL, el resultado puede ser inesperado — la query puede no devolver ninguna fila. Para evitarlo, agregá WHERE columna IS NOT NULL dentro de la subconsulta, o usá NOT EXISTS que veremos más abajo.

Probá el Ejemplo 2: Subconsultas con IN

Subconsultas en FROM — tablas derivadas

También podés usar una subconsulta en el FROM, como si fuera una tabla. Se llama tabla derivada: MySQL la calcula primero y luego la usás como cualquier otra tabla. Es obligatorio ponerle un alias.

El caso típico: necesitás filtrar o calcular sobre un resultado que ya viene agrupado. Por ejemplo, "de todos los clientes, mostrar solo los que gastaron más de $500 en total":

-- Primero agrupamos el gasto por cliente (subconsulta)
-- Luego filtramos sobre ese resultado (consulta exterior)
SELECT resumen.cliente, resumen.gasto_total
FROM (
    SELECT
        c.nombre                AS cliente,
        ROUND(SUM(o.total), 2) AS gasto_total
    FROM clientes c
    JOIN ordenes o ON c.id = o.id_cliente
    GROUP BY c.id, c.nombre
) AS resumen
WHERE resumen.gasto_total > 500
ORDER BY resumen.gasto_total DESC;
¿Por qué no usar HAVING directamente?

En este caso particular sí podría usarse HAVING. Pero la tabla derivada se vuelve necesaria cuando querés hacer cálculos o filtros sobre el resultado agrupado que HAVING no permite — por ejemplo, un JOIN posterior sobre el resumen.

Probá el Ejemplo 3: Tabla derivada

EXISTS — verificar si existe algo

EXISTS no compara valores — simplemente verifica si la subconsulta devuelve al menos una fila. Si devuelve aunque sea una, EXISTS es verdadero.

Es útil cuando la pregunta es "¿existe alguna fila que cumpla esta condición?" y no te importa cuántas hay ni cuáles son sus valores:

-- Clientes que tienen al menos una orden con estado 'entregado'
SELECT c.nombre, c.ciudad
FROM clientes c
WHERE EXISTS (
    SELECT 1
    FROM ordenes o
    WHERE o.id_cliente = c.id
      AND o.estado = 'entregado'
)
ORDER BY c.nombre;

Notá que dentro del EXISTS se escribe SELECT 1 en vez de una columna real. No importa qué se selecciona — solo importa si hay filas o no.

EXISTS vs IN — ¿cuándo usar cada uno?

INEXISTS
Qué hace Compara el valor con una lista Verifica si existe al menos una fila
Cuándo usarlo Lista de valores relativamente pequeña Subconsultas grandes o correlacionadas
Con NULLs Puede dar resultados inesperados con NOT IN Más seguro — no tiene problemas con NULLs

Subconsultas correlacionadas

Una subconsulta correlacionada es la que hace referencia a la consulta exterior — se ejecuta una vez por cada fila de la consulta principal. Son más lentas pero permiten comparaciones que dependen del contexto de cada fila.

-- Para cada producto, mostrar si su precio es mayor o menor al promedio de su categoría
SELECT
    p.nombre,
    p.precio,
    ROUND((
        SELECT AVG(p2.precio)
        FROM productos p2
        WHERE p2.id_categoria = p.id_categoria  -- referencia a la fila exterior
    ), 2) AS promedio_categoria,
    CASE
        WHEN p.precio > (SELECT AVG(p2.precio) FROM productos p2 WHERE p2.id_categoria = p.id_categoria)
        THEN 'Por encima del promedio'
        ELSE 'Por debajo del promedio'
    END AS comparacion
FROM productos p
ORDER BY p.id_categoria, p.precio DESC;
Las subconsultas correlacionadas pueden ser lentas

Como se ejecutan una vez por cada fila, en tablas grandes pueden ser muy lentas. Muchas veces se pueden reemplazar por un JOIN con una tabla derivada que calcula el promedio una sola vez. Si el rendimiento importa, preferí esa alternativa.

Probá el Ejemplo 4: Productos sobre el promedio de ventas

Buenas Prácticas

  • Probá la subconsulta sola primero — copiala, ejecutala por separado y verificá que devuelve lo que esperás
  • Indentá cada nivel — la subconsulta debería estar claramente más indentada que la consulta exterior
  • Alias obligatorio en tablas derivadas — MySQL lo exige y además hace el código más legible
  • Usá EXISTS en vez de IN para listas grandes — especialmente con NOT EXISTS, que es más seguro con NULLs que NOT IN
  • Si podés resolver con JOIN, usá JOIN — suele ser más eficiente que una subconsulta correlacionada que se repite por cada fila