SUM IF

En el artículo anterior vimos cómo SUM calcula el total de una columna numérica. Ahora vamos un paso más allá: sumar solo los valores que cumplen una condición determinada. Este patrón, conocido como SUM IF, te permite calcular múltiples sumas condicionales en una sola consulta, lo que resulta ideal para construir informes de tipo pivote, comparar períodos y segmentar datos sin recurrir a subconsultas.

Sintaxis

-- Patrón con IF
SUM(IF(condicion, expresion, 0))
 
-- Patrón con CASE (más portable)
SUM(CASE WHEN condicion THEN expresion ELSE 0 END)

Ambos patrones son equivalentes. IF es más conciso y específico de MySQL, mientras que CASE funciona en cualquier base de datos SQL. En ambos casos, cuando la condición no se cumple, la expresión devuelve 0, que al sumarse no afecta al total. También podrías usar NULL en lugar de 0 ya que SUM ignora los NULL, pero usar 0 hace la intención más explícita.

Comportamiento básico

Supongamos que quieres saber cuánto se ha facturado en pedidos completados frente a pedidos cancelados:

SELECT
    SUM(IF(estado = 'completado', total, 0)) AS ingresos_completados,
    SUM(IF(estado = 'cancelado', total, 0)) AS ingresos_cancelados,
    SUM(total) AS ingresos_brutos
FROM pedidos;
ingresos_completadosingresos_canceladosingresos_brutos
178340.5514520.30215680.85

De los 215680.85 euros en pedidos brutos, 178340.55 corresponden a pedidos completados y 14520.30 a cancelados. La diferencia entre los ingresos brutos y la suma de completados más cancelados corresponde a pedidos en otros estados (pendiente, procesando, enviado).

Caso práctico: comparación de ingresos por trimestre

Uno de los usos más potentes de SUM IF es comparar períodos en la misma fila. Veamos los ingresos por categoría desglosados por trimestre:

