Día 3 – Funciones y agregaciones: COUNT, SUM, AVG, GROUP BY, HAVING
Hoy vas a dar un salto importante. Hasta ahora has trabajado con filas individuales: seleccionar, filtrar, ordenar. Pero en la realidad muchas veces necesitas resumir información: contar cuántos registros hay, calcular totales, promedios, máximos…
Esto es lo que hacen las funciones de agregación. Te permiten pasar de «mostrar datos» a «analizar datos».
Escenario del día
Imagina que tu jefe te pide un informe urgente:
- ¿Cuántos pedidos tenemos en total?
- ¿Cuál es el valor promedio de un pedido?
- ¿Qué usuario ha gastado más dinero?
- ¿Cuántos productos vendemos por categoría?
Sin SQL tendrías que exportar todo a Excel y hacer las cuentas manualmente. Con lo que aprenderás hoy, lo resuelves en minutos.
1. Funciones de agregación básicas
Las funciones de agregación toman múltiples filas y devuelven un solo valor. Son perfectas para obtener estadísticas rápidas.
COUNT() – Contar registros
La más usada de todas. Cuenta cuántas filas hay.
-- ¿Cuántos usuarios tenemos?
SELECT COUNT(*) AS total_usuarios
FROM usuarios;
Resultado: Un número, por ejemplo 10.
-- ¿Cuántos productos hay en la categoría Electronica?
SELECT COUNT(*) AS productos_electronica
FROM productos
WHERE categoria = 'Electronica';
Diferencia importante:
COUNT(*)→ Cuenta todas las filas (incluye NULLs)COUNT(columna)→ Cuenta solo filas donde esa columna NO es NULL
-- Contar productos que tienen stock
SELECT COUNT(stock) AS productos_con_stock
FROM productos
WHERE stock > 0;
SUM() – Sumar valores
Suma todos los valores de una columna numérica.
-- ¿Cuánto vale todo nuestro inventario?
SELECT SUM(precio * stock) AS valor_total_inventario
FROM productos;
-- Total de ventas (suma de todos los pedidos)
SELECT SUM(total) AS ventas_totales
FROM pedidos;
⚠️ Importante: SUM() solo funciona con columnas numéricas. No puedes sumar textos ni fechas.
AVG() – Calcular promedio
Calcula la media aritmética de una columna.
-- ¿Cuál es el precio promedio de nuestros productos?
SELECT AVG(precio) AS precio_promedio
FROM productos;
El resultado puede tener muchos decimales. Usa ROUND() para redondear:
-- Precio promedio con 2 decimales
SELECT ROUND(AVG(precio), 2) AS precio_promedio
FROM productos;
-- Valor promedio de un pedido
SELECT ROUND(AVG(total), 2) AS ticket_promedio
FROM pedidos;
MIN() y MAX() – Valores mínimo y máximo
Encuentra el valor más pequeño o más grande de una columna.
-- ¿Cuál es el producto más barato y el más caro?
SELECT
MIN(precio) AS producto_mas_barato,
MAX(precio) AS producto_mas_caro
FROM productos;
-- Primer y último registro de usuario
SELECT
MIN(fecha_registro) AS primer_usuario,
MAX(fecha_registro) AS ultimo_usuario
FROM usuarios;
Combinar varias agregaciones
Puedes usar varias funciones en la misma consulta:
-- Estadísticas completas de productos
SELECT
COUNT(*) AS total_productos,
ROUND(AVG(precio), 2) AS precio_promedio,
MIN(precio) AS precio_minimo,
MAX(precio) AS precio_maximo,
SUM(stock) AS unidades_totales
FROM productos;
2. GROUP BY – Agrupar por categorías
Aquí es donde todo se vuelve interesante. GROUP BY te permite agrupar filas que tienen algo en común y aplicar funciones de agregación a cada grupo.
Concepto básico
Sin GROUP BY:
-- Esto te da UN solo número (total de productos)
SELECT COUNT(*) FROM productos;
Con GROUP BY:
-- Esto te da UN número POR CADA categoría
SELECT
categoria,
COUNT(*) AS cantidad
FROM productos
GROUP BY categoria;
¿Cómo funciona?
- SQL agrupa todas las filas que tienen la misma
categoria - Para cada grupo, aplica
COUNT(*) - Te devuelve una fila por cada grupo
Resultado:
categoria | cantidad
---------------|----------
Electronica | 4
Accesorios | 4
Muebles | 1
Almacenamiento | 1
Ejemplos prácticos
-- ¿Cuántos productos tenemos de cada categoría?
SELECT
categoria,
COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
ORDER BY total_productos DESC;
-- Valor promedio por categoría
SELECT
categoria,
ROUND(AVG(precio), 2) AS precio_promedio,
COUNT(*) AS cantidad_productos
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;
-- Usuarios por país
SELECT
pais,
COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
ORDER BY total_usuarios DESC;
Múltiples agregaciones por grupo
Puedes calcular varias estadísticas a la vez para cada grupo:
-- Análisis completo por categoría
SELECT
categoria,
COUNT(*) AS productos,
ROUND(AVG(precio), 2) AS precio_promedio,
MIN(precio) AS precio_min,
MAX(precio) AS precio_max,
SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;
Agrupar por varias columnas
Puedes agrupar por más de una columna:
-- Usuarios por país y año de registro
SELECT
pais,
EXTRACT(YEAR FROM fecha_registro) AS año,
COUNT(*) AS registros
FROM usuarios
GROUP BY pais, EXTRACT(YEAR FROM fecha_registro)
ORDER BY pais, año;
Esto agrupa primero por país, y dentro de cada país, por año.
3. HAVING – Filtrar grupos
Ya conoces WHERE para filtrar filas individuales. Pero ¿cómo filtras grupos después de agrupar? Ahí entra HAVING.
Diferencia clave: WHERE vs HAVING
WHERE → Filtra filas ANTES de agrupar HAVING → Filtra grupos DESPUÉS de agrupar
-- ❌ ESTO NO FUNCIONA (no puedes usar COUNT en WHERE)
SELECT categoria, COUNT(*) AS total
FROM productos
WHERE COUNT(*) > 3 -- ERROR
GROUP BY categoria;
-- ✅ CORRECTO (usa HAVING para filtrar grupos)
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;
Ejemplos con HAVING
-- Categorías con más de 3 productos
SELECT
categoria,
COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3
ORDER BY total_productos DESC;
-- Países con más de 2 usuarios
SELECT
pais,
COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
HAVING COUNT(*) > 2;
-- Categorías donde el precio promedio supera los 200€
SELECT
categoria,
ROUND(AVG(precio), 2) AS precio_promedio,
COUNT(*) AS productos
FROM productos
GROUP BY categoria
HAVING AVG(precio) > 200;
Combinar WHERE y HAVING
Puedes usar ambos en la misma consulta:
-- Categorías con más de 2 productos en stock
-- que cuestan más de 100€
SELECT
categoria,
COUNT(*) AS productos_caros,
ROUND(AVG(precio), 2) AS precio_promedio
FROM productos
WHERE precio > 100 -- Filtro ANTES de agrupar
GROUP BY categoria
HAVING COUNT(*) > 2 -- Filtro DESPUÉS de agrupar
ORDER BY precio_promedio DESC;
Orden de ejecución:
FROM→ Qué tabla usarWHERE→ Filtra filas individualesGROUP BY→ Agrupa filasHAVING→ Filtra gruposSELECT→ Calcula las columnasORDER BY→ Ordena el resultado
4. Casos prácticos del día a día
Ahora que conoces las herramientas, veamos consultas útiles para informes reales.
Top categorías por ventas
-- ¿Qué categorías generan más ingresos?
SELECT
p.categoria,
COUNT(dp.id) AS unidades_vendidas,
ROUND(SUM(dp.precio_unitario * dp.cantidad), 2) AS ingresos_totales
FROM detalle_pedidos dp
JOIN productos p ON dp.producto_id = p.id
GROUP BY p.categoria
ORDER BY ingresos_totales DESC;
Usuarios más activos
-- Usuarios con más pedidos
SELECT
u.nombre,
u.email,
COUNT(pe.id) AS total_pedidos,
ROUND(SUM(pe.total), 2) AS gasto_total
FROM usuarios u
JOIN pedidos pe ON u.id = pe.usuario_id
GROUP BY u.id, u.nombre, u.email
ORDER BY total_pedidos DESC;
Productos más vendidos
-- Top 5 productos por cantidad vendida
SELECT
p.nombre,
SUM(dp.cantidad) AS unidades_vendidas,
ROUND(SUM(dp.precio_unitario * dp.cantidad), 2) AS ingresos
FROM productos p
JOIN detalle_pedidos dp ON p.id = dp.producto_id
GROUP BY p.id, p.nombre
ORDER BY unidades_vendidas DESC
LIMIT 5;
Análisis de inventario
-- Productos con stock bajo (menos de 20 unidades)
SELECT
categoria,
COUNT(*) AS productos_stock_bajo,
SUM(stock) AS unidades_totales
FROM productos
WHERE stock < 20
GROUP BY categoria;
5. Errores comunes (y cómo evitarlos)
Error 1: Usar columnas sin agrupar
-- ❌ ESTO DA ERROR
SELECT categoria, nombre, COUNT(*)
FROM productos
GROUP BY categoria;
Problema: nombre no está en GROUP BY ni es una agregación.
Solución: Solo puedes seleccionar columnas que están en GROUP BY o son funciones de agregación.
-- ✅ CORRECTO
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria;
Error 2: Olvidar el alias
-- Poco claro
SELECT COUNT(*) FROM productos;
-- ✅ Mejor
SELECT COUNT(*) AS total_productos FROM productos;
Los alias hacen tu consulta más legible.
Error 3: Confundir WHERE con HAVING
-- ❌ WHERE no puede usar funciones de agregación
SELECT categoria, COUNT(*) AS total
FROM productos
WHERE COUNT(*) > 3
GROUP BY categoria;
-- ✅ Usa HAVING
SELECT categoria, COUNT(*) AS total
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;
6. Ejercicios prácticos
Intenta resolver estos ejercicios antes de ver las soluciones.
- Cuenta cuántos usuarios hay por país
- Calcula el precio promedio de productos por categoría (con 2 decimales)
- Muestra solo las categorías que tienen más de 3 productos
- Encuentra el total de unidades en stock por categoría
- Muestra los usuarios que han hecho más de 1 pedido
- Calcula cuántos productos hay en cada rango de precio:
- Baratos (menos de 100€)
- Medios (100€ – 300€)
- Caros (más de 300€)
- Encuentra las categorías donde el stock total supera las 30 unidades
- Calcula el ticket promedio (valor medio de los pedidos)
💡 Consejo: Empieza siempre por SELECT y FROM, luego añade GROUP BY, y finalmente HAVING si lo necesitas.
7. Soluciones
Solución Ejercicio 1
SELECT
pais,
COUNT(*) AS total_usuarios
FROM usuarios
GROUP BY pais
ORDER BY total_usuarios DESC;
Solución Ejercicio 2
SELECT
categoria,
ROUND(AVG(precio), 2) AS precio_promedio
FROM productos
GROUP BY categoria
ORDER BY precio_promedio DESC;
Solución Ejercicio 3
SELECT
categoria,
COUNT(*) AS total_productos
FROM productos
GROUP BY categoria
HAVING COUNT(*) > 3;
Solución Ejercicio 4
SELECT
categoria,
SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
ORDER BY stock_total DESC;
Solución Ejercicio 5
SELECT
u.nombre,
u.email,
COUNT(p.id) AS total_pedidos
FROM usuarios u
JOIN pedidos p ON u.id = p.usuario_id
GROUP BY u.id, u.nombre, u.email
HAVING COUNT(p.id) > 1
ORDER BY total_pedidos DESC;
Solución Ejercicio 6
SELECT
CASE
WHEN precio < 100 THEN 'Barato'
WHEN precio BETWEEN 100 AND 300 THEN 'Medio'
ELSE 'Caro'
END AS rango_precio,
COUNT(*) AS cantidad_productos
FROM productos
GROUP BY rango_precio
ORDER BY
CASE rango_precio
WHEN 'Barato' THEN 1
WHEN 'Medio' THEN 2
WHEN 'Caro' THEN 3
END;
Solución Ejercicio 7
SELECT
categoria,
SUM(stock) AS stock_total
FROM productos
GROUP BY categoria
HAVING SUM(stock) > 30;
Solución Ejercicio 8
SELECT
ROUND(AVG(total), 2) AS ticket_promedio
FROM pedidos;
Resumen del Día 3
¡Excelente trabajo! Ahora dominas las herramientas para analizar datos y generar informes.
Has aprendido:
✅ Funciones de agregación: COUNT(), SUM(), AVG(), MIN(), MAX()
✅ Agrupar datos: GROUP BY para análisis por categorías
✅ Filtrar grupos: HAVING para condiciones sobre agregaciones
✅ Combinar todo: Consultas complejas para informes reales
✅ Orden de ejecución: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
Estas técnicas te permiten pasar de «mostrar datos» a «analizar datos». Son fundamentales para generar reportes, dashboards y KPIs.
Siguiente paso
En el Día 4 aprenderás JOINs: cómo relacionar varias tablas para obtener información completa. Por ejemplo, unir usuarios con pedidos para ver quién compró qué.
Hasta ahora has trabajado con una sola tabla a la vez. Mañana verás cómo combinar información de múltiples tablas, que es donde SQL realmente brilla.
¡Nos vemos en el Día 4!
