2009-06-29 16 views
17

Ho una tabella molto grande di dati di misura in MySQL e ho bisogno di calcolare il rango percentile per ognuno di questi valori. Oracle sembra avere una funzione chiamata percent_rank ma non riesco a trovare nulla di simile per MySQL. Certo, potrei solo forzarlo in Python che uso comunque per popolare il tavolo, ma sospetto che sarebbe abbastanza inefficiente perché un campione potrebbe avere 200.000 osservazioni.Calcolo del rango percentile in MySQL

+0

Puoi spiegare esattamente cosa intendi per grado percentile? –

+0

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

+0

Ho creato una funzione Mysql funzionante per qualsiasi percentile: http://stackoverflow.com/a/40266115/1662956 – dartaloufe

risposta

1

Questa è una risposta relativamente brutta, e mi sento in colpa a dirlo. Detto questo, potrebbe aiutarti con il tuo problema.

Un modo per determinare la percentuale consiste nel contare tutte le righe e contare il numero di righe maggiore del numero fornito. È possibile calcolare maggiore o minore e prendere l'inverso, se necessario.

Creare un indice sul proprio numero. total = select count (); less_equal = select count () dove value> indexed_number;

La percentuale sarebbe qualcosa di simile: less_equal/totale o (totale - less_equal)/totale

Assicurarsi che entrambi stanno utilizzando l'indice che si è creato. Se non lo sono, modificali finché non lo sono. La query explain dovrebbe avere "using index" nella colonna di destra. Nel caso del conteggio selezionato (*) dovrebbe utilizzare l'indice per InnoDB e qualcosa come const per MyISAM. MyISAM conoscerà questo valore in qualsiasi momento senza doverlo calcolare.

Se è necessario avere la percentuale memorizzata nel database, è possibile utilizzare l'impostazione di cui sopra per le prestazioni e quindi calcolare il valore per ogni riga utilizzando la seconda query come selezione interna. Il valore della prima query può essere impostato come costante.

Questo aiuto?

Jacob

+0

In realtà l'ho provato poche settimane fa ed era incredibilmente lento, quindi ho finito per calcolare percentili in python e aver inserito il valore nel database. – lhahne

+0

Hai provato a utilizzare il conteggio selezione (*) e seleziona conteggio (*) <= valore? Hai confermato che entrambi erano gestiti da un indice che aveva solo le colonne necessarie? Se la soluzione dovesse toccare le righe di dati, mi aspetto che sia più lento di uno o due ordini di grandezza. Se gli indici includevano più delle colonne necessarie o la configurazione della memoria di MySQL non era impostata correttamente, sarebbe molto lenta. Se è così, questo dovrebbe essere stato veloce. Quanto tempo è "incredibilmente lento"? A seconda dell'ordine di grandezza della risposta attesa, la mia risposta potrebbe non essere lenta. – TheJacobTaylor

+0

@TheJacobTaylor Risposta corretta ma codice breve. Se metti una query di tipo "seleziona distinta", ottieni il mio +1. Inoltre, se puoi risolvere il problema, ottieni un bel +1 brillante e controlla! ;)) http://stackoverflow.com/questions/13689434/update-all-rows-with-countdistinct-only-updates-first-row-the-rest-0 –

0

Per ottenere il rango, direi che è necessario (a sinistra) esterno uniscono al tavolo su se stesso qualcosa di simile:

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 

Per ogni riga, si contare quanti (se del caso) le righe della stessa tabella hanno un valore inferiore.

Nota che sono più familiare con sqlserver, quindi la sintassi potrebbe non essere corretta. Inoltre, il distinto potrebbe non avere il comportamento giusto per ciò che si desidera ottenere. Ma questa è l'idea generale.
Quindi per ottenere il rango percentile reale è necessario prima ottenere il numero di valori in una variabile (o valori distinti a seconda della convenzione che si desidera prendere) e calcolare il rango percentile utilizzando il rango reale indicato in precedenza.

2

Se stai combinando SQL con un linguaggio procedurale come PHP, è possibile effettuare le seguenti operazioni. Questo esempio suddivide i tempi di blocco di volo in eccesso in un aeroporto, nei loro percentili. Utilizza la clausola LIMIT x, in MySQL in combinazione con ORDER BY. Non molto bella, ma non il lavoro (scusate lottato con la formattazione):

$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

Ecco un approccio diverso che non richiede una join. Nel mio caso (una tabella con oltre 15.000) righe, viene eseguito in circa 3 secondi. (Il metodo JOIN ha un ordine di grandezza più lungo).

Nell'esempio, supporre che misura è la colonna su cui si sta calcolando il rango per cento, e id è solo un identificatore di riga (non richiesto):

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 

credito per questo il metodo va a Shlomi Noach. Scrive a questo proposito in dettaglio qui:

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

Ho provato questo in MySQL e funziona benissimo; nessuna idea su Oracle, SQLServer, ecc.

+1

Questo funziona estremamente bene. Genius SQL. –

+2

Sfortunatamente ciò dipende dall'ordine di valutazione delle variabili utente, che è un comportamento non definito. Il primo commento in quel link cita il manuale di MySQL: "L'ordine di valutazione per le variabili utente non è definito e può variare in base agli elementi contenuti in una determinata query ... La regola generale non è mai assegnare un valore a una variabile utente in una parte di una dichiarazione e usa la stessa variabile in qualche altra parte della stessa affermazione. Potresti ottenere i risultati che ti aspetti, ma questo non è garantito. " Riferimento: 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;