SELECT
    c.nombre AS categoria,
    SUM(CASE WHEN QUARTER(p.fecha_pedido) = 1
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS Q1,
    SUM(CASE WHEN QUARTER(p.fecha_pedido) = 2
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS Q2,
    SUM(CASE WHEN QUARTER(p.fecha_pedido) = 3
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS Q3,
    SUM(CASE WHEN QUARTER(p.fecha_pedido) = 4
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS Q4
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
WHERE YEAR(p.fecha_pedido) = 2025
    AND p.estado = 'completado'
GROUP BY c.nombre
ORDER BY c.nombre;
categoriaQ1Q2Q3Q4
Accesorios electrónicos5230.406120.806580.206628.90
Deportes2340.002890.501980.003120.00
Hogar1890.302150.601650.402450.80
Portátiles12450.0014230.5013100.0015639.50
Ropa hombre2560.202980.001870.403149.80
Ropa mujer3120.503450.202680.003999.90
Smartphones16890.3018560.4019230.0019549.80

Esta tabla pivote te permite ver de un vistazo las tendencias de cada categoría a lo largo del año. Los Smartphones muestran un crecimiento sostenido trimestre a trimestre, mientras que Deportes tiene una caída en el tercer trimestre que probablemente corresponde al verano.

Caso práctico: informe de ventas actual vs anterior

Otro patrón muy habitual es comparar el año actual con el anterior en la misma consulta:

SELECT
    DATE_FORMAT(p.fecha_pedido, '%m') AS mes,
    SUM(CASE WHEN YEAR(p.fecha_pedido) = 2025
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS ingresos_2025,
    SUM(CASE WHEN YEAR(p.fecha_pedido) = 2024
        THEN dp.cantidad * dp.precio_unitario ELSE 0 END) AS ingresos_2024,
    ROUND(
        (SUM(CASE WHEN YEAR(p.fecha_pedido) = 2025
            THEN dp.cantidad * dp.precio_unitario ELSE 0 END) -
         SUM(CASE WHEN YEAR(p.fecha_pedido) = 2024
            THEN dp.cantidad * dp.precio_unitario ELSE 0 END)) /
        NULLIF(SUM(CASE WHEN YEAR(p.fecha_pedido) = 2024
            THEN dp.cantidad * dp.precio_unitario ELSE 0 END), 0) * 100
    , 1) AS variacion_pct
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
WHERE p.estado = 'completado'
    AND MONTH(p.fecha_pedido) <= 6
GROUP BY DATE_FORMAT(p.fecha_pedido, '%m')
ORDER BY mes;
mesingresos_2025ingresos_2024variacion_pct
0118450.7515680.2017.7
0221320.4018230.5016.9
0325890.2522150.8016.9
0419870.6017450.3013.9
0524150.8020890.4015.6
0629560.5024560.0020.4

La columna variacion_pct calcula el porcentaje de crecimiento interanual. NULLIF en el denominador evita la división por cero en caso de que un mes no tuviera ventas en 2024.

Caso práctico: tabla pivote de métodos de pago

SUM IF es perfecto para crear tablas pivote que transforman filas en columnas:

SELECT
    cl.ciudad,
    SUM(CASE WHEN p.metodo_pago = 'tarjeta' THEN p.total ELSE 0 END) AS tarjeta,
    SUM(CASE WHEN p.metodo_pago = 'transferencia' THEN p.total ELSE 0 END) AS transferencia,
    SUM(CASE WHEN p.metodo_pago = 'paypal' THEN p.total ELSE 0 END) AS paypal,
    SUM(p.total) AS total_ciudad
FROM pedidos p
JOIN clientes cl ON p.cliente_id = cl.id
WHERE p.estado = 'completado'
GROUP BY cl.ciudad
ORDER BY total_ciudad DESC
LIMIT 5;
ciudadtarjetatransferenciapaypaltotal_ciudad
Madrid28450.3012560.208920.0049930.50
Barcelona22340.809870.407650.2039861.40
Valencia14560.506230.004890.3025680.80
Sevilla11230.205120.603450.0019800.80
Bilbao8920.404230.502890.2016041.10

Cada fila muestra cómo se distribuyen los pagos en cada ciudad. Este tipo de informe sería muy difícil de generar sin el patrón SUM IF.

Manejo de NULL

Cuando la expresión dentro de SUM IF puede ser NULL, hay que tener cuidado. Si usas ELSE 0 en el CASE, los valores que no cumplen la condición se convierten en cero. Pero si la propia expresión que sumas es NULL, el resultado de toda la rama THEN será NULL:

SELECT
    SUM(CASE WHEN categoria_id = 1
        THEN descuento ELSE 0 END) AS descuento_electronica
FROM productos;

Si algún producto de electrónica tiene descuento como NULL, esa fila aportará NULL a la suma, que será ignorado por SUM. Esto generalmente es el comportamiento deseado, pero si quieres contar los NULL como cero, usa IFNULL:

SELECT
    SUM(CASE WHEN categoria_id = 1
        THEN IFNULL(descuento, 0) ELSE 0 END) AS descuento_electronica
FROM productos;

Combinación con otras funciones

Puedes anidar SUM IF dentro de otras expresiones para cálculos más sofisticados:

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_netos,
    ROUND(
        SUM(CASE WHEN p.estado = 'completado'
            THEN dp.cantidad * dp.precio_unitario ELSE 0 END) /
        SUM(dp.cantidad * dp.precio_unitario) * 100
    , 1) AS tasa_conversion
FROM pedidos p
JOIN detalle_pedidos dp ON p.id = dp.pedido_id
JOIN productos pr ON dp.producto_id = pr.id
JOIN categorias c ON pr.categoria_id = c.id
GROUP BY c.nombre
HAVING SUM(dp.cantidad * dp.precio_unitario) > 10000
ORDER BY tasa_conversion DESC;
categoriaingresos_totalesingresos_netostasa_conversion
Accesorios electrónicos28750.4024560.3085.4
Smartphones87450.2574230.5084.9
Portátiles65320.8055420.0084.8
Ropa hombre12450.9010560.4084.8
Ropa mujer15680.2013250.6084.5

La tasa de conversión muestra qué porcentaje de los ingresos brutos se materializó en ventas completadas. Es una métrica valiosa para entender la salud de cada línea de producto.

En el siguiente artículo veremos AVG para calcular promedios.

Escrito por Eduardo Lázaro