Módulo 5 · 02 | Subconsultas

MÓDULO 5 · CLASE 2

Subconsultas

Clase práctica. Subconsultas escalares, con IN, con EXISTS, correlacionadas, tablas derivadas y CTE (WITH), paso a paso y con resultados exactos.

Antes de empezar

Trabajamos con la base tablas_libro: empleados (4), alumnos (6), cursos (5, uno sin profesor), profesores (4, uno sin cursos), alumnos_cursos (8) y articulos (5). Ejecutá USE tablas_libro;

1. Cómo se ejecuta una subconsulta, paso a paso

Una subconsulta es un SELECT escrito entre paréntesis dentro de otra consulta. Ejemplo: empleados que ganan más que el salario promedio.

SELECT NOMBRE, SALARIO FROM empleados WHERE SALARIO > (SELECT AVG(SALARIO) FROM empleados);
Consulta interna (se ejecuta primero)SELECT AVG(SALARIO) FROM empleados1Devuelve: 1381,3752Consulta externa (usa ese valor)SELECT NOMBRE, SALARIO FROM empleadosWHERE SALARIO > (1381,375);3Resultado: Carlos · José4
MySQL resuelve primero la consulta de adentro y usa su resultado en la de afuera.
  1. Se ejecuta la consulta interna: AVG(SALARIO) de los 4 empleados.
  2. Devuelve un solo valor: (1500 + 1300 + 1400 + 1325,5) / 4 = 1381,375.
  3. La consulta externa compara cada salario contra ese valor.
  4. Resultado: Carlos (1500) y José (1400).

Truco para depurar

Ejecutá primero solo la subconsulta (seleccionala en Workbench y presioná Ctrl + Shift + Enter). Si su resultado es correcto, recién después ejecutá la consulta completa.

2. Dónde se puede escribir una subconsulta

Dónde se puede escribir una subconsultaSELECT(SELECT COUNT(*) FROM cursos …) AS nEscalar en SELECTun valor por cada filaFROM(SELECT … GROUP BY …) AS tTabla derivadaobligatorio poner aliasWHERESALARIO > (SELECT AVG(…))Escalarcompara contra un valorWHEREID_ALUMNO IN (SELECT …)Lista: IN / NOT INcompara contra variosWHEREEXISTS (SELECT 1 FROM …)EXISTS / NOT EXISTS¿hay al menos una fila?
Según dónde la pongas, la subconsulta debe devolver un valor, una lista o una tabla.
TipoQué devuelveSe usa con
Escalar1 fila × 1 columna=, >, <, o como columna en el SELECT
De listaVarias filas × 1 columnaIN, NOT IN, ANY, ALL
De tablaVarias filas × varias columnasEn el FROM (tabla derivada) o en un WITH

Error 1242

Si usás = y la subconsulta devuelve más de una fila, MySQL responde Subquery returns more than 1 row. En ese caso usá IN.

3. Subconsultas con IN y NOT IN

-- Alumnos inscriptos en «SQL desde cero» SELECT NOMBRE, APELLIDOS FROM alumnos WHERE ID_ALUMNO IN ( SELECT ac.ID_ALUMNO FROM alumnos_cursos AS ac INNER JOIN cursos AS c ON c.ID_CURSO = ac.ID_CURSO WHERE c.TITULO = 'SQL desde cero' ); -- Pablo Hernandaz Mata, Sergio Ot Dirmet -- Alumnos que NO cursan «SQL desde cero» SELECT NOMBRE FROM alumnos WHERE ID_ALUMNO NOT IN ( SELECT ac.ID_ALUMNO FROM alumnos_cursos AS ac INNER JOIN cursos AS c ON c.ID_CURSO = ac.ID_CURSO WHERE c.TITULO = 'SQL desde cero' ); -- Jeremias, Teresa, Marta, Carmen

La trampa de NOT IN con NULL (probala)

¿Qué profesores no dictan ningún curso? Parece fácil, pero cursos.ID_PROFE tiene un NULL («SQL avanzado»):
SELECT NOMBRE FROM profesores WHERE ID_PROFE NOT IN (SELECT ID_PROFE FROM cursos); -- 0 filas, ¡sin ningún aviso!
La comparación con NULL da «desconocido» y NOT IN descarta todas las filas. Soluciones: filtrar los NULL dentro de la subconsulta (WHERE ID_PROFE IS NOT NULL) o usar NOT EXISTS. Las dos devuelven Carlos.

4. EXISTS y NOT EXISTS

EXISTS solo pregunta «¿hay al menos una fila?». No importa qué columnas devuelva la subconsulta; por convención se escribe SELECT 1.

