SQL · Capítulo 4 · Lección de conceptos · 5 min de lectura

Cómo GROUP BY pliega filas en grupos

Un agregado comprime muchas filas en un número. GROUP BY lo hace una vez por grupo, y el orden de las cláusulas decide si una condición filtra filas o grupos.

La idea

Una función de agregado toma muchas filas y devuelve un valor: un conteo, una suma, un promedio, un mínimo o un máximo. SELECT COUNT(*) FROM orders convierte doce mil filas en una sola fila que dice 12000.

GROUP BY hace lo mismo una vez por grupo en lugar de una vez para toda la tabla. Reparte las filas en montones según el valor de una columna, como channel, y luego cada montón se pliega en una sola fila. Entran ocho pedidos y salen tres filas. El detalle desaparece a propósito, para que puedas ver el patrón.

Figura 1

GROUP BY channel pliega ocho pedidos en tres filas

orders
order_idchannelstatus
1003marketplacecompleted
1004marketplacecompleted
1005webcompleted
1006marketplacecancelled
1007marketplacecompleted
1008webcompleted
1009appcompleted
1010webcompleted
COUNT(*) por canal
channelorders
marketplace4
web3
app1

Paso 1 de 4 Ocho pedidos, una fila cada uno. La pregunta: ¿cuántos pedidos hay por canal? Eso es SELECT channel, COUNT(*) FROM orders GROUP BY channel.

  1. Ocho pedidos, una fila cada uno. La pregunta: ¿cuántos pedidos hay por canal? Eso es SELECT channel, COUNT(*) FROM orders GROUP BY channel.
  2. Primero GROUP BY reparte las filas en montones, uno por cada canal distinto. Los cuatro pedidos de marketplace van al primer montón.
  3. Luego el agregado corre una vez por montón. Cada montón se convierte en una fila de resultado. Los tres pedidos web se pliegan en una fila que dice 3.
  4. El montón de app tiene un solo pedido, así que se pliega en una fila que dice 1. Entran ocho filas y salen tres. Los pedidos individuales desaparecen: solo quedan el grupo y su conteo.

Por qué existe

Nadie en una reunión quiere una lista de 12,000 pedidos. Quieren saber cuántos hay por canal, los ingresos por mes, el pedido promedio por cliente. Son resúmenes, y la idea es perder el detalle de forma controlada. Sin GROUP BY tendrías que filtrar y contar a mano, una vez por canal, y escribir una consulta nueva cada vez que apareciera un canal. Con él, una sola consulta atiende todos los grupos, incluso los que no sabías que existían. El costo es que un resumen puede esconder cosas: un promedio de 40 dólares puede venir de cuarenta pedidos de 40 o de uno de 1,000 y muchos diminutos. Mira siempre un conteo junto a cada promedio, para saber cuántas filas lo respaldan.

Cómo funciona por dentro

La consulta se ejecuta en un orden fijo, y ese orden explica todas las reglas de este capítulo:

  1. FROM elige la tabla.
  2. WHERE conserva o descarta filas individuales.
  3. GROUP BY reparte las filas que sobrevivieron en montones según la clave.
  4. Los agregados (COUNT, SUM, AVG, MIN, MAX) calculan un valor por montón.
  5. HAVING conserva o descarta montones completos, usando los valores de los agregados.
  6. SELECT, ORDER BY y LIMIT dan forma y cortan lo que queda.

WHERE y HAVING

Los dos filtran, pero en momentos distintos y sobre cosas distintas. WHERE habla de una fila (un status, un channel). HAVING habla de un grupo (su COUNT(*), su SUM). Una condición sobre un total no puede ir en WHERE, porque los totales todavía no existen cuando WHERE corre.

Figura 2

WHERE quita filas antes de que existan los grupos, HAVING quita grupos después

  1. FROM orders 8 pedidos
  2. WHERE status = 'completed' quita el pedido 1006, una fila
  3. GROUP BY channel marketplace 3, web 3, app 1
  4. HAVING COUNT(*) >= 3 quita app, un grupo
  5. SELECT channel, COUNT(*) marketplace 3, web 3

Paso 1 de 5 Entran los ocho pedidos. Todavía no se ha agrupado nada, así que no hay ningún conteo que probar.

  1. Entran los ocho pedidos. Todavía no se ha agrupado nada, así que no hay ningún conteo que probar.
  2. WHERE mira filas sueltas. El pedido cancelado 1006 falla y sale, así que marketplace solo contará 3 pedidos, no 4.
  3. Ahora las filas se pliegan en grupos: marketplace 3, web 3 y app 1. Solo aquí existen los conteos.
  4. HAVING mira grupos completos, usando el agregado. El grupo app tiene 1 pedido, así que todo el grupo se descarta.
  5. SELECT muestra lo que queda: dos grupos, dos filas. WHERE COUNT(*) >= 3 fallaría, porque en el paso de WHERE todavía no existe ningún conteo.

