PERCENT_RANK

PERCENT_RANK calcula el rango porcentual de cada fila dentro de su partición, devolviendo un valor entre 0 y 1. La primera fila siempre recibe 0 y la última recibe 1, distribuyendo las demás proporcionalmente entre ambos extremos. Es similar a CUME_DIST pero usa una fórmula diferente que resulta más intuitiva para ciertos análisis, especialmente cuando necesitas saber qué porcentaje de valores está estrictamente por debajo de la fila actual.

La fórmula que MySQL aplica es: (rango de la fila - 1) dividido entre (número total de filas en la partición - 1). Si hay solo una fila, el resultado es 0. Esta fórmula garantiza que el primer valor sea siempre 0 y el último siempre 1, creando una escala uniforme.

Sintaxis

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

Al igual que CUME_DIST, PERCENT_RANK no acepta argumentos y su resultado depende del ORDER BY dentro de OVER. Sin ORDER BY, todas las filas obtienen 0.

Comportamiento básico

Para ver cómo la fórmula produce los valores, observa este ejemplo con 8 empleados:

SELECT
    nombre,
    salario,
    RANK() OVER (ORDER BY salario) AS rango,
    ROUND(PERCENT_RANK() OVER (ORDER BY salario), 4) AS pct_rank,
    ROUND(PERCENT_RANK() OVER (ORDER BY salario) * 100, 1) AS percentil
FROM empleados
ORDER BY salario;
nombresalariorangopct_rankpercentil
Pedro Sánchez43000.0010.00000.0
Elena Torres45000.0020.142914.3
David Moreno48000.0030.285728.6
Laura Díaz52000.0040.428642.9
Marta Ruiz55000.0050.571457.1
Ana García58000.0060.714371.4
Roberto Fernández65000.0070.857185.7
Carlos López72000.0081.0000100.0

La fórmula (rango - 1) / (total - 1) produce: Pedro = (1-1)/(8-1) = 0, Elena = (2-1)/7 = 0.1429, y así sucesivamente hasta Carlos = (8-1)/7 = 1.0. El resultado se distribuye uniformemente entre 0 y 1.

Comportamiento con empates

Cuando hay empates, las filas empatadas comparten el mismo rango y por tanto el mismo PERCENT_RANK:

SELECT
    nombre,
    salario,
    RANK() OVER (ORDER BY salario) AS rango,
    ROUND(PERCENT_RANK() OVER (ORDER BY salario), 4) AS pct_rank
FROM empleados
ORDER BY salario;
nombresalariorangopct_rank
Pedro Sánchez43000.0010.0000
David Moreno48000.0020.1429
Elena Torres48000.0020.1429
Laura Díaz52000.0040.4286
Ana García55000.0050.5714
Marta Ruiz55000.0050.5714
Roberto Fernández65000.0070.8571
Carlos López72000.0081.0000

David y Elena comparten el rango 2, así que ambos tienen PERCENT_RANK = (2-1)/7 = 0.1429. El siguiente rango es 4 (salto por el empate), que produce (4-1)/7 = 0.4286. Los empates crean saltos en la distribución pero el primer y último valor siempre son 0 y 1 respectivamente.

Diferencia con CUME_DIST

La diferencia entre PERCENT_RANK y CUME_DIST es sutil pero importante. Comparémoslas lado a lado:

SELECT
    nombre,
    salario,
    ROUND(PERCENT_RANK() OVER (ORDER BY salario) * 100, 1) AS pct_rank,
    ROUND(CUME_DIST() OVER (ORDER BY salario) * 100, 1) AS cume_dist
FROM empleados
ORDER BY salario;
nombresalariopct_rankcume_dist
Pedro Sánchez43000.000.012.5
Elena Torres45000.0014.325.0
David Moreno48000.0028.637.5
Laura Díaz52000.0042.950.0
Marta Ruiz55000.0057.162.5
Ana García58000.0071.475.0
Roberto Fernández65000.0085.787.5
Carlos López72000.00100.0100.0

