Domanda

Ho una tabella molto grande di dati di misurazione in MySQL e ho bisogno di calcolare il grado 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 semplicemente forzarlo in Python, che uso comunque per popolare la tabella, ma sospetto che sarebbe abbastanza inefficiente perché un campione potrebbe avere 200.000 osservazioni.

È stato utile?

Soluzione

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 sarebbe quello di contare tutte le righe e contare il numero di righe che è maggiore del numero fornito. Puoi calcolare maggiore o minore di e prendere l'inverso se necessario.

Crea un indice sul tuo numero. total = seleziona count (); less_equal = seleziona count () dove valore > indexed_number;

La percentuale sarebbe simile a: less_equal / total o (total - less_equal) / total

Assicurati che entrambi stiano utilizzando l'indice che hai creato. Se non lo sono, modificali fino a quando non lo sono. La query di spiegazione dovrebbe avere & Quot; usando index & Quot; nella colonna di destra. Nel caso del conteggio delle selezioni (*) dovrebbe usare l'indice per InnoDB e qualcosa di simile a const per MyISAM. MyISAM conoscerà questo valore in qualsiasi momento senza doverlo calcolare.

Se è necessario disporre della percentuale memorizzata nel database, è possibile utilizzare l'installazione dall'alto 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 aiuta?

Jacob

Altri suggerimenti

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

Nell'esempio, supponi che misura sia la colonna su cui stai calcolando il rango percentuale e id sia solo un identificatore di riga (non richiesto):

SELECT
    id,
    @prev := @curr as prev,
    @curr := measure as curr,
    @rank := IF(@prev > @curr, @rank+@ties, @rank) AS rank,
    @ties := IF(@prev = @curr, @ties+1, 1) AS ties,
    (1-@rank/@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

Il merito di questo metodo va a Shlomi Noach. Ne parla in dettaglio qui:

http://code.openark.org/blog/mysql/sql-ranking -senza-auto collegamento

L'ho provato su MySQL e funziona benissimo; nessuna idea su Oracle, SQLServer, ecc.

SELECT 
    c.id, c.score, ROUND(((@rank - rank) / @rank) * 100, 2) AS percentile_rank
FROM
    (SELECT 
    *,
        @prev:=@curr,
        @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;

Se stai combinando il tuo SQL con un linguaggio procedurale come PHP, puoi fare quanto segue. Questo esempio suddivide i tempi di blocco del volo in eccesso in un aeroporto, nei loro percentili. Utilizza la clausola LIMIT x, y in MySQL in combinazione con ORDER BY. Non molto carino, ma fa il lavoro (mi dispiace 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";
        }
    }
}

MySQL 8 ha infine introdotto le funzioni della finestra e tra queste, PERCENT_RANK() che stavi cercando. Quindi, basta scrivere:

SELECT col, percent_rank() OVER (ORDER BY col)
FROM t
ORDER BY col

La tua domanda menziona " percentili " ;, che sono una cosa leggermente diversa. Per completezza, ci sono PERCENTILE_DISC e PERCENTILE_CONT funzioni di distribuzione inversa nello standard SQL e in alcuni RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), ma non in MySQL. Con MySQL 8 e le funzioni della finestra, è possibile emulare PERCENT_RANK, tuttavia, utilizzando nuovamente le funzioni della finestra FIRST_VALUE e <=> .

Per ottenere il grado, direi che è necessario (a sinistra) unire il tavolo su se stesso qualcosa come:

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, conterai quante (se presenti) righe della stessa tabella hanno un valore inferiore.

Nota che ho più familiarità con sqlserver, quindi la sintassi potrebbe non essere corretta. Inoltre, il distinto potrebbe non avere il comportamento giusto per quello che vuoi ottenere. Ma questa è l'idea generale.
Quindi per ottenere il rango percentile reale dovrai prima ottenere il numero di valori in una variabile (o valori distinti a seconda della convenzione che vuoi prendere) e calcolare il rango percentile usando il rango reale indicato sopra.

Supponiamo di avere una tabella di vendita come:

user_id, unità

la seguente query fornirà il percentile di ciascun utente:

select a.user_id,a.units,
(sum(case when a.units >= b.units then 1 else 0 end )*100)/count(1) percentile
from sales a join sales b ;

Nota che questo vale per il cross join, quindi risulta nella complessità O (n2), quindi può essere considerata una soluzione non ottimizzata ma sembra semplice dato che non abbiamo alcuna funzione nella versione mysql.

Non sono sicuro di cosa significhi l'operazione per "rango di percentile", ma per ottenere un dato percentile per un set di valori vedi http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html Il calcolo sql potrebbe essere facilmente modificato per produrre un altro o più percentili.

Una nota: ho dovuto modificare leggermente il calcolo, ad esempio il 90 ° percentile - " 90/100 * COUNT (*) + 0,5 " invece di " 90/100 * COUNT (*) + 1 " ;. A volte saltava due valori oltre il punto percentile nell'elenco ordinato, anziché selezionare il valore successivo più alto per il percentile. Forse il modo in cui l'arrotondamento degli interi funziona in mysql.

vale a dire:

.... SUBSTRING_INDEX (SUBSTRING_INDEX (GROUP_CONCAT (fieldValue ORDER BY fieldValue SEPARATOR ','), ',', 90/100 * COUNT (*) + 0.5 ), ',', -1) come 90thPercentile ....

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top