HAVING
La cláusula HAVING filtra grupos creados por GROUP BY, igual que WHERE filtra filas individuales. La diferencia clave es que WHERE actúa antes de la agrupación (sobre filas) y HAVING actúa después (sobre grupos). Esto significa que HAVING puede usar funciones de agregación como COUNT, SUM o AVG, cosa que WHERE no puede.
Sintaxis
SELECT columnas, funcion_agregacion(columna)
FROM tabla
WHERE condicion_filas
GROUP BY columna
HAVING condicion_grupos
ORDER BY columna;HAVING se escribe después de GROUP BY y antes de ORDER BY.
Filtrar por conteo
El uso más común de HAVING es filtrar grupos según el resultado de una función de agregación. Categorías que tengan más de 2 productos:
SELECT
c.nombre AS categoria,
COUNT(*) AS total_productos
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
HAVING total_productos > 2
ORDER BY total_productos DESC;| categoria | total_productos |
|---|---|
| Smartphones | 4 |
| Portátiles | 3 |
| Accesorios electrónicos | 3 |
| Muebles | 3 |
| Fitness | 3 |
| Programación | 3 |
| Novelas | 3 |
De las 11 categorías con productos, solo 7 tienen más de 2. Las categorías Camisetas, Pantalones, Cocina y Running (con 2 productos cada una) se filtran.
Observa que usamos el alias total_productos en HAVING. MySQL permite esto porque HAVING se procesa después de SELECT.
HAVING con SUM
Clientes cuyo gasto total supera los 500 euros:
SELECT
cl.nombre,
cl.apellidos,
COUNT(p.id) AS pedidos,
SUM(p.total) AS gasto_total
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
GROUP BY cl.id, cl.nombre, cl.apellidos
HAVING gasto_total > 500
ORDER BY gasto_total DESC;| nombre | apellidos | pedidos | gasto_total |
|---|---|---|---|
| Pedro | Fernández Castro | 2 | 1979.97 |
| María | García López | 3 | 1849.96 |
| Carlos | Rodríguez Martín | 2 | 1029.98 |
| Carmen | Ruiz Jiménez | 1 | 1399.00 |
| Javier | Moreno Díaz | 1 | 599.99 |
| David | Sánchez Moreno | 2 | 509.97 |
Primero agrupamos todos los pedidos por cliente, después filtramos los grupos cuya suma de totales supera los 500 euros. Pedro lidera con casi 1980 euros en 2 pedidos.
HAVING con AVG
Categorías cuyo precio medio supera los 100 euros:
SELECT
c.nombre AS categoria,
ROUND(AVG(p.precio), 2) AS precio_medio,
COUNT(*) AS productos
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
HAVING precio_medio > 100
ORDER BY precio_medio DESC;| categoria | precio_medio | productos |
|---|---|---|
| Portátiles | 1616.00 | 3 |
| Smartphones | 874.74 | 4 |
| Muebles | 382.66 | 3 |
| Cocina | 144.99 | 2 |
| Running | 139.99 | 2 |
Solo 5 de las 11 categorías tienen un precio medio superior a 100 euros.
WHERE vs HAVING
La diferencia fundamental:
- WHERE filtra filas antes de agrupar. No puede usar funciones de agregación.
- HAVING filtra grupos después de agrupar. Puede usar funciones de agregación.
Puedes usarlos juntos en la misma consulta. Cada uno hace su trabajo en un momento diferente:
SELECT
estado,
COUNT(*) AS total,
ROUND(AVG(total), 2) AS media
FROM pedidos
WHERE estado != 'cancelado'
GROUP BY estado
HAVING total >= 4
ORDER BY total DESC;| estado | total | media |
|---|---|---|
| pendiente | 7 | 307.70 |
| entregado | 7 | 692.84 |
| enviado | 4 | 509.74 |
El flujo es:
WHERE estado != 'cancelado': elimina los 3 pedidos cancelados (quedan 22).GROUP BY estado: agrupa los 22 pedidos en 4 grupos (pendiente, procesando, enviado, entregado).HAVING total >= 4: filtra los grupos con menos de 4 pedidos. "procesando" tiene solo 4, así que pasa. Veamos... en realidad "procesando" tiene 4, así que sí pasa el filtro>= 4.
Un error común es usar WHERE donde debería ir HAVING:
-- ERROR: no puedes usar funciones de agregación en WHERE
SELECT estado, COUNT(*) AS total
FROM pedidos
WHERE COUNT(*) > 3
GROUP BY estado;ERROR 1111 (HY000): Invalid use of group function
Las funciones de agregación solo tienen sentido después de la agrupación, y WHERE se ejecuta antes.
HAVING sin GROUP BY
Técnicamente, puedes usar HAVING sin GROUP BY. En ese caso, MySQL trata toda la tabla como un solo grupo:
SELECT
COUNT(*) AS total_productos,
AVG(precio) AS precio_medio
FROM productos
HAVING total_productos > 20;| total_productos | precio_medio |
|---|---|
| 30 | 339.6567 |
Como hay 30 productos (mayor que 20), la condición se cumple y se muestra el resultado. Si hubiera 20 o menos, el resultado estaría vacío. Aunque esto es válido, en la práctica casi siempre usarás HAVING junto con GROUP BY.
HAVING con múltiples condiciones
Puedes combinar varias condiciones en HAVING con AND y OR:
SELECT
c.nombre AS categoria,
COUNT(*) AS total,
ROUND(AVG(p.precio), 2) AS precio_medio,
SUM(p.stock) AS stock
FROM productos p
JOIN categorias c ON p.categoria_id = c.id
GROUP BY c.nombre
HAVING total >= 3
AND precio_medio < 100
ORDER BY stock DESC;| categoria | total | precio_medio | stock |
|---|---|---|---|
| Accesorios electrónicos | 3 | 35.32 | 650 |
| Fitness | 3 | 83.32 | 320 |
| Novelas | 3 | 14.99 | 265 |
| Programación | 3 | 34.99 | 157 |
Categorías con al menos 3 productos y un precio medio inferior a 100 euros. Ambas condiciones deben cumplirse para que el grupo aparezca en el resultado.
Orden de procesamiento completo
Con HAVING en la mezcla, el orden completo de procesamiento queda:
- FROM / JOIN: identifica y une tablas.
- WHERE: filtra filas individuales.
- GROUP BY: agrupa las filas restantes.
- HAVING: filtra grupos.
- SELECT: evalúa expresiones y alias.
- ORDER BY: ordena el resultado.
- LIMIT: restringe filas.
Practica con HAVING
Usa el editor para filtrar grupos con HAVING:
En el siguiente artículo veremos patrones comunes de HAVING con COUNT para encontrar duplicados, valores únicos y distribuciones.
Escrito por Eduardo Lázaro
