CUME_DIST

CUME_DIST calcula la distribución acumulativa de una fila dentro de su partición. Dicho de forma simple, te dice qué proporción de filas tienen un valor menor o igual al de la fila actual. El resultado es un número entre 0 (exclusivo) y 1 (inclusivo), donde 1 indica que la fila tiene el valor más alto (o igual al más alto) del grupo.

La fórmula que MySQL aplica es: número de filas con valor menor o igual al actual dividido entre el número total de filas en la partición. Si un empleado tiene un CUME_DIST de 0.75 ordenando por salario, significa que el 75% de los empleados ganan lo mismo o menos que él. Es una herramienta fundamental en análisis estadístico y de rendimiento.

Sintaxis

CUME_DIST() OVER (
    [PARTITION BY columna1, columna2, ...]
    ORDER BY columna1 [ASC|DESC], columna2 [ASC|DESC], ...
)

CUME_DIST no acepta argumentos. Su resultado depende completamente del ORDER BY dentro de OVER. Sin ORDER BY, todas las filas tendrían el mismo valor (1.0). El PARTITION BY es opcional y reinicia el cálculo para cada grupo.

Comportamiento básico

Para entender cómo funciona la fórmula, observa este ejemplo con 8 empleados ordenados por salario ascendente:

SELECT
    nombre,
    salario,
    CUME_DIST() OVER (ORDER BY salario) AS cume_dist,
    ROUND(CUME_DIST() OVER (ORDER BY salario) * 100, 1) AS percentil
FROM empleados
ORDER BY salario;
nombresalariocume_distpercentil
Pedro Sánchez43000.000.12512.5
Elena Torres45000.000.2525.0
David Moreno48000.000.37537.5
Laura Díaz52000.000.550.0
Marta Ruiz55000.000.62562.5
Ana García58000.000.7575.0
Roberto Fernández65000.000.87587.5
Carlos López72000.001.0100.0

Pedro, con el salario más bajo, tiene un CUME_DIST de 0.125 (1/8): solo el 12.5% de los empleados gana lo mismo o menos que él (él mismo). Laura, en el punto medio, tiene 0.5: el 50% gana lo mismo o menos. Carlos, con el salario más alto, tiene 1.0: todos (el 100%) ganan lo mismo o menos que él.

Comportamiento con empates

Cuando hay valores iguales, CUME_DIST les asigna el mismo valor, porque el mismo porcentaje de filas tiene un valor menor o igual:

SELECT
    nombre,
    salario,
    CUME_DIST() OVER (ORDER BY salario) AS cume_dist
FROM empleados
ORDER BY salario;
nombresalariocume_dist
Pedro Sánchez43000.000.125
David Moreno48000.000.375
Elena Torres48000.000.375
Laura Díaz52000.000.5
Ana García55000.000.75
Marta Ruiz55000.000.75
Roberto Fernández65000.000.875
Carlos López72000.001.0

David y Elena, ambos con 48000, comparten el CUME_DIST de 0.375 porque 3 de 8 empleados (37.5%) ganan 48000 o menos. Ana y Marta comparten 0.75 porque 6 de 8 (75%) ganan 55000 o menos. Nota que se salta del 0.125 al 0.375 (no hay 0.25) porque los empates agrupan las posiciones.

Caso práctico: identificar el top 10% de clientes

Un uso clásico de CUME_DIST es identificar los clientes más valiosos. El top 10% son aquellos cuyo gasto acumulado supera al 90% del resto:

WITH clientes_ranking AS (
    SELECT
        c.nombre,
        SUM(p.total) AS gasto_total,
        CUME_DIST() OVER (ORDER BY SUM(p.total)) AS cume_dist
    FROM clientes c
        JOIN pedidos p ON c.id_cliente = p.id_cliente
    GROUP BY c.id_cliente, c.nombre
)
SELECT
    nombre,
    gasto_total,
    ROUND(cume_dist * 100, 1) AS percentil
FROM clientes_ranking
WHERE cume_dist > 0.90
ORDER BY gasto_total DESC;
nombregasto_totalpercentil
Empresa Textil Norte S.L.25400.00100.0
Distribuciones García22800.0099.6
Restaurante El Molino19500.0099.2
Ferretería López e Hijos18200.0098.8
Consultora Digital ABC16900.0098.4

El filtro cume_dist > 0.90 selecciona solo a los clientes que están por encima del percentil 90, es decir, el 10% con mayor gasto. Estos son los clientes VIP que merecen atención personalizada.

Caso práctico: segmentación por percentiles

Puedes usar CUME_DIST para crear segmentos basados en percentiles, que son más precisos que los cuartiles de NTILE porque respetan los empates:

SELECT
    nombre,
    departamento,
    salario,
    ROUND(CUME_DIST() OVER (ORDER BY salario) * 100, 1) AS percentil,
    CASE
        WHEN CUME_DIST() OVER (ORDER BY salario) <= 0.25 THEN 'Q1 - Bajo'
        WHEN CUME_DIST() OVER (ORDER BY salario) <= 0.50 THEN 'Q2 - Medio-bajo'
        WHEN CUME_DIST() OVER (ORDER BY salario) <= 0.75 THEN 'Q3 - Medio-alto'
        ELSE 'Q4 - Alto'
    END AS segmento
