Aprende SQL · lección gratuita
HAVING filtra grupos después de que GROUP BY los haya formado y agregado, igual que WHERE filtra filas antes de agrupar. Sirve para preguntas como "¿qué vendedores cerraron 10 o más pedidos?" o "¿qué categorías facturan más de 230 000?". La diferencia clave: HAVING sí puede usar funciones de agregación (SUM, COUNT, AVG), mientras que WHERE no.
WHERE filtra filas, HAVING filtra grupos: WHERE actúa antes de agrupar; HAVING actúa sobre los resultados ya agregados.HAVING admite agregados: HAVING COUNT(*) >= 10 o HAVING SUM(...) > 230000 son válidos; en WHERE darían error.WHERE (descartar filas) y HAVING (descartar grupos). Es el patrón más potente.HAVING el alias del SELECT (HAVING revenue > X); para máxima compatibilidad, repetir el agregado (HAVING SUM(...) > X) siempre funciona.SELECT col_grupo, AGG(expr)
FROM tabla
[WHERE filtro_de_filas] -- antes de agrupar, SIN agregados
GROUP BY col_grupo
HAVING condicion_de_grupo -- después de agrupar, PUEDE usar agregados
[ORDER BY ...];
¿Cuándo WHERE y cuándo HAVING? Si la condición se evalúa sobre una fila individual (estado, fecha, país), va en WHERE — y es más eficiente, porque reduce los datos antes de agregar. Si la condición depende de un valor agregado del grupo (total, conteo, promedio), va en HAVING, porque ese valor no existe hasta haber agrupado.
Un truco mental: WHERE estado = 'Completado' es válido (es por fila); WHERE COUNT() > 5 es un error de sintaxis (el agregado aún no existe). Ese mismo COUNT() > 5 debe ir en HAVING.
SELECT departamento_id, ROUND(AVG(salario), 0) AS salario_prom FROM empleados GROUP BY departamento_id HAVING AVG(salario) > 6000
-- Vendedores con 10 o más pedidos (filtrado de grupos por conteo).
SELECT e.nombre, COUNT(*) AS pedidos
FROM pedidos p
JOIN empleados e ON p.empleado_id = e.id
GROUP BY e.id
HAVING COUNT(*) >= 10
ORDER BY pedidos DESC;
-- Categorías que facturan más de 230000 (HAVING sobre SUM con ROUND).
SELECT
c.nombre AS categoria,
ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue
FROM detalle_pedidos d
JOIN productos pr ON d.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY c.id
HAVING SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)) > 230000
ORDER BY revenue DESC;
-- WHERE + HAVING juntos: revenue de pedidos COMPLETADOS por vendedor,
-- mostrando solo a quienes superan 100000.
SELECT
e.nombre,
ROUND(SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)), 2) AS revenue_completado
FROM detalle_pedidos d
JOIN pedidos p ON d.pedido_id = p.id
JOIN empleados e ON p.empleado_id = e.id
WHERE p.estado = 'Completado' -- filtra filas antes de agrupar
GROUP BY e.id
HAVING SUM(d.cantidad * d.precio_unitario * (1 - d.descuento)) > 100000 -- filtra grupos
ORDER BY revenue_completado DESC;
-- Departamentos cuyo salario promedio supera 6000.
SELECT dp.nombre, ROUND(AVG(e.salario), 2) AS salario_promedio
FROM empleados e
JOIN departamentos dp ON e.departamento_id = dp.id
GROUP BY dp.id
HAVING AVG(e.salario) > 6000
ORDER BY salario_promedio DESC;
💡 No usesHAVINGpara condiciones que podrían ir enWHERE: filtrar antes de agregar es más rápido y reduce el trabajo. ReservaHAVINGpara lo que de verdad necesita el valor del grupo.
| Cláusula | Momento | ¿Agregados? |
|---|---|---|
WHERE | Antes de GROUP BY (por fila) | ❌ No |
HAVING | Después de GROUP BY (por grupo) | ✅ Sí |
WHERE + HAVING | Combinables en la misma consulta | — |
HAVING COUNT(*) > n | Grupos con más de n filas | ✅ |
HAVING SUM(x) > n | Grupos cuya suma supera n | ✅ |
---
← Agrupar datos: GROUP BYAgrupación múltiple y orden de ejecución →