2009-06-29 34 views
17

Tengo una gran tabla de datos de medición en MySQL y necesito calcular el rango percentil para todos y cada uno de estos valores. Oracle parece tener una función llamada percent_rank pero no puedo encontrar nada similar para MySQL. Claro que podría simplemente usar fuerza bruta en Python, que utilizo para llenar la tabla, pero sospecho que sería bastante ineficiente porque una muestra podría tener 200,000 observaciones.Cálculo del rango percentil en MySQL

+0

¿Puede explicar exactamente qué quiere decir con percentile rank? –

+0

@AssafLavie: http://en.wikipedia.org/wiki/Percentile_rank – eliasah

+0

Hice una función Mysql que funciona para cualquier percentil: http://stackoverflow.com/a/40266115/1662956 – dartaloufe

Respuesta

1

Esta es una respuesta relativamente fea, y me siento culpable de decirlo. Dicho esto, podría ayudarte con tu problema.

Una forma de determinar el porcentaje sería contar todas las filas y contar el número de filas que son mayores que el número que proporcionó. Puede calcular mayor o menor que y tomar el inverso según sea necesario.

Cree un índice de su número. total = select count (); less_equal = select count () donde value> indexed_number;

El porcentaje sería algo así como: less_equal/o total (Total - less_equal)/total de

Asegúrese de que ambos de ellos están utilizando el índice que ha creado. Si no lo son, ajústelos hasta que lo estén. La consulta de explicación debe tener "usar índice" en la columna de la derecha. En el caso del recuento selectivo (*) debería estar usando el índice para InnoDB y algo así como const para MyISAM. MyISAM conocerá este valor en cualquier momento sin tener que calcularlo.

Si necesita tener el porcentaje almacenado en la base de datos, puede utilizar la configuración de arriba para el rendimiento y luego calcular el valor de cada fila utilizando la segunda consulta como una selección interna. El primer valor de la consulta se puede establecer como una constante.

¿Le sirve de ayuda?

Jacob

+0

De hecho, lo intenté hace unas semanas y fue increíblemente lento, así que terminé calculando percentiles en python y poniendo el valor en la base de datos. – lhahne

+0

¿Intentó usar el recuento de selección (*) y seleccionar conteo (*) <= su valor? ¿Confirmaste que ambos estaban siendo manejados por un índice que solo tenía las columnas que necesitabas? Si la solución tuviera que tocar las filas de datos, esperaría que fuera una o dos órdenes de magnitud más lenta. Si los índices incluían más columnas que las necesarias o si la configuración de memoria de MySQL no se configuraba correctamente, era muy lento. Si es así, esto debería haber sido rápido. ¿Aproximadamente cuánto tiempo es "increíblemente lento"? Dependiendo del orden de magnitud de la respuesta esperada, mi respuesta podría ser mansamente lenta. – TheJacobTaylor

+0

@TheJacobTaylor Respuesta correcta pero corta de código. Si pones una consulta de tipo 'seleccionar distinto' funcional, obtienes mi +1. Además, si puedes arreglar esto, obtienes un bonito +1 brillante y ¡mira! ;)) http://stackoverflow.com/questions/13689434/update-all-rows-with-countdistinct-only-updates-first-row-the-rest-0 –

0

Para obtener el rango, yo diría que necesita para exterior (izquierda) se une a la mesa en sí algo como:

select t1.name, t1.value, count(distinct isnull(t2.value,0)) 
from table t1 
left join table t2 
on t1.value>t2.value 
group by t1.name, t1.value 

Para cada fila, se contar cuántas (si los hay) las filas de la misma tabla tienen un valor inferior.

Tenga en cuenta que estoy más familiarizado con sqlserver, por lo que la sintaxis podría no ser correcta. Además, es posible que los distintos no tengan el comportamiento correcto para lo que quieres lograr. Pero esa es la idea general.
Luego, para obtener el rango percentil real, primero tendrá que obtener el número de valores en una variable (o valores distintos según la convención que desea tomar) y calcular el rango percentil utilizando el rango real dado anteriormente.

2