FROM empleados
ORDER BY salario;
nombredepartamentosalariopercentilsegmento
Pedro SánchezVentas43000.0012.5Q1 - Bajo
Elena TorresSoporte45000.0025.0Q1 - Bajo
David MorenoSoporte48000.0037.5Q2 - Medio-bajo
Laura DíazMarketing52000.0050.0Q2 - Medio-bajo
Marta RuizVentas55000.0062.5Q3 - Medio-alto
Ana GarcíaVentas58000.0075.0Q3 - Medio-alto
Roberto FernándezMarketing65000.0087.5Q4 - Alto
Carlos LópezVentas72000.00100.0Q4 - Alto

La diferencia con NTILE(4) es sutil pero importante: NTILE distribuye las filas en grupos de tamaño igual sin considerar los valores, mientras que CUME_DIST calcula la posición relativa real basada en los valores. Con empates, CUME_DIST puede producir segmentos de distinto tamaño.

Caso práctico: análisis de rendimiento por departamento

Para comparar cómo se distribuyen los rendimientos dentro de cada departamento, particiona por departamento:

SELECT
    nombre,
    departamento,
    total_ventas,
    ROUND(CUME_DIST() OVER (
        PARTITION BY departamento
        ORDER BY total_ventas
    ) * 100, 1) AS percentil_depto,
    ROUND(CUME_DIST() OVER (
        ORDER BY total_ventas
    ) * 100, 1) AS percentil_global
FROM empleados
ORDER BY departamento, total_ventas;
nombredepartamentototal_ventaspercentil_deptopercentil_global
Laura DíazMarketing115000.0050.025.0
Roberto FernándezMarketing142000.00100.062.5
Pedro SánchezVentas87000.0025.012.5
Marta RuizVentas128000.0050.037.5
Ana GarcíaVentas142000.0075.062.5
Carlos LópezVentas185000.00100.0100.0

Laura está en el percentil 50 de Marketing (es la peor de 2) pero en el percentil 25 global. Roberto está en el percentil 100 de Marketing (el mejor de su departamento) pero solo en el 62.5 global. Esta doble perspectiva revela que ser el mejor de un departamento pequeño no implica estar entre los mejores a nivel empresa.

Manejo de NULL

Los valores NULL en la columna de orden se tratan como equivalentes entre sí y se ordenan primero en orden ascendente (el comportamiento por defecto de MySQL). Todos los NULL reciben el mismo CUME_DIST:

SELECT
    nombre,
    comision,
    ROUND(CUME_DIST() OVER (ORDER BY comision) * 100, 1) AS percentil
FROM empleados;
nombrecomisionpercentil
Pedro SánchezNULL37.5
Elena TorresNULL37.5
David MorenoNULL37.5
Roberto Fernández3000.0050.0
Marta Ruiz5000.0075.0
Ana García5000.0075.0
Carlos López8000.0087.5
Laura Díaz12000.00100.0

Los tres empleados sin comisión comparten el percentil 37.5 (3 de 8 = 0.375). Si prefieres excluirlos del análisis, filtra con WHERE comision IS NOT NULL.

Combinación con otras funciones

CUME_DIST se combina con funciones de formato y condicionales para crear informes de distribución claros:

SELECT
    nombre,
    salario,
    ROUND(CUME_DIST() OVER (ORDER BY salario) * 100, 0) AS percentil,
    CONCAT(
        'Percentil ',
        ROUND(CUME_DIST() OVER (ORDER BY salario) * 100, 0)
    ) AS etiqueta,
    ROUND(salario - AVG(salario) OVER (), 2) AS vs_promedio,
    CASE
        WHEN CUME_DIST() OVER (ORDER BY salario) >= 0.9 THEN 'Top 10%'
        WHEN CUME_DIST() OVER (ORDER BY salario) >= 0.75 THEN 'Top 25%'
        WHEN CUME_DIST() OVER (ORDER BY salario) >= 0.5 THEN 'Mitad superior'
        ELSE 'Mitad inferior'
    END AS clasificacion
FROM empleados
ORDER BY salario DESC;
nombresalariopercentiletiquetavs_promedioclasificacion
Carlos López72000.00100Percentil 10017250.00Top 10%
Roberto Fernández65000.0088Percentil 8810250.00Top 25%
Ana García58000.0075Percentil 753250.00Top 25%
Marta Ruiz55000.0063Percentil 63250.00Mitad superior
Laura Díaz52000.0050Percentil 50-2750.00Mitad superior
David Moreno48000.0038Percentil 38-6750.00Mitad inferior

El informe combina el percentil exacto, una etiqueta legible, la desviación respecto al promedio y una clasificación por tramos. Cada empleado puede ver exactamente dónde se sitúa en la distribución salarial de la empresa.

En el siguiente artículo veremos PERCENT_RANK para calcular el rango porcentual.

Escrito por Eduardo Lázaro