GROUP BY y HAVING: resumir y filtrar por grupo

Un total general puede decirnos cuántos pedidos existen, pero no cómo se distribuyen. Si necesitamos una cantidad por cliente, por estado o por mes, debemos formar grupos antes de calcular el resumen.

GROUP BY define esos grupos. HAVING permite conservar únicamente aquellos cuyo resultado agregado cumple una condición.

La idea

Todas las filas que comparten los valores indicados en GROUP BY se reúnen dentro del mismo grupo. Después aplicamos funciones como COUNT, SUM o AVG.

WHERE y HAVING actúan en momentos distintos:

  • WHERE filtra filas antes de agrupar.
  • HAVING filtra grupos después de calcular los agregados.

Sintaxis

SELECT
    ColumnaDeGrupo,
    COUNT(*) AS Cantidad
FROM dbo.Tabla
WHERE CondicionDeFila
GROUP BY ColumnaDeGrupo
HAVING COUNT(*) >= CantidadMinima;

Ejemplo

Queremos encontrar clientes con al menos dos pedidos no cancelados:

SELECT
    c.IdCliente,
    c.Nombre,
    COUNT(*) AS CantidadPedidos
FROM dbo.Cliente AS c
INNER JOIN dbo.Pedido AS p
    ON p.IdCliente = c.IdCliente
WHERE p.Estado <> 'Cancelado'
GROUP BY
    c.IdCliente,
    c.Nombre
HAVING COUNT(*) >= 2
ORDER BY CantidadPedidos DESC;
IdCliente Nombre CantidadPedidos
1 Ana Torres 2

Primero se descarta el pedido cancelado de Diego. Después se agrupan los pedidos restantes por cliente. Finalmente, HAVING conserva únicamente el grupo de Ana porque contiene dos filas.

Por qué agrupamos identificador y nombre

La consulta muestra IdCliente y Nombre, así que ambos aparecen en GROUP BY. Agrupar solo por el nombre podría mezclar personas diferentes que comparten el mismo valor.

El identificador distingue al cliente. El nombre se incluye para presentarlo sin aplicar una agregación artificial sobre él.

WHERE no sustituye a HAVING

Esta condición no puede escribirse directamente en WHERE:

COUNT(*) >= 2

En el momento en que se evalúa WHERE, los grupos todavía no existen. HAVING opera después de GROUP BY y puede evaluar el resultado de COUNT.

Cuando la condición no depende de un agregado, suele ser preferible aplicarla en WHERE para reducir las filas antes de formar los grupos.

Agrupar sin HAVING

Si queremos observar todos los estados y su cantidad de pedidos:

SELECT
    p.Estado,
    COUNT(*) AS CantidadPedidos
FROM dbo.Pedido AS p
GROUP BY p.Estado
ORDER BY p.Estado;
Estado CantidadPedidos
Cancelado 1
Confirmado 2
Enviado 1
Pendiente 1

Qué conviene comprobar

Agregar columnas a GROUP BY cambia el nivel de detalle. Un resumen por cliente no es equivalente a uno por cliente y fecha: el segundo puede producir varios grupos para la misma persona.

También debemos revisar las uniones previas. Si cada pedido tiene varias partidas y agrupamos después de unir PedidoDetalle, COUNT(*) contará partidas, no pedidos. En ese caso quizá necesitamos COUNT(DISTINCT p.IdPedido) o una consulta intermedia, según la intención.

Relacionado con

  • Funciones de agregación: calculan el resumen de cada grupo.
  • WHERE: filtra filas antes de agrupar.
  • DISTINCT: elimina repeticiones del resultado, pero no calcula agregados.
  • OVER y PARTITION BY: calculan por grupo sin reducir el número de filas.