Una prueba útil: si la condición menciona un agregado, va en HAVING. Si describe una sola fila, ponla en WHERE. Las condiciones de fila en WHERE además son más rápidas, porque reducen los datos antes de que empiece el trabajo de agrupar.

Qué puede llevar SELECT

Después de agrupar hay una fila por grupo, así que cada columna de SELECT debe ser una de dos cosas: la propia clave de agrupación o un agregado. SELECT channel, status, COUNT(*) ... GROUP BY channel es ambiguo: un canal tiene varios estados, y la base de datos no sabe cuál mostrarte. O agregas status al GROUP BY, lo que crea grupos más pequeños, o lo quitas.

Contar: tres versiones

COUNT(*) cuenta filas. COUNT(columna) cuenta las filas donde esa columna no es NULL. COUNT(DISTINCT columna) cuenta los valores distintos que no son NULL. Todos los demás agregados también se saltan los NULL: AVG(unit_cost) promedia solo los productos que tienen costo.

Figura 3

COUNT(*) cuenta filas, COUNT(columna) cuenta valores que no son NULL

customers
customer_idfirst_namereferral_source
1Owensearch
2DouglasNULL
3HenryNULL
4Brianpinterest
5KyleNULL
Resultado
count_starcount_referral
52

Paso 1 de 3 Cinco clientes. Tres no tienen referral_source, que es NULL.

  1. Cinco clientes. Tres no tienen referral_source, que es NULL.
  2. COUNT(*) cuenta filas. No mira dentro de ellas, así que la respuesta es 5.
  3. COUNT(referral_source) cuenta solo las filas donde esa columna tiene un valor. Los tres NULL se saltan y la respuesta es 2.

Por eso "¿cuántos clientes llegaron por referencia?" y "¿cuántos clientes hay?" son preguntas distintas con conteos distintos, y por eso debes decir cuál conteo quieres.

Revisar un resultado agrupado

Suma los conteos de los grupos: 4 + 3 + 1 es 8, igual que las ocho filas con que empezaste. Si los números de un GROUP BY no suman el COUNT(*) simple de la tabla filtrada, algo se perdió: normalmente una clave NULL. Las filas con NULL en la columna de agrupación no se descartan. Forman un grupo propio, que se muestra con la clave vacía o NULL, así que búscalo en la salida antes de fiarte de los totales.

Razones y tasas

Muchos números de negocio son un agregado dividido por otro: reembolsos entre pedidos, pedidos completados entre todos los pedidos. Calcula ambos en la misma consulta y divídelos, recordando que en algunas bases de datos la división entera puede redondear a cero. Multiplica por 1.0 primero, o convierte uno de los lados a decimal. Lo habitual es calcular la tasa por grupo: el grupo es el canal, y los dos números se pliegan del mismo montón.

Ingresos a partir de las líneas de pedido

Los ingresos no se guardan por pedido. Son la suma de cantidad por precio sobre las líneas de cada pedido, así que una consulta de ingresos suma una expresión: SUM(quantity * unit_price). Los agregados aceptan expresiones, no solo nombres de columna. Comprueba que sumas en el nivel correcto. Si primero haces un join y luego sumas una columna que pertenece al pedido, cada línea la repite.

Errores comunes

"COUNT(columna) y COUNT(*) son lo mismo"

Solo coinciden cuando la columna no tiene NULL. En Harbor & Pine, COUNT(referral_source) es cerca de dos tercios de COUNT(*) en los clientes, porque muchos clientes no tienen fuente de referencia. Elige a propósito, y para "cuántas filas" usa COUNT(*).

"WHERE y HAVING son intercambiables"

HAVING con una condición de fila a veces funciona, pero filtra después de que se hizo el trabajo de agrupar y es fácil de malinterpretar. Y un total en WHERE es un error. Piensa en cuándo corre cada uno y la elección se hace sola. Poner una condición de fila en el lugar equivocado puede cambiar en silencio lo que se cuenta.

"GROUP BY ordena el resultado"

Reparte las filas en montones internamente, pero no promete el orden de los grupos en la salida. Si quieres el canal más grande primero, agrega ORDER BY COUNT(*) DESC. Sin eso, la misma consulta puede listar los canales en otro orden mañana.

Ponte a prueba

Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.

Con los pedidos 1003 a 1010 ejecutas WHERE status = 'completed' GROUP BY channel HAVING COUNT(*) >= 4. Marketplace tiene 4 pedidos, uno de ellos (1006) cancelado. ¿Qué pasa con marketplace?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.