5.2 GROUP BY y HAVING¶
Los agregados resumen toda la tabla. Con GROUP BY se resume por grupos: una fila de resultado por cada valor distinto de la columna de agrupación.
GROUP BY¶
¿Cuántos alumnos hay en cada ciudad?
SELECT ciudad, COUNT(*) AS alumnos
FROM alumnos
GROUP BY ciudad
ORDER BY alumnos DESC, ciudad;
Resultado:
| ciudad | alumnos |
|---|---|
| Cádiz | 5 |
| Sevilla | 4 |
| Jerez | 3 |
| Málaga | 2 |
4 filas
El motor agrupa las filas con la misma ciudad y calcula el COUNT(*) dentro de cada grupo.
La regla de oro: en el SELECT solo puede haber las columnas por las que se agrupa y funciones de agregado. Todo lo demás no tiene un valor único dentro del grupo.
Agrupar tras un JOIN¶
Se combina con lo aprendido: la media de cada asignatura (con su nombre, que está en otra tabla):
SELECT s.nombre AS asignatura,
COUNT(*) AS notas,
ROUND(AVG(n.nota), 2) AS media
FROM notas n
JOIN asignaturas s ON s.id = n.id_asignatura
WHERE n.convocatoria = 1
GROUP BY s.id, s.nombre
ORDER BY media DESC;
Resultado:
| asignatura | notas | media |
|---|---|---|
| Entornos de Desarrollo | 8 | 7.13 |
| Sistemas Informáticos | 4 | 7.13 |
| Programación | 9 | 6.67 |
| Lenguajes de Marcas | 8 | 6.63 |
| Bases de Datos | 13 | 5.81 |
| Inglés Técnico | 4 | 5.63 |
6 filas
Se agrupa por s.id y s.nombre (el id garantiza que dos asignaturas con el mismo nombre no se mezclen, y el nombre permite mostrarlo).
Agrupar por varias columnas¶
Se obtiene una fila por cada combinación de valores:
SELECT g.nombre AS grupo, a.ciudad, COUNT(*) AS alumnos
FROM alumnos a
JOIN grupos g ON g.id = a.id_grupo
GROUP BY g.nombre, a.ciudad
ORDER BY g.nombre, a.ciudad;
Resultado:
| grupo | ciudad | alumnos |
|---|---|---|
| 1DAM-A | Cádiz | 2 |
| 1DAM-A | Sevilla | 2 |
| 1DAM-B | Cádiz | 1 |
| 1DAM-B | Jerez | 1 |
| 1DAM-B | Málaga | 1 |
| 1DAM-B | Sevilla | 2 |
| 2DAM-A | Cádiz | 2 |
| 2DAM-A | Jerez | 1 |
| … | … | … |
9 filas (se muestran 8)
HAVING: filtrar los grupos¶
WHERE filtra filas antes de agrupar. Para filtrar grupos según su resultado (por ejemplo, «solo las ciudades con más de 2 alumnos») se usa HAVING, que va después del GROUP BY:
SELECT ciudad, COUNT(*) AS alumnos
FROM alumnos
GROUP BY ciudad
HAVING COUNT(*) >= 3
ORDER BY alumnos DESC;
Resultado:
| ciudad | alumnos |
|---|---|
| Cádiz | 5 |
| Sevilla | 4 |
| Jerez | 3 |
3 filas
WHERE |
HAVING |
|
|---|---|---|
| Filtra | Filas | Grupos |
| Se aplica | Antes de agrupar | Después de agrupar |
| Puede usar agregados | No | Sí |
Se pueden usar los dos en la misma consulta. Aquí: de las notas de la convocatoria 1 (WHERE), las asignaturas con media de 7 o más (HAVING):
SELECT id_asignatura, ROUND(AVG(nota), 2) AS media
FROM notas
WHERE convocatoria = 1
GROUP BY id_asignatura
HAVING AVG(nota) >= 7;
Resultado:
| id_asignatura | media |
|---|---|
| 3 | 7.13 |
| 5 | 7.13 |
2 filas
El orden de las cláusulas¶
Se escriben siempre en este orden, y el motor las evalúa en un orden distinto, que conviene tener en la cabeza:
| Se escribe | Se evalúa (aproximadamente) |
|---|---|
SELECT |
5.º: qué columnas y cálculos mostrar |
FROM / JOIN |
1.º: de dónde salen las filas |
WHERE |
2.º: filtrar filas |
GROUP BY |
3.º: agrupar |
HAVING |
4.º: filtrar grupos |
ORDER BY |
6.º: ordenar |
LIMIT |
7.º: quedarse con las primeras |
Por eso en el ORDER BY se puede usar el alias del SELECT (se evalúa después), pero no en el WHERE (se evalúa antes).
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
Columna en el SELECT que no está en el GROUP BY ni es un agregado |
Añadirla al GROUP BY o quitarla (SQLite no avisa; los demás motores sí) |
Agregado en el WHERE |
Usar HAVING |
Filtrar con HAVING algo que debería ir en WHERE |
Si no usa agregados, ponlo en WHERE: es más eficiente |
| Agrupar solo por el nombre cuando puede repetirse | Agrupar también por el id |
Usar un alias en el WHERE |
Repetir la expresión |
Para practicar¶
Los ejercicios S5.3 a S5.8 de S5 · Ejercicios practican GROUP BY y HAVING.