-- Cursos con al menos un alumno inscripto SELECT c.TITULO FROM cursos AS c WHERE EXISTS (SELECT 1 FROM alumnos_cursos AS ac WHERE ac.ID_CURSO = c.ID_CURSO); -- Programación PHP, Modelos abstracto de datos, SQL desde cero, Dibujo técnico -- Profesores sin cursos: la forma segura (no le afectan los NULL) SELECT p.NOMBRE FROM profesores AS p WHERE NOT EXISTS (SELECT 1 FROM cursos AS c WHERE c.ID_PROFE = p.ID_PROFE); -- Carlos

5. Subconsultas correlacionadas

Una subconsulta es correlacionada cuando usa una columna de la consulta externa (e.CARGO). No se ejecuta una sola vez: se recalcula para cada fila.

-- Empleados que ganan más que el promedio de SU cargo SELECT e.NOMBRE, e.CARGO, e.SALARIO FROM empleados AS e WHERE e.SALARIO > ( SELECT AVG(e2.SALARIO) FROM empleados AS e2 WHERE e2.CARGO = e.CARGO );
Subconsulta correlacionada: se recalcula para cada filaNOMBRECARGOSALARIOpromedio de su cargo¿supera?CarlosMozo15001450✔JoséMozo14001450✘ElenaSecretaria13001312,75✘MargaritaSecretaria1325,51312,75✔Por cada empleado: SELECT AVG(SALARIO) FROM empleados WHERE CARGO = (su cargo)
Los mozos promedian 1450 y las secretarias 1312,75. Superan el promedio de su cargo: Carlos y Margarita.

Escalar en el SELECT: una columna calculada por cada fila.

SELECT p.NOMBRE, (SELECT COUNT(*) FROM cursos AS c WHERE c.ID_PROFE = p.ID_PROFE) AS cursos FROM profesores AS p; -- Federico 1 · Ana 1 · Rosa 2 · Carlos 0

6. Tablas derivadas (subconsulta en el FROM)

Primero se arma una tabla temporal con la subconsulta y después se consulta sobre ella. Siempre necesita un alias; si falta, MySQL responde con el error 1248 (Every derived table must have its own alias).

-- Promedio de inscriptos por curso (entre los cursos que tienen alumnos) SELECT AVG(t.inscriptos) AS promedio FROM ( SELECT ID_CURSO, COUNT(*) AS inscriptos FROM alumnos_cursos GROUP BY ID_CURSO ) AS t; -- (3 + 1 + 2 + 2) / 4 = 2.0000

7. CTE con WITH: subconsultas con nombre (MySQL 8.0+)

Una CTE (Common Table Expression) es una tabla derivada a la que le ponés nombre al principio. Se lee de arriba hacia abajo y se puede reutilizar dentro de la misma consulta.

WITH inscriptos AS ( SELECT ID_CURSO, COUNT(*) AS cantidad FROM alumnos_cursos GROUP BY ID_CURSO ) SELECT c.TITULO, i.cantidad FROM inscriptos AS i INNER JOIN cursos AS c ON c.ID_CURSO = i.ID_CURSO WHERE i.cantidad > (SELECT AVG(cantidad) FROM inscriptos); -- Programación PHP · 3

Dato BI

Las CTE son ideales para consultas largas de reportes: cada bloque WITH es un paso con nombre (totales, ranking, filtros), igual que los pasos de Power Query.

8. ¿Subconsulta o JOIN?

Necesitás…Usá
Mostrar columnas de las dos tablasJOIN
Filtrar por «existe / no existe» en otra tablaEXISTS / NOT EXISTS
Comparar contra un valor calculado (promedio, máximo)Subconsulta escalar
Agregar en dos niveles (inscriptos por curso y luego promedio)Tabla derivada o WITH

MySQL 8 suele transformar internamente IN y EXISTS en joins (semijoins), así que elegí la forma más clara de leer y verificá el rendimiento con EXPLAIN (lo vemos en la próxima clase).

9. Práctica

  • Listá los artículos con precio menor al promedio de articulos.
  • Listá los alumnos que tienen alguna actuación destacada, usando EXISTS.
  • Mostrá la descripción del artículo más caro usando una subconsulta con MAX.
-- Soluciones -- 1) Promedio = 2,966 → Leche 1L., Agua 5L., Galletas 200 gr., Fregasuelos SELECT DESCRIPCION, PRECIO FROM articulos WHERE PRECIO < (SELECT AVG(PRECIO) FROM articulos); -- 2) Jeremias, Teresa, Sergio SELECT a.NOMBRE FROM alumnos AS a WHERE EXISTS ( SELECT 1 FROM actuaciones_destacadas AS ad WHERE ad.ID_ALUMNO = a.ID_ALUMNO ); -- 3) Café 250 gr. SELECT DESCRIPCION FROM articulos WHERE PRECIO = (SELECT MAX(PRECIO) FROM articulos);

Bibliografía y recursos online

Los recursos en inglés se pueden leer con el traductor del navegador.

Siguiente clase: Vistas e índices.

Scroll al inicio
¿Consultas? Escribinos