Las diferencias clave son tres. Primera, PERCENT_RANK empieza en 0 mientras que CUME_DIST nunca es 0 (su valor mínimo es 1/N). Segunda, la fórmula de PERCENT_RANK es (rango-1)/(total-1), mientras que CUME_DIST usa filas_menores_o_iguales/total. Tercera, PERCENT_RANK responde "qué porcentaje de valores están estrictamente por debajo", mientras que CUME_DIST responde "qué porcentaje de valores están por debajo o son iguales".

La elección entre una y otra depende de la pregunta. Si quieres saber "el 75% de los empleados ganan menos o igual que Ana", usa CUME_DIST. Si quieres saber "Ana supera al 71.4% de los empleados", usa PERCENT_RANK.

Caso práctico: distribución salarial de la empresa

PERCENT_RANK es ideal para visualizar cómo se distribuyen los salarios en toda la organización y detectar desigualdades:

SELECT
    nombre,
    departamento,
    salario,
    ROUND(PERCENT_RANK() OVER (ORDER BY salario) * 100, 1) AS percentil_empresa,
    ROUND(PERCENT_RANK() OVER (
        PARTITION BY departamento
        ORDER BY salario
    ) * 100, 1) AS percentil_depto
FROM empleados
ORDER BY salario;
nombredepartamentosalariopercentil_empresapercentil_depto
Pedro SánchezVentas43000.000.00.0
Elena TorresSoporte45000.0014.30.0
David MorenoSoporte48000.0028.6100.0
Laura DíazMarketing52000.0042.90.0
Marta RuizVentas55000.0057.133.3
Ana GarcíaVentas58000.0071.466.7
Roberto FernándezMarketing65000.0085.7100.0
Carlos LópezVentas72000.00100.0100.0

Elena está en el percentil 0 dentro de Soporte (es la peor pagada de su departamento) pero en el 14.3 de la empresa. David, con solo 3000 más de salario, está en el percentil 100 de Soporte (el mejor de su departamento de 2 personas) pero solo en el 28.6 global. Esta comparación revela que el "mejor pagado" de Soporte gana menos que la mediana de la empresa.

Caso práctico: clasificación por percentiles

Los percentiles permiten crear clasificaciones estándar como las que se usan en evaluaciones educativas:

SELECT
    nombre,
    nota_final,
    ROUND(PERCENT_RANK() OVER (ORDER BY nota_final) * 100, 0) AS percentil,
    CASE
        WHEN PERCENT_RANK() OVER (ORDER BY nota_final) >= 0.90 THEN 'Sobresaliente'
        WHEN PERCENT_RANK() OVER (ORDER BY nota_final) >= 0.70 THEN 'Notable'
        WHEN PERCENT_RANK() OVER (ORDER BY nota_final) >= 0.50 THEN 'Bien'
        WHEN PERCENT_RANK() OVER (ORDER BY nota_final) >= 0.30 THEN 'Suficiente'
        ELSE 'Insuficiente'
    END AS calificacion
FROM calificaciones
WHERE id_asignatura = 101
ORDER BY nota_final DESC;
nombrenota_finalpercentilcalificacion
Lucía Martín9.80100Sobresaliente
Diego Navarro9.5090Sobresaliente
Sara Romero8.7580Notable
Pablo Gil8.2070Notable
Irene Molina7.5060Bien
Adrián Serrano7.1050Bien
Marcos Vidal6.8040Suficiente
Clara Jiménez6.2030Suficiente
Hugo Prieto5.5020Insuficiente
Nerea Castro4.8010Insuficiente
Álvaro Peña3.900Insuficiente

Los percentiles asignan automáticamente las calificaciones según la distribución real de las notas, no según umbrales fijos. Si todos los estudiantes obtienen notas altas, el percentil 30 podría ser un 7.5, mientras que en un grupo con notas bajas podría ser un 4.0.

Caso práctico: análisis de precios en el mercado

Para entender la posición competitiva de tus productos, PERCENT_RANK muestra dónde se sitúa cada precio dentro de la categoría:

