La cláusula HAVING

Ya vimos la consulta que obtiene el precio medio del alquiler de los alojamientos según su tipo:

MySQL 8.4
SELECT home_type, AVG(price) as avg_price FROM Rooms
GROUP BY home_type
home_typeavg_price
Private room89.4286
Entire home/apt148.6667
Shared room40

Vamos a modificar esta consulta para que en el resultado solo aparezcan los grupos cuyo precio medio sea mayor que 50.

Con lo que ya sabes, es muy tentador usar la cláusula WHERE para esto. Pero si intentas ejecutar una consulta así, el SGBD devolverá inevitablemente un error y te indicará que la sintaxis WHERE avg_price > 50 no es correcta.

MySQL 8.4
SELECT home_type, AVG(price) as avg_price FROM Rooms
GROUP BY home_type
WHERE avg_price > 50

Adelantando la respuesta: para filtrar grupos hay que usar la cláusula HAVING:

MySQL 8.4
SELECT home_type, AVG(price) as avg_price FROM Rooms
GROUP BY home_type
HAVING avg_price > 50
MySQL 8.4
SELECT home_type, AVG(price) as avg_price FROM Rooms
GROUP BY home_type
HAVING AVG(price) > 50
home_typeavg_price
Private room89.4286
Entire home/apt148.6667

Orden de ejecución de una consulta SQL

Pero ¿por qué no podíamos usar WHERE y para qué hace falta una cláusula aparte? Todo se debe al orden en que se ejecuta una consulta SQL.

Esquema del orden de ejecución de una consulta SQL

Nuestra primera consulta era incorrecta porque intentábamos usar la columna avg_price de los grupos antes de que se formaran, ya que la cláusula WHERE se ejecuta antes de la agrupación.

Es decir, en el momento de ejecutarse, WHERE no sabe nada de la agrupación posterior: solo trabaja con los registros de la tabla. Por eso podemos usarlo, por ejemplo, para filtrar por precio los registros de la tabla Rooms antes de agrupar y solo después calcular el precio medio de los grupos de alojamientos que quedan:

MySQL 8.4
SELECT home_type, AVG(price) as avg_price FROM Rooms
WHERE price > 50
GROUP BY home_type
home_typeavg_price
Private room96.875
Entire home/apt148.6667

Estructura general de una consulta con HAVING

MySQL 8.4
SELECT campos_seleccion
FROM nombre_tabla
[WHERE condicion_filas]
GROUP BY campo_agrupacion [, ...]
HAVING condicion_grupos
[ORDER BY campo_ordenacion];
  • [ ]parte opcional
  • ...se puede repetir
campos_seleccion

Qué mostrar para cada grupo: literales, funciones de agregación y columnas de agrupación.

nombre_tabla
La tabla de la que se toman las filas.
condicion_filas

Filtra las filas de la tabla antes de agrupar. En esta condición no puedes usar funciones de agregación.

Opcional
campo_agrupacion

La columna por la que las filas se dividen en grupos. Puedes indicar varias columnas separadas por comas.

condicion_grupos

Filtra los grupos después de agrupar, por ejemplo COUNT(*) >= 5. En esta condición sí puedes usar funciones de agregación.

campo_ordenacion

La columna o expresión por la que se ordena el resultado.

Opcional

Ejemplo de uso de HAVING

Como ejemplo, vamos a obtener el precio mínimo de cada tipo de alojamiento con televisor. Además, solo nos interesan los tipos de alojamiento que tengan al menos 5 alojamientos.

Para obtener este resultado, tenemos que:

  • Primero, obtener todos los datos de la tabla

    MySQL 8.4
    SELECT ... FROM Rooms;
    
  • Después, quedarnos solo con los registros de la tabla Rooms que nos interesan, es decir, solo los alojamientos con televisor

    MySQL 8.4
    SELECT ... FROM Rooms
    WHERE has_tv = true
    
  • A continuación, agrupar estos registros de alojamientos por su tipo

    MySQL 8.4
    SELECT ... FROM Rooms
    WHERE has_tv = true
    GROUP BY home_type
    
  • Luego, filtrar los grupos obtenidos según la condición. Nos interesan los grupos que tengan al menos 5 elementos

    MySQL 8.4
    SELECT ... FROM Rooms
    WHERE has_tv = true
    GROUP BY home_type
    HAVING COUNT(*) >= 5
    
  • Y, por último, mirar qué pide la tarea y añadir la salida de la información necesaria. En nuestro caso, hay que mostrar el nombre del tipo de alojamiento y su precio mínimo.

    MySQL 8.4
    SELECT home_type, MIN(price) as min_price FROM Rooms
    WHERE has_tv = true
    GROUP BY home_type
    HAVING COUNT(*) >= 5;