Si está combinando su SQL con un lenguaje de procedimientos como PHP, puede hacer lo siguiente. Este ejemplo descompone los tiempos de exceso de bloque de vuelo en un aeropuerto, en sus percentiles. Utiliza la cláusula LIMIT x, y en MySQL en combinación con ORDER BY. No es muy bonito, pero hace el trabajo (lo siento luchado con el formato):

$startDt = "2011-01-01"; 
$endDt = "2011-02-28"; 
$arrPort= 'JFK'; 

$strSQL = "SELECT COUNT(*) as TotFlights FROM FIDS where depdt >= '$startDt' And depdt <= '$endDt' and ArrPort='$arrPort'"; 
if (!($queryResult = mysql_query($strSQL, $con))) { 
    echo $strSQL . " FAILED\n"; echo mysql_error(); 
    exit(0); 
} 
$totFlights=0; 
while($fltRow=mysql_fetch_array($queryResult)) { 
    echo "Total Flights into " . $arrPort . " = " . $fltRow['TotFlights']; 
    $totFlights = $fltRow['TotFlights']; 

    /* 1906 flights. Percentile 90 = int(0.9 * 1906). */ 
    for ($x = 1; $x<=10; $x++) { 
     $pctlPosn = $totFlights - intval(($x/10) * $totFlights); 
     echo "PCTL POSN for " . $x * 10 . " IS " . $pctlPosn . "\t"; 
     $pctlSQL = "SELECT (ablk-sblk) as ExcessBlk from FIDS where ArrPort='" . $arrPort . "' order by ExcessBlk DESC limit " . $pctlPosn . ",1;"; 
     if (!($query2Result = mysql_query($pctlSQL, $con))) { 
      echo $pctlSQL . " FAILED\n"; 
      echo mysql_error(); 
      exit(0); 
     } 
     while ($pctlRow = mysql_fetch_array($query2Result)) { 
      echo "Excess Block is :" . $pctlRow['ExcessBlk'] . "\n"; 
     } 
    } 
} 
18

Aquí es un enfoque diferente que no requiere de una combinación. En mi caso (una tabla con más de 15,000 filas), se ejecuta en aproximadamente 3 segundos. (El método JOIN toma un orden de magnitud más largo).

En la muestra, asumen que medida es la columna en la que está calculando el rango ciento, y Identificación es sólo un identificador de fila (no es obligatorio):

SELECT 
    id, 
    @prev := @curr as prev, 
    @curr := measure as curr, 
    @rank := IF(@prev > @curr, @[email protected], @rank) AS rank, 
    @ties := IF(@prev = @curr, @ties+1, 1) AS ties, 
    ([email protected]/@total) as percentrank 
FROM 
    mytable, 
    (SELECT 
     @curr := null, 
     @prev := null, 
     @rank := 0, 
     @ties := 1, 
     @total := count(*) from mytable where measure is not null 
    ) b 
WHERE 
    measure is not null 
ORDER BY 
    measure DESC 

de crédito para este método va a Shlomi Noach. Él escribe sobre ello en detalle aquí:

http://code.openark.org/blog/mysql/sql-ranking-without-self-join

He probado esto en MySQL y funciona muy bien; ninguna idea acerca de Oracle, SQLServer, etc.

+1

Esto funciona extremadamente bien. Genius SQL. –

+2

Desafortunadamente, esto depende del orden de evaluación de las variables de usuario, que es un comportamiento indefinido. El primer comentario en ese enlace cita el manual de MySQL: "El orden de evaluación de las variables de usuario no está definido y puede cambiar en función de los elementos contenidos en una consulta determinada ... La regla general es nunca asignar un valor a una variable de usuario en una parte de una declaración y use la misma variable en alguna otra parte de la misma declaración. Puede obtener los resultados que espera, pero esto no está garantizado ". Referencia: http://dev.mysql.com/doc/refman/5.1/en/user-variables.html – rep

1
SELECT 
    c.id, c.score, ROUND(((@rank - rank)/@rank) * 100, 2) AS percentile_rank 
FROM 
    (SELECT 
    *, 
     @prev:[email protected], 
     @curr:=a.score, 
     @rank:=IF(@prev = @curr, @rank, @rank + 1) AS rank 
    FROM 
     (SELECT id, score FROM mytable) AS a, 
     (SELECT @curr:= null, @prev:= null, @rank:= 0) AS b 
ORDER BY score DESC) AS c;