A veces no solo necesitamos agrupar datos y filtrar el resultado, sino hacerlo teniendo en cuenta una lógica extra — por ejemplo, comparar la nota media de los estudiantes de un grupo con algún criterio externo. Aquí es donde entra en juego HAVING con subconsultas — una herramienta potente que te ayuda a tomar decisiones más inteligentes directamente dentro de la consulta SQL.
Recordando HAVING
Vamos a centrarnos en las subconsultas que se usan junto con HAVING para filtrar datos a nivel de valores agregados. ¿Por qué? Si WHERE te permite filtrar filas individuales, HAVING se aplica ya a los datos agrupados — es otro nivel de análisis que amplía tus posibilidades.
Antes de meternos de lleno en la combinación de subconsultas y HAVING, vamos a refrescar qué es HAVING y en qué se diferencia de WHERE.
WHEREfiltra filas antes de hacer el agrupamiento (GROUP BY).HAVINGfiltra datos después de agregarlos, cuando ya están agrupados.
Imagina que analizas estudiantes y sus notas. Con WHERE puedes excluir estudiantes con ciertas notas mínimas, pero HAVING te permite excluir grupos enteros de estudiantes según su nota media o máxima.
Ejemplo de datos
Aquí tienes una tabla de ejemplo con estudiantes:
Tabla students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Ejemplo de uso de HAVING (sin subconsultas)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
Resultado:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
El departamento "History" no aparece en el resultado porque su nota media es menor que 75. Fácil, ¿no? Ahora vamos a meterle un poco de magia con subconsultas. En el siguiente ejemplo, por ejemplo, podemos filtrar comparando con la media general de todos los departamentos.
Subconsultas en HAVING
Las subconsultas en HAVING son una forma genial de añadir flexibilidad al filtrar datos agregados. Te permiten comparar agregados, como la nota media o el máximo, con valores calculados de otras partes de la base. O sea, puedes comprobar: "¿Nuestro resultado es mejor que la media general?"
Ejemplo: filtrar departamentos por nota media
Supón que quieres encontrar los departamentos donde los estudiantes sacan mejores notas que el resto — es decir, la media del departamento es mayor que la media de toda la universidad.
Aquí tienes los datos:
Tabla students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Primero sacamos la nota media de todos los estudiantes:
SELECT AVG(grade) AS university_avg
FROM students;
Ahora aplicamos una subconsulta en HAVING:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Resultado:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
¿Qué pasa aquí?
- La subconsulta (
SELECT AVG(grade) FROM students) calcula la nota media general — en este caso es 77. - La consulta principal agrupa los estudiantes por departamento y calcula la media de cada uno.
HAVINGcompara la media del departamento con la media general y solo deja pasar los departamentos que están por encima.
Comparando el uso de WHERE y HAVING
Para entender la diferencia, imagina que quieres seleccionar solo los estudiantes que tienen notas por encima de la media. Esto solo se puede hacer con WHERE:
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
Resultado (usando la tabla de los ejemplos anteriores):
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
Pero si quieres ver en qué departamentos la nota media de los estudiantes es mayor que la media de la universidad, necesitas HAVING — porque filtras grupos, no filas:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Resultado:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
En resumen:
WHEREtrabaja con filas individuales antes del agrupamiento.HAVINGfiltra grupos después de agregarlos.
Ejemplo: trabajando con varios agregados
Vamos a ver otro caso. Supón que tienes una tabla students donde se guardan las notas de los estudiantes y sus departamentos:
Tabla students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Ahora queremos encontrar los departamentos donde:
- La nota media de los estudiantes es mayor que la media de la universidad.
- La nota máxima del departamento es mayor que 90.
Para eso escribimos esta consulta:
SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND MAX(grade) > 90;
¿Qué pasa en esta consulta?
AVG(grade)> (SELECT AVG(grade) FROM students) — comprobamos que el departamento es más fuerte que la media.MAX(grade)> 90 — significa que hay alguien que ha sacado una nota brutal.
Resultado:
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
El departamento "Math" es el único que tiene una media por encima de la general y un estudiante sobresaliente con nota mayor que 90.
Ejemplo: selección de grupos con desviación mínima
Supón que quieres encontrar grupos donde la diferencia entre la nota máxima y mínima de los estudiantes es menor que la diferencia en toda la universidad.
Aquí tienes la tabla students con la que vamos a trabajar:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Dividimos la tarea en pasos:
- Primero calculamos la diferencia máximo-mínimo de toda la universidad:
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - Ahora creamos la consulta principal y la combinamos con esa subconsulta:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );
Resultado de la consulta:
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
Los grupos "Physics" y "Math" han mostrado notas más estables — su rango es menor que el de la universidad en general.
Optimización de consultas con HAVING y subconsultas
Recuerda que las subconsultas anidadas pueden afectar mucho al rendimiento, sobre todo en bases de datos grandes. Aquí van algunos consejos:
Usa índices. Si la subconsulta se ejecuta sobre una columna que está en WHERE o JOIN, asegúrate de que esa columna tenga un índice.
Evita el desbordamiento de datos. Si la subconsulta devuelve demasiados resultados intermedios, divídela en pasos o usa tablas temporales.
Perfila las consultas con EXPLAIN. Siempre revisa cómo PostgreSQL ejecuta tu consulta. Si ves que la subconsulta se ejecuta muchas veces, piensa en optimizarla.
Compara con CTE. En algunos casos, usar WITH (Common Table Expressions) puede ser más rápido y fácil de leer. Pero eso lo veremos en próximas lecciones :P
Combinando subconsultas, HAVING y GROUP BY
Con subconsultas en HAVING puedes construir filtros más complejos, sobre todo cuando necesitas tener en cuenta agregados, medias y otras métricas a la vez. Todo esto te ayuda a encontrar insights interesantes en datos reales.
Ejemplo: comparar departamentos por nota media y número de estudiantes
Supón que quieres seleccionar los departamentos donde:
- La nota media es mayor que la media de la universidad.
- El número de estudiantes es mayor que en el departamento con la nota media más baja.
Aquí tienes la tabla original students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
| Oleg | 60 | History |
Consulta:
SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND COUNT(*) > (
SELECT COUNT(*)
FROM students
GROUP BY department
ORDER BY AVG(grade)
LIMIT 1
);
Esta consulta muestra las posibilidades de combinar subconsultas en HAVING y GROUP BY para analizar varios criterios a la vez. Resultado:
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
El departamento History no aparece porque tiene la nota media más baja y el menor número de estudiantes. Physics y Math — ambos están por encima de la media tanto en notas como en número de estudiantes.
Errores típicos y cómo evitarlos
Error con NULL. Si los datos contienen NULL, las subconsultas con HAVING pueden devolver resultados inesperados. Usa COALESCE para manejar estos casos:
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
Datos redundantes en la subconsulta. Si la subconsulta devuelve resultados de más, eso afecta al rendimiento. Siempre afina las condiciones de la subconsulta.
No entender el orden de ejecución. Recuerda que HAVING se ejecuta después del agrupamiento, y las subconsultas pueden ejecutarse antes de la consulta principal.
Falta de índices. Si las columnas que participan en la subconsulta no están indexadas, la consulta será mucho más lenta.
Las subconsultas en HAVING te abren un montón de posibilidades para analizar datos a nivel de agregados. Puedes filtrar grupos con condiciones complejas, comparar resultados entre grupos y crear consultas analíticas avanzadas. ¡Enhorabuena, ahora ya estás listo para aplicar estos conocimientos en proyectos reales!
GO TO FULL VERSION