1. Qué es
GROUP BY parte las filas en montones y hace que las agregaciones se calculen una vez por montón, en vez de una vez para toda la tabla.
830 pedidos → GROUP BY ship_country → 21 grupos → 21 filas de resultado
Es el paso 3 del orden-logico-de-ejecucion, y de ahí sale la regla que gobierna todo el tema:
Después del
GROUP BY, la fila individual ya no existe. Solo existen los grupos. Por eso elSELECTúnicamente puede pedir dos cosas: columnas por las que agrupaste, o agregaciones. Nada más.
Entender eso quita de golpe el error más frecuente de SQL.
2. Sintaxis
SELECT columna_agrupada, agregación(...)
FROM tabla
WHERE filtro_de_filas -- antes de agrupar
GROUP BY columna_agrupada
HAVING filtro_de_grupos -- después de agrupar
ORDER BY ...;
Reglas:
- Se puede agrupar por una o varias columnas, por una expresión, por el alias o por el número de posición.
- El número de filas del resultado = el número de combinaciones distintas de las columnas agrupadas.
- Los
NULLforman su propio grupo. Uno solo, aunqueNULL <> NULL. - Sin
GROUP BYpero con agregaciones, toda la tabla es un grupo y sale una fila.
3. Ejemplos sobre Northwind
Lo básico:
SELECT ship_country,
count(*) AS pedidos,
round(sum(freight),2) AS flete
FROM orders
GROUP BY ship_country
ORDER BY pedidos DESC
LIMIT 5;
ship_country | pedidos | flete
--------------+---------+----------
Germany | 122 | 11283.28
USA | 122 | 13771.29
Brazil | 83 | 4880.19
France | 77 | 4237.84
UK | 56 | 2954.27
Alemania y Estados Unidos empatan a 122 pedidos, pero el flete de EE. UU. es 2.500 mayor. Ese contraste solo aparece porque pediste dos agregaciones a la vez; con count(*) solo, los dos países parecerían iguales.
3.1 El error que todo el mundo comete
SELECT ship_country, order_id, count(*)
FROM orders
GROUP BY ship_country;
ERROR: column "orders.order_id" must appear in the GROUP BY clause
or be used in an aggregate function
El mensaje es literalmente la regla. Y la pregunta que hay detrás es razonable: si Alemania tiene 122 pedidos, ¿cuál de los 122 order_id debería enseñarte? No hay respuesta, así que SQL se niega en vez de inventar.
Las tres salidas, según lo que querías de verdad:
| Lo que querías | Qué escribir |
|---|---|
| Un pedido cualquiera del grupo | min(order_id) o max(order_id) |
| Todos los pedidos del grupo | string_agg(order_id::text, ', ') |
| Un desglose más fino | Añadir order_id al GROUP BY — pero entonces cada grupo es una fila y el count(*) vale 1 |
MySQL en modo permisivo acepta esa consulta y devuelve un order_id arbitrario sin avisar. Es una de las razones por las que informes migrados desde MySQL traen números que nadie sabe explicar.
3.2 Agrupar por varias columnas
SELECT ship_country, ship_city, count(*)
FROM orders
GROUP BY ship_country, ship_city
ORDER BY 3 DESC
LIMIT 4;
ship_country | ship_city | count
--------------+----------------+-------
Brazil | Rio de Janeiro | 34
UK | London | 33
Brazil | Sao Paulo | 31
USA | Boise | 31
70 grupos, uno por pareja país-ciudad. Cuantas más columnas agrupas, más grupos y menos filas por grupo — hasta el extremo de agrupar por la clave primaria, donde cada grupo es una fila y agrupar deja de servir de nada.
3.3 Agrupar por una expresión
No hace falta que la columna exista:
SELECT extract(year FROM order_date)::int AS anio, count(*)
FROM orders
GROUP BY 1
ORDER BY 1;
anio | count
------+-------
1996 | 152
1997 | 408
1998 | 270
Tres formas equivalentes de escribir ese GROUP BY: GROUP BY 1 (posición), GROUP BY anio (alias), o repetir extract(...) entero. Las tres funcionan — es la comodidad que GROUP BY concede y HAVING no, y el porqué está en la sección 4 de este mismo tema.
Y el mismo patrón, por mes:
SELECT to_char(order_date, 'YYYY-MM') AS mes, count(*)
FROM orders
WHERE order_date >= '1997-01-01' AND order_date < '1997-04-01'
GROUP BY 1 ORDER BY 1;
mes | count
---------+-------
1997-01 | 33
1997-02 | 29
1997-03 | 30
Fíjate en el orden WHERE → GROUP BY: el filtro se aplica a las filas antes de agrupar, así que los meses de 1996 ni siquiera llegan a formar grupo.
3.4 Los NULL forman un grupo
SELECT region, count(*)
FROM customers
GROUP BY region
ORDER BY count(*) DESC
LIMIT 3;
region | count
--------+-------
| 60
SP | 6
OR | 4
La primera fila, con la región vacía, es el grupo de los 60 NULL. GROUP BY los junta todos en uno, aunque NULL = NULL no sea cierto (null-y-la-logica-de-tres-valores). Es una excepción deliberada del estándar: para agrupar, los nulos se consideran "el mismo desconocido".
En un informe conviene etiquetarlos, porque una fila con la primera celda vacía parece un error:
SELECT coalesce(region, '(sin región)') AS region, count(*) FROM customers GROUP BY 1;
3.5 Lo que GROUP BY no puede hacer: inventar grupos vacíos
SELECT category_id, count(*) FROM products GROUP BY category_id ORDER BY category_id;
category_id | count
-------------+-------
1 | 12
2 | 12
3 | 13
4 | 10
5 | 7
6 | 6
7 | 5
8 | 12
Ocho categorías, todas presentes. Pero solo porque todas tienen productos. Si mañana se crea la categoría 9 sin productos, no aparecerá en este resultado: GROUP BY agrupa las filas que hay, no las que podría haber.
Es el mismo problema que con los meses sin pedidos: una serie mensual construida con GROUP BY se salta los meses vacíos y el gráfico pinta una línea continua donde había un agujero. La solución es generar el eje aparte y hacer LEFT JOIN — fechas-sin-actividad.
3.6 Dos niveles a la vez
SELECT employee_id,
extract(year FROM order_date)::int AS anio,
count(*)
FROM orders
GROUP BY employee_id, anio
ORDER BY employee_id, anio
LIMIT 6;
employee_id | anio | count
-------------+------+-------
1 | 1996 | 26
1 | 1997 | 55
1 | 1998 | 42
2 | 1996 | 16
2 | 1997 | 41
Esta forma —una fila por combinación— es la larga. La misma información en formato ancho, con los años como columnas, es el pivote con CASE de case-condicionales-en-sql. La larga es la que se puede seguir procesando; la ancha es la que se enseña.
4. Dónde encaja en el orden de ejecución
GROUP BY es el paso 3 del orden-logico-de-ejecucion: después de filtrar filas (WHERE, paso 2) y antes de que se ejecute el SELECT (paso 5). Tres consecuencias.
4.1 El WHERE filtra antes de agrupar; no hay forma de que sea al revés
Todo lo que descartes en el paso 2 no llega a formar grupos. Por eso filtrar en el WHERE es más barato que filtrar después: el GROUP BY trabaja con menos filas.
4.2 Después del paso 3, la fila individual ya no existe
Es la regla de la sección 1, ahora con su porqué: el SELECT se ejecuta en el paso 5, cuando lo único que queda son grupos. No puede pedir una columna suelta porque, sencillamente, ya no hay ninguna que pedir — hay 21 montones, no 830 filas.
4.3 GROUP BY sí acepta un nombre puesto con AS, aunque el orden diga que no debería
SELECT ship_country AS pais, count(*) AS pedidos
FROM orders
GROUP BY pais; -- ✅ funciona
Si el orden lógico manda, el paso 3 no debería ver un nombre que nace en el paso 5. Y en teoría no lo ve: es una comodidad que el estándar SQL concede solo a GROUP BY y a ORDER BY, no a WHERE ni a HAVING. La regla práctica, que conviene memorizar tal cual:
En
GROUP BYyORDER BYpuedes usar el nombre del alias. EnWHEREyHAVING, nunca.
5. Errores comunes
- Poner en el
SELECTuna columna que no está agrupada ni agregada. Da error, y el error tiene razón. - Agrupar por demasiadas columnas. Si añades la clave primaria al
GROUP BY, cada grupo es una fila y todos loscount(*)valen 1. Suele pasar al añadir columnas "para verlas". - Esperar que aparezcan los grupos vacíos. No aparecen.
- Olvidar que los
NULLhacen grupo. Aparece una fila con la celda en blanco que parece un fallo. - Filtrar en
HAVINGlo que debería ir enWHERE. Funciona pero es más lento y más confuso (having-vs-where). - Agrupar por una columna descriptiva en vez de por su id. Si dos categorías se llaman igual, se fusionan en un grupo. Agrupa por la clave, muestra el nombre.
- Creer que
GROUP BYordena. No lo garantiza. Si quieres orden,ORDER BY.
6. Diferencias con SQL Server
GROUP BY es igual en ambos: mismas reglas, mismo error cuando falta una columna. Las diferencias son de alrededor:
| PostgreSQL | SQL Server | |
|---|---|---|
| Agrupar por alias | ✅ GROUP BY anio |
❌ Hay que repetir la expresión |
| Agrupar por posición | ✅ GROUP BY 1 |
❌ No se permite |
| Columnas dependientes de la PK | ✅ Si agrupas por la PK, puedes seleccionar el resto sin agregarlos | ❌ Hay que listarlas todas |
GROUPING SETS / ROLLUP / CUBE |
✅ | ✅ |
La tercera es la más cómoda de PostgreSQL y conviene conocerla:
SELECT c.customer_id, c.company_name, c.country, count(*)
FROM customers c JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id; -- funciona: customer_id es la PK de customers
PostgreSQL sabe que si agrupaste por la clave primaria, company_name y country están funcionalmente determinados y no pueden ser ambiguos. SQL Server exige listar las tres. Es más cómodo, y menos portable: si el material tiene que valer para los dos motores, lista las columnas.
7. Ejercicios
- Cuántos productos hay por categoría, de la categoría con más a la que menos.
- Los 5 clientes con más pedidos.
- Unidades vendidas por producto, y los 3 más vendidos. ¿Cuántos productos distintos se han vendido alguna vez?
- Pedidos por empleado y por año, solo de 1997.
- Explica por qué esta consulta devuelve 830 filas y no 21:
sql SELECT ship_country, order_id, count(*) FROM orders GROUP BY ship_country, order_id;
8. Pregunta de negocio
Ventas quiere saber "qué comercial vende más". Te dan libertad para elegir la métrica. Entrega la respuesta y explica por qué elegiste esa métrica y no otra.
Con lo que sabes hasta aquí puedes hacerlo sin JOIN, aunque salgan ids en vez de nombres.