SUM
La función SUM calcula la suma total de una columna numérica o de una expresión. Es la función de agregación que usarás cada vez que necesites calcular totales: ingresos, costes, cantidades vendidas, horas trabajadas o cualquier otro valor acumulable. Al igual que las demás funciones de agregación, SUM ignora los valores NULL y puede combinarse con GROUP BY para obtener sumas por grupo.
Sintaxis
SUM(expresion)
SUM(ALL expresion)
SUM(DISTINCT expresion)SUM(expresion) suma todos los valores no nulos. SUM(ALL expresion) es equivalente y ALL es el comportamiento por defecto. SUM(DISTINCT expresion) suma solo los valores únicos, descartando duplicados antes de sumar. Si todas las filas del grupo contienen NULL, el resultado es NULL, no cero.
Comportamiento básico
La forma más simple es sumar una columna numérica completa:
SELECT SUM(stock) AS stock_total
FROM productos;| stock_total |
|---|
| 4350 |
Hay 4350 unidades en total entre todos los productos del inventario. MySQL recorrió todas las filas de la tabla, tomó el valor de la columna stock de cada una y los sumó.
Para sumar el valor total del inventario necesitas una expresión que multiplique precio por stock:
SELECT
SUM(precio * stock) AS valor_inventario_total
FROM productos;| valor_inventario_total |
|---|
| 845230.50 |
SUM acepta cualquier expresión que devuelva un número. En este caso, precio * stock se evalúa fila por fila y luego se suman todos los resultados.
Caso práctico: ingresos por pedido
Para calcular los ingresos de cada pedido a partir de las líneas de detalle:
SELECT
dp.pedido_id,
SUM(dp.cantidad * dp.precio_unitario) AS total_pedido
FROM detalle_pedidos dp
GROUP BY dp.pedido_id
ORDER BY total_pedido DESC
LIMIT 5;| pedido_id | total_pedido |
|---|---|
| 142 | 3847.96 |
| 89 | 3245.00 |
| 215 | 2998.97 |
| 56 | 2549.99 |
| 301 | 2199.00 |
Cada pedido puede tener varias líneas de detalle (diferentes productos). SUM agrupa las líneas de cada pedido y calcula el total. Este es uno de los patrones más comunes en bases de datos de comercio electrónico.
Caso práctico: ingresos mensuales con GROUP BY
Para generar un informe de ingresos mensuales:
SELECT
DATE_FORMAT(p.fecha_pedido, '%Y-%m') AS mes,
COUNT(DISTINCT p.id) AS num_pedidos,
SUM(dp.cantidad * dp.precio_unitario) AS ingresos,
ROUND(SUM(dp.cantidad * dp.precio_unitario) / COUNT(DISTINCT p.id), 2) AS ticket_medio
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
WHERE p.estado = 'completado'
AND YEAR(p.fecha_pedido) = 2025
GROUP BY DATE_FORMAT(p.fecha_pedido, '%Y-%m')
ORDER BY mes;| mes | num_pedidos | ingresos | ticket_medio |
|---|---|---|---|
| 2025-01 | 20 | 18450.75 | 922.54 |
| 2025-02 | 23 | 21320.40 | 927.00 |
| 2025-03 | 27 | 25890.25 | 958.90 |
| 2025-04 | 21 | 19870.60 | 946.22 |
| 2025-05 | 25 | 24150.80 | 966.03 |
| 2025-06 | 30 | 29560.50 | 985.35 |
El ticket medio se calcula dividiendo los ingresos totales entre el número de pedidos distintos de cada mes. Observa que el ticket medio ha ido creciendo mes a mes.
Caso práctico: SUM con CASE para sumas condicionales
Puedes usar SUM con expresiones CASE para sumar diferentes cosas en la misma consulta. Esto es útil para crear informes que comparan categorías:
SELECT
c.nombre AS categoria,
SUM(dp.cantidad * dp.precio_unitario) AS ingresos_totales,
SUM(CASE WHEN p.estado = 'completado'
THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS ingresos_confirmados,
SUM(CASE WHEN p.estado = 'cancelado'
THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS ingresos_perdidos
FROM detalle_pedidos dp
JOIN pedidos p ON dp.pedido_id = p.id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY c.nombre
ORDER BY ingresos_totales DESC;| categoria | ingresos_totales | ingresos_confirmados | ingresos_perdidos |
|---|---|---|---|
| Smartphones | 87450.25 | 74230.50 | 5680.00 |
| Portátiles | 65320.80 | 55420.00 | 4250.00 |
| Accesorios electrónicos | 28750.40 | 24560.30 | 1890.00 |
| Ropa mujer | 15680.20 | 13250.60 | 980.00 |
| Ropa hombre | 12450.90 | 10560.40 | 750.00 |
Con una sola consulta obtienes los ingresos totales, los confirmados y los perdidos por cancelación. El ELSE 0 en el CASE es importante: sin él, las filas que no cumplen la condición aportarían NULL en vez de cero, y aunque SUM ignora los NULL, ser explícito hace la consulta más legible.
Manejo de NULL
SUM ignora completamente los valores NULL. Si sumas una columna donde algunas filas tienen NULL, esos valores se omiten sin afectar al resultado:
SELECT
SUM(descuento) AS suma_descuentos,
COUNT(*) AS total_productos,
COUNT(descuento) AS productos_con_descuento
FROM productos;| suma_descuentos | total_productos | productos_con_descuento |
|---|---|---|
| 156.50 | 50 | 12 |
La suma de 156.50 corresponde solo a los 12 productos que tienen descuento. Los 38 productos donde descuento es NULL se ignoran. Si quieres tratar los NULL como cero en la suma, puedes usar IFNULL:
SELECT SUM(IFNULL(descuento, 0)) AS suma_descuentos
FROM productos;En este caso el resultado sería el mismo porque SUM ya ignora los NULL, pero IFNULL es útil dentro de expresiones más complejas donde un NULL podría propagar y anular todo el cálculo.
Un caso importante: si todas las filas del grupo tienen NULL, SUM devuelve NULL, no cero. Esto puede causar problemas en la aplicación si no lo manejas:
SELECT COALESCE(SUM(descuento), 0) AS total_descuento
FROM productos
WHERE categoria_id = 99;Si no existe la categoría 99 o ningún producto en ella tiene descuento, COALESCE convierte el NULL en cero.
Combinación con otras funciones
SUM se combina frecuentemente con HAVING para filtrar grupos según su total:
SELECT
cl.nombre,
cl.apellidos,
SUM(dp.cantidad * dp.precio_unitario) AS gasto_total
FROM clientes cl
JOIN pedidos p ON cl.id = p.cliente_id
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
WHERE p.estado = 'completado'
GROUP BY cl.id, cl.nombre, cl.apellidos
HAVING SUM(dp.cantidad * dp.precio_unitario) > 5000
ORDER BY gasto_total DESC;| nombre | apellidos | gasto_total |
|---|---|---|
| María | García López | 8920.45 |
| Carlos | Rodríguez Martín | 7650.30 |
| Ana | Martínez Ruiz | 6430.75 |
| Pedro | Sánchez Gómez | 5890.20 |
| Laura | Fernández Díaz | 5230.60 |
Esta consulta identifica a los clientes VIP que han gastado más de 5000 euros. HAVING filtra después de la agrupación, permitiendo usar funciones de agregación como criterio de filtro.
SUM(DISTINCT) es menos frecuente pero tiene sus usos. Suma solo los valores únicos:
SELECT
SUM(precio) AS suma_todos,
SUM(DISTINCT precio) AS suma_precios_unicos
FROM productos;Esto puede ser útil si tienes productos con el mismo precio y quieres sumar cada precio una sola vez.
Practica con SUM
Usa el editor para sumar valores numéricos:
En el siguiente artículo veremos SUM IF para sumas condicionales.
Escrito por Eduardo Lázaro
