GROUP BY en SQL: formas comunes de usarlo con ejemplos claros

Guía práctica para estudiantes sobre GROUP BY en SQL: conteos, sumas, promedios, HAVING, varias columnas, JOIN, fechas, CASE y subtotales.

GROUP BY en SQL: guía directa para estudiantes

Cuando usamos SELECT normalmente vemos filas individuales. Con GROUP BY dejamos de pensar fila por fila y empezamos a pensar por grupos.

Ejemplos típicos:

  • Cuántos alumnos hay por curso.
  • Cuánto se vendió por cliente.
  • Cuántas ventas hubo por día.
  • Qué emails están repetidos.
  • Promedio de notas por materia.

La estructura base es esta:

SELECT columna_agrupada, FUNCION_AGREGADA(columna)
FROM tabla
GROUP BY columna_agrupada;

Script base para practicar

Podés copiar este ejemplo y probar todas las consultas del artículo.

CREATE TABLE Ventas (
  Id INT PRIMARY KEY,
  Cliente VARCHAR(50),
  Categoria VARCHAR(50),
  Producto VARCHAR(50),
  Cantidad INT,
  PrecioUnitario DECIMAL(10, 2),
  FechaVenta DATE
);

INSERT INTO Ventas (Id, Cliente, Categoria, Producto, Cantidad, PrecioUnitario, FechaVenta) VALUES
(1, 'Ana', 'Cursos', 'SQL básico', 1, 1200.00, '2026-06-01'),
(2, 'Bruno', 'Cursos', 'Java básico', 1, 1500.00, '2026-06-01'),
(3, 'Ana', 'Libros', 'Apunte SQL', 2, 300.00, '2026-06-02'),
(4, 'Carla', 'Cursos', 'SQL básico', 1, 1200.00, '2026-06-02'),
(5, 'Bruno', 'Libros', 'Apunte Java', 3, 250.00, '2026-06-03'),
(6, 'Ana', 'Cursos', 'SQL avanzado', 1, 1800.00, '2026-06-03'),
(7, 'Carla', 'Libros', 'Apunte SQL', 1, 300.00, '2026-06-03');

1. GROUP BY simple

Queremos saber cuántas ventas hizo cada cliente.

SELECT
  Cliente,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente;

Qué pasa:

ClienteQué hace SQL
AnaJunta todas las filas de Ana y las cuenta
BrunoJunta todas las filas de Bruno y las cuenta
CarlaJunta todas las filas de Carla y las cuenta

COUNT(*) cuenta filas. Como agrupamos por Cliente, el conteo se calcula por cada cliente.


2. GROUP BY con SUM

Queremos saber cuántas unidades se vendieron por categoría.

SELECT
  Categoria,
  SUM(Cantidad) AS UnidadesVendidas
FROM Ventas
GROUP BY Categoria;

SUM(Cantidad) suma la columna Cantidad dentro de cada grupo.

Esto responde preguntas como:

  • Cuántos productos se vendieron por categoría.
  • Cuántas horas trabajó cada empleado.
  • Cuántos puntos hizo cada jugador.

3. GROUP BY con cálculo de importes

Muchas veces no queremos sumar una columna directa, sino una cuenta.

SELECT
  Cliente,
  SUM(Cantidad * PrecioUnitario) AS TotalGastado
FROM Ventas
GROUP BY Cliente;

Acá cada fila calcula Cantidad * PrecioUnitario, y después se suma por cliente.

Este patrón es muy común en reportes de ventas.


4. GROUP BY con AVG, MIN y MAX

También podemos sacar promedios, mínimos y máximos.

SELECT
  Categoria,
  AVG(PrecioUnitario) AS PrecioPromedio,
  MIN(PrecioUnitario) AS PrecioMasBajo,
  MAX(PrecioUnitario) AS PrecioMasAlto
FROM Ventas
GROUP BY Categoria;

Funciones agregadas comunes:

FunciónPara qué sirve
COUNT(*)Contar filas
SUM(columna)Sumar valores
AVG(columna)Calcular promedio
MIN(columna)Obtener el menor valor
MAX(columna)Obtener el mayor valor

5. GROUP BY con varias columnas

Podemos agrupar por más de una columna. Por ejemplo: total vendido por cliente y categoría.

SELECT
  Cliente,
  Categoria,
  SUM(Cantidad * PrecioUnitario) AS Total
FROM Ventas
GROUP BY Cliente, Categoria;

Esto no crea un grupo solo por cliente, sino por la combinación:

  • Ana + Cursos
  • Ana + Libros
  • Bruno + Cursos
  • Bruno + Libros
  • Carla + Cursos
  • Carla + Libros

6. GROUP BY con WHERE

