Aprende SQL · lección gratuita

Lección 15 · Filtrar grupos: HAVING

Resumen

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.

Sintaxis / Conceptos

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

Ejemplos

-- 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 uses HAVING para condiciones que podrían ir en WHERE: filtrar antes de agregar es más rápido y reduce el trabajo. Reserva HAVING para lo que de verdad necesita el valor del grupo.

Cheatsheet

CláusulaMomento¿Agregados?
WHEREAntes de GROUP BY (por fila)❌ No
HAVINGDespués de GROUP BY (por grupo)✅ Sí
WHERE + HAVINGCombinables en la misma consulta
HAVING COUNT(*) > nGrupos con más de n filas
HAVING SUM(x) > nGrupos cuya suma supera n

---

← Agrupar datos: GROUP BYAgrupación múltiple y orden de ejecución →

Ver todas las lecciones de Aprende SQL →