Quiero volver una vez más al tema de las subconsultas en SELECT. Sobre todo, quiero recalcar que la consulta interna puede referenciar datos de la consulta externa. Parece sencillo, pero no lo es tanto. Vamos a profundizar otra vez en este tema...
Las subconsultas en SELECT te permiten añadir columnas adicionales con valores calculados o datos que dependen de otros registros o tablas. Por ejemplo, puedes mostrar una lista de estudiantes con su nota media, el número de cursos en los que están inscritos o la nota máxima actual en el grupo. Esto es útil cuando necesitas analizar datos "al vuelo", creando columnas resumen sin procesar los datos previamente.
Bases de las subconsultas en SELECT
Antes de pasar a los ejemplos, vamos a ver la sintaxis general. Las subconsultas en SELECT se ven así:
SELECT column1,
column2,
(SELECT agregación_o_condición FROM otra_tabla WHERE condición) AS nuevo_nombre_columna
FROM tabla_principal;
Fíjate que la subconsulta devuelve un solo valor, que aparece en el resultado como una nueva columna. Además, la condición puede referenciar columnas de la tabla_principal.
Ejemplo 1: Añadir la nota media del estudiante
Vamos a empezar con una consulta sencilla y útil: tenemos una tabla students y una tabla grades donde se guardan las notas de los estudiantes.
Tabla students:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Dan Seth |
Tabla grades:
| student_id | grade |
|---|---|
| 1 | 90 |
| 1 | 85 |
| 2 | 76 |
| 3 | 88 |
| 3 | 92 |
Ahora queremos obtener una lista de estudiantes con sus nombres y su nota media. Para eso usamos una subconsulta en SELECT:
SELECT
s.id,
s.name,
(SELECT AVG(g.grade)
FROM grades g
WHERE g.student_id = s.id) AS average_grade
FROM students s;
Resultado:
| id | name | average_grade |
|---|---|---|
| 1 | Alex Lin | 87.5 |
| 2 | Anna Song | 76.0 |
| 3 | Dan Seth | 90.0 |
Aquí la subconsulta (SELECT AVG(g.grade) FROM grades g WHERE g.student_id = s.id) calcula la nota media de cada estudiante. Devuelve un valor por cada fila de la tabla students, y esto es muy cómodo cuando no quieres hacer un JOIN o crear vistas previas.
Ejemplo 2: Contar el número de cursos de cada estudiante
Ahora vamos a añadir datos sobre los estudiantes: cuántos cursos están haciendo. Para eso tenemos tablas adicionales:
Tabla enrollments:
| student_id | course_id |
|---|---|
| 1 | 101 |
| 1 | 102 |
| 2 | 101 |
Mostramos la lista de estudiantes con el número de cursos en los que están inscritos:
SELECT
s.id,
s.name,
(SELECT COUNT(*)
FROM enrollments e
WHERE e.student_id = s.id) AS course_count -- referencia a la tabla students de la consulta externa
FROM students s;
Resultado:
| id | name | course_count |
|---|---|---|
| 1 | Alex Lin | 2 |
| 2 | Anna Song | 1 |
| 3 | Dan Seth | 0 |
La subconsulta (SELECT COUNT(*) FROM enrollments e WHERE e.student_id = s.id) cuenta el número de registros en la tabla enrollments para cada estudiante.
Agregación de datos en subconsultas
A menudo las subconsultas en SELECT se usan para calcular datos agregados. Funciones como AVG, SUM, COUNT, MAX, MIN te permiten procesar datos directamente dentro de otras consultas.
Ejemplo 3: Nota total del estudiante
Vamos a añadir la nota total de cada estudiante. Para eso usamos una subconsulta que suma todas las notas de la tabla grades:
SELECT
s.id,
s.name,
(SELECT SUM(g.grade)
FROM grades g
WHERE g.student_id = s.id) AS total_grade
FROM students s;
Resultado:
| id | name | total_grade |
|---|---|---|
| 1 | Alex Lin | 175 |
| 2 | Anna Song | 76 |
| 3 | Dan Seth | 180 |
Esta subconsulta (SELECT SUM(g.grade) FROM grades g WHERE g.student_id = s.id) suma las notas de cada estudiante. Si un estudiante no tiene notas, el resultado será NULL, porque SUM devuelve NULL si no hay valores.
Limitaciones y recomendaciones
- Rendimiento. Las subconsultas en
SELECTse ejecutan por separado para cada fila de la tabla principal. Esto puede causar retrasos importantes en conjuntos de datos grandes. Si puedes, cámbialas porJOINo usa datos agregados preparados de antemano. Por ejemplo:
SELECT
s.id,
s.name,
g.total_grade
FROM students s
LEFT JOIN (
SELECT student_id, SUM(grade) AS total_grade
FROM grades
GROUP BY student_id
) g ON s.id = g.student_id;
Este enfoque con JOIN es más óptimo, porque el agrupamiento y el cálculo se hacen solo una vez.
2. Problemas con NULL.
Si en la subconsulta no hay datos, el resultado será NULL. Esto puede pillarte por sorpresa. Ejemplo:
SELECT
s.id,
s.name,
(SELECT SUM(g.grade)
FROM grades g
WHERE g.student_id = s.id) AS total_grade
FROM students s;
Si un estudiante no tiene registros en grades, el resultado total_grade será NULL. Para cambiar NULL por 0, usa la función COALESCE:
SELECT
s.id,
s.name,
COALESCE((SELECT SUM(g.grade)
FROM grades g
WHERE g.student_id = s.id), 0) AS total_grade
FROM students s;
Sí, aquí como primer parámetro de la función COALESCE pasamos
(
SELECT SUM(g.grade)
FROM grades g
WHERE g.student_id = s.id
)
Optimización de subconsultas en SELECT
Para evitar cálculos innecesarios y mejorar el rendimiento:
- Usa índices en las columnas que participan en las subconsultas. Por ejemplo, indexar
student_iden la tablagradesacelerará el filtrado. - Sustituye subconsultas por datos agregados preparados con
JOINsi puedes. - Limita la cantidad de datos que procesan las subconsultas usando filtros (
WHERE).
Ejemplo final: combinando subconsultas
Vamos a juntar todo lo aprendido y crear una consulta que muestre el nombre del estudiante, la nota media, el número de cursos y la nota total:
SELECT
s.id,
s.name,
(SELECT AVG(g.grade)
FROM grades g
WHERE g.student_id = s.id) AS average_grade,
(SELECT COUNT(*)
FROM enrollments e
WHERE e.student_id = s.id) AS course_count,
(SELECT SUM(g.grade)
FROM grades g
WHERE g.student_id = s.id) AS total_grade
FROM students s;
Esta consulta devuelve el perfil completo del estudiante, usando la potencia de las subconsultas. Vemos la nota media y total, y también el número de cursos en los que está inscrito cada estudiante. Esta construcción es una forma genial de obtener información agregada rápidamente sin crear vistas (VIEW) o hacer JOINs.
| id | name | average_grade | course_count | total_grade |
|---|---|---|---|---|
| 1 | Alex Lin | 87.5 | 2 | 175 |
| 2 | Anna Song | 76.0 | 1 | 76 |
| 3 | Dan Seth | 90.0 | 0 | 180 |
GO TO FULL VERSION