SELECT
    nombre,
    categoria,
    precio,
    ROUND(PERCENT_RANK() OVER (
        PARTITION BY categoria
        ORDER BY precio
    ) * 100, 1) AS percentil_precio,
    CASE
        WHEN PERCENT_RANK() OVER (PARTITION BY categoria ORDER BY precio) >= 0.75
            THEN 'Premium'
        WHEN PERCENT_RANK() OVER (PARTITION BY categoria ORDER BY precio) >= 0.25
            THEN 'Rango medio'
        ELSE 'Económico'
    END AS posicionamiento
FROM productos
WHERE activo = 1
ORDER BY categoria, precio;
nombrecategoriapreciopercentil_precioposicionamiento
Ratón inalámbricoElectrónica29.900.0Económico
Teclado mecánico RGBElectrónica129.0033.3Rango medio
Auriculares ProElectrónica189.0066.7Rango medio
Monitor 27" 4KElectrónica349.00100.0Premium
Camiseta algodónRopa25.000.0Económico
Pantalón chinoRopa49.9050.0Rango medio
Abrigo lana italianoRopa245.00100.0Premium

El teclado mecánico está en el percentil 33 de Electrónica, lo que indica que un tercio de los productos de la categoría son más baratos. El monitor, en el percentil 100, es el más caro de su categoría.

Manejo de NULL

PERCENT_RANK trata los valores NULL de la misma forma que RANK: se agrupan juntos y reciben el mismo rango. En orden ascendente, los NULL aparecen primero y reciben el rango 1, que produce PERCENT_RANK = 0:

SELECT
    nombre,
    comision,
    ROUND(PERCENT_RANK() OVER (ORDER BY comision) * 100, 1) AS pct_rank
FROM empleados;
nombrecomisionpct_rank
Pedro SánchezNULL0.0
Elena TorresNULL0.0
David MorenoNULL0.0
Roberto Fernández3000.0042.9
Marta Ruiz5000.0057.1

Los tres empleados sin comisión comparten el rango 1 y por tanto PERCENT_RANK = 0. El primer empleado con comisión (Roberto) salta al 42.9% porque 3 de los 7 empleados están por debajo de él. Si necesitas excluir los NULL del cálculo, filtra antes con WHERE comision IS NOT NULL.

Combinación con otras funciones

PERCENT_RANK se combina con funciones de agregación y formato para crear informes de distribución detallados:

SELECT
    nombre,
    departamento,
    salario,
    ROUND(PERCENT_RANK() OVER w * 100, 1) AS percentil,
    ROUND(AVG(salario) OVER (), 2) AS media_empresa,
    salario - ROUND(AVG(salario) OVER (), 2) AS vs_media,
    CONCAT(
        ROUND(PERCENT_RANK() OVER w * 100, 0),
        '% de empleados ganan menos'
    ) AS interpretacion
FROM empleados
WINDOW w AS (ORDER BY salario)
ORDER BY salario DESC;
nombredepartamentosalariopercentilmedia_empresavs_mediainterpretacion
Carlos LópezVentas72000.00100.054750.0017250.00100% de empleados ganan menos
Roberto FernándezMarketing65000.0085.754750.0010250.0086% de empleados ganan menos
Ana GarcíaVentas58000.0071.454750.003250.0071% de empleados ganan menos
Marta RuizVentas55000.0057.154750.00250.0057% de empleados ganan menos
Laura DíazMarketing52000.0042.954750.00-2750.0043% de empleados ganan menos
David MorenoSoporte48000.0028.654750.00-6750.0029% de empleados ganan menos

La columna de interpretación traduce el número abstracto del percentil en una frase que cualquier persona puede entender: "el 86% de los empleados ganan menos que Roberto". Combinando esto con la desviación respecto a la media, el informe ofrece una visión completa de la posición relativa de cada empleado.

En la siguiente sección veremos las funciones de control de flujo como CASE, IF y COALESCE.

Escrito por Eduardo Lázaro