WHERE filtra filas antes de agrupar.

Ejemplo: calcular ventas por cliente, pero solo para la categoría Cursos.

SELECT
  Cliente,
  SUM(Cantidad * PrecioUnitario) AS TotalCursos
FROM Ventas
WHERE Categoria = 'Cursos'
GROUP BY Cliente;

Orden lógico:

  1. Primero SQL filtra las filas con WHERE Categoria = 'Cursos'.
  2. Después agrupa por Cliente.
  3. Después calcula el SUM.

7. GROUP BY con HAVING

HAVING filtra grupos después de agrupar.

Ejemplo: mostrar solo clientes que gastaron más de 1500.

SELECT
  Cliente,
  SUM(Cantidad * PrecioUnitario) AS TotalGastado
FROM Ventas
GROUP BY Cliente
HAVING SUM(Cantidad * PrecioUnitario) > 1500;

Diferencia clave:

CláusulaCuándo filtraEjemplo
WHEREAntes del GROUP BYFilas de la categoría Cursos
HAVINGDespués del GROUP BYClientes con total mayor a 1500

8. GROUP BY para detectar duplicados

Este es un clásico de ejercicios y entrevistas.

Supongamos una tabla simple:

CREATE TABLE Personas (
  Id INT PRIMARY KEY,
  Nombre VARCHAR(50),
  Email VARCHAR(100)
);

INSERT INTO Personas (Id, Nombre, Email) VALUES
(1, 'Ana', 'ana@mail.com'),
(2, 'Bruno', 'bruno@mail.com'),
(3, 'Carla', 'ana@mail.com'),
(4, 'Diego', 'diego@mail.com'),
(5, 'Elena', 'bruno@mail.com');

Consulta para encontrar emails repetidos:

SELECT
  Email,
  COUNT(*) AS Cantidad
FROM Personas
GROUP BY Email
HAVING COUNT(*) > 1;

La idea:

  1. Agrupamos por Email.
  2. Contamos cuántas veces aparece cada email.
  3. Con HAVING COUNT(*) > 1 dejamos solo los repetidos.

9. GROUP BY con ORDER BY

Podemos ordenar el resultado final.

SELECT
  Cliente,
  SUM(Cantidad * PrecioUnitario) AS TotalGastado
FROM Ventas
GROUP BY Cliente
ORDER BY TotalGastado DESC;

Esto devuelve los clientes de mayor a menor gasto.

También podrías ordenar por conteo:

SELECT
  Cliente,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente
ORDER BY TotalVentas DESC;

10. GROUP BY por fecha

Si la columna es una fecha completa, podés agrupar por día directamente.

SELECT
  FechaVenta,
  COUNT(*) AS VentasDelDia,
  SUM(Cantidad * PrecioUnitario) AS TotalDelDia
FROM Ventas
GROUP BY FechaVenta
ORDER BY FechaVenta;

Si tenés una columna con fecha y hora, muchas bases permiten convertirla a fecha para agrupar por día. En SQL Server suele verse así:

SELECT
  CAST(FechaVenta AS DATE) AS Dia,
  COUNT(*) AS VentasDelDia
FROM Ventas
GROUP BY CAST(FechaVenta AS DATE);

11. GROUP BY con CASE

CASE sirve para crear grupos personalizados.

Ejemplo: separar ventas chicas y ventas grandes.

SELECT
  CASE
    WHEN Cantidad * PrecioUnitario >= 1000 THEN 'Venta grande'
    ELSE 'Venta chica'
  END AS TipoVenta,
  COUNT(*) AS CantidadVentas
FROM Ventas
GROUP BY
  CASE
    WHEN Cantidad * PrecioUnitario >= 1000 THEN 'Venta grande'
    ELSE 'Venta chica'
  END;

Este patrón se usa cuando el grupo no existe como columna, pero lo podés construir con una condición.


12. GROUP BY con JOIN

En proyectos reales es muy común agrupar después de unir tablas.

Ejemplo simple con clientes y ventas:

CREATE TABLE Clientes (
  Id INT PRIMARY KEY,
  Nombre VARCHAR(50),
  Ciudad VARCHAR(50)
);

CREATE TABLE VentasPorCliente (
  Id INT PRIMARY KEY,
  ClienteId INT,
  Total DECIMAL(10, 2)
);

Consulta: total vendido por ciudad.

SELECT
  C.Ciudad,
  SUM(V.Total) AS TotalVendido
FROM Clientes C
JOIN VentasPorCliente V
  ON C.Id = V.ClienteId
GROUP BY C.Ciudad;

Primero se relaciona cada venta con su cliente. Después se agrupa por ciudad.


13. GROUP BY con subconsulta o CTE

