Ejemplos de SQL GROUP BY

GROUP BY agrupa y colapsa filas: produce una fila de salida por grupo. Las funciones de agregación (COUNT, SUM, AVG, MAX, MIN) resumen cada grupo. Aquí tienes los patrones que vas a reutilizar y el error con el que acabarás tropezando.

🎙️ Publicado y grabado:

🎧 Escuchar esta guía

Los patrones fundamentales

-- ¿cuántos pedidos tiene cada cliente?
SELECT user_id, COUNT(*) AS orders
FROM orders
GROUP BY user_id;

-- ingresos por cliente, de mayor a menor, los 10 primeros
SELECT user_id, SUM(amount) AS revenue
FROM orders
GROUP BY user_id
ORDER BY revenue DESC
LIMIT 10;

-- varias agregaciones a la vez: la consulta de informes
SELECT user_id,
       COUNT(*)    AS orders,
       SUM(amount) AS revenue,
       AVG(amount) AS avg_order,
       MAX(amount) AS biggest
FROM orders
GROUP BY user_id;

-- agrupar por un valor calculado: pedidos por mes
SELECT strftime('%Y-%m', created_at) AS month,   -- SQLite;
       COUNT(*)                                    -- Postgres: date_trunc
FROM orders
GROUP BY month;

EL error

SELECT user_id, name, SUM(amount)   -- ← name es el problema
FROM orders JOIN users ON ...
GROUP BY user_id;

ERROR: column "users.name" must appear in the GROUP BY
clause or be used in an aggregate function

Por qué ocurre: después de colapsar las filas, un grupo de 50 filas contiene 50 valores de name; la base de datos se niega a elegir uno por ti. Solución: añádelo a la clave del grupo (GROUP BY user_id, name; está bien porque el nombre es constante para cada usuario) o envuélvelo en una función de agregación (MAX(name), la forma de decir «sé que es constante»).

Filtrar grupos: HAVING

-- clientes que gastaron 100 o más (condición sobre el GRUPO → HAVING)
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE created_at >= '2026-01-01'    -- filtro de filas: ANTES de agrupar
GROUP BY user_id
HAVING SUM(amount) >= 100;          -- filtro de grupos: DESPUÉS

-- la pista: si la condición contiene SUM/COUNT, no puede ir en WHERE

El detector de duplicados: memorízalo

SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- cada email duplicado con su recuento. sirve para cualquier columna.
Dos trampas discretas
COUNT(*) frente a COUNT(col): el asterisco cuenta filas; COUNT(col) omite los NULL de esa columna. Los resultados son distintos y ambos son «correctos». ¿Quieres contar pedidos? Usa el asterisco. ¿Pedidos con cupón? Usa COUNT(coupon). NULL forma su propio grupo: las filas con NULL en la clave de agrupación se juntan en un único grupo NULL. Esa fila misteriosa al principio del informe no es un error: son tus datos ausentes llamando la atención.
📚 Esta es una página de nuestra serie de SQL. El curso intensivo completo incluye JOINs, trampas de NULL y la historia de terror de UPDATE: SQL en 10 minutos →