Cuando una consulta empieza a quedar larga, conviene separar pasos.

WITH TotalesPorCliente AS (
  SELECT
    Cliente,
    SUM(Cantidad * PrecioUnitario) AS TotalGastado
  FROM Ventas
  GROUP BY Cliente
)
SELECT
  Cliente,
  TotalGastado
FROM TotalesPorCliente
WHERE TotalGastado > 1500;

Es lo mismo que usar HAVING, pero a veces se lee más claro porque separa:

  1. Calcular totales.
  2. Filtrar resultados calculados.

14. GROUP BY y DISTINCT no son lo mismo

DISTINCT elimina duplicados. GROUP BY agrupa y permite calcular.

Lista de clientes sin repetir:

SELECT DISTINCT Cliente
FROM Ventas;

Clientes con cantidad de ventas:

SELECT
  Cliente,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente;

Si solo querés ver valores únicos, DISTINCT alcanza. Si necesitás contar, sumar o promediar, usá GROUP BY.


15. GROUP BY con ROLLUP, CUBE y GROUPING SETS

Estas formas son más avanzadas, pero aparecen en reportes.

ROLLUP

ROLLUP agrega subtotales.

SELECT
  Categoria,
  Producto,
  SUM(Cantidad * PrecioUnitario) AS Total
FROM Ventas
GROUP BY ROLLUP (Categoria, Producto);

Puede devolver:

  • Total por categoría y producto.
  • Subtotal por categoría.
  • Total general.

CUBE

CUBE genera más combinaciones de subtotales.

SELECT
  Cliente,
  Categoria,
  SUM(Cantidad * PrecioUnitario) AS Total
FROM Ventas
GROUP BY CUBE (Cliente, Categoria);

Puede servir para reportes donde querés analizar por cliente, por categoría, por ambas cosas y por total general.

GROUPING SETS

GROUPING SETS te deja elegir exactamente qué agrupaciones querés.

SELECT
  Cliente,
  Categoria,
  SUM(Cantidad * PrecioUnitario) AS Total
FROM Ventas
GROUP BY GROUPING SETS (
  (Cliente),
  (Categoria),
  (Cliente, Categoria)
);

Errores comunes con GROUP BY

Error 1: Seleccionar columnas que no están agrupadas

SELECT
  Cliente,
  Producto,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente;

El problema es Producto: si agrupás solo por Cliente, puede haber muchos productos para el mismo cliente. SQL no sabe cuál mostrar.

Solución:

SELECT
  Cliente,
  Producto,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente, Producto;

Error 2: Usar WHERE para filtrar agregados

Incorrecto:

SELECT
  Cliente,
  COUNT(*) AS TotalVentas
FROM Ventas
WHERE COUNT(*) > 1
GROUP BY Cliente;

Correcto:

SELECT
  Cliente,
  COUNT(*) AS TotalVentas
FROM Ventas
GROUP BY Cliente
HAVING COUNT(*) > 1;

Error 3: Olvidar que NULL también agrupa

Si una columna tiene valores NULL, GROUP BY los junta en un mismo grupo.

SELECT
  Categoria,
  COUNT(*) AS Total
FROM Ventas
GROUP BY Categoria;

Si hubiera ventas sin categoría, aparecerían agrupadas como NULL.


Orden recomendado para escribir consultas con GROUP BY

Aunque SQL se ejecuta internamente en otro orden, para aprender conviene escribir así:

SELECT
  columna_grupo,
  COUNT(*) AS total
FROM tabla
WHERE condicion_sobre_filas
GROUP BY columna_grupo
HAVING condicion_sobre_grupos
ORDER BY total DESC;

Y pensarlo así:

PasoPregunta
FROMDe qué tabla salen los datos
WHEREQué filas necesito antes de agrupar
GROUP BYPor qué columna voy a formar grupos
SELECTQué quiero mostrar por cada grupo
HAVINGQué grupos quiero dejar
ORDER BYCómo quiero ordenar el resultado

Resumen rápido

NecesidadConsulta típica
Contar por grupoCOUNT(*) + GROUP BY
Sumar por grupoSUM(columna) + GROUP BY
Filtrar filas antes de agruparWHERE
Filtrar grupos ya calculadosHAVING
Agrupar por más detalleGROUP BY columna1, columna2
Detectar duplicadosGROUP BY campo HAVING COUNT(*) > 1
Crear categorías manualesCASE + GROUP BY
Reportes con subtotalesROLLUP, CUBE, GROUPING SETS

Si entendés bien GROUP BY, después se vuelven mucho más fáciles los reportes, dashboards, ejercicios de entrevistas y consultas reales de trabajo.

En este artículo