Question

J'ai une très grande table de données de mesure dans MySQL et je dois calculer le rang de centile pour chacune de ces valeurs. Oracle semble avoir une fonction appelée percent_rank mais je ne trouve rien de semblable pour MySQL. Bien sûr, je pourrais simplement le forcer brutalement en Python, ce que j’utilise de toute façon pour remplir le tableau, mais je suppose que cela serait assez inefficace, car un échantillon pourrait avoir 200 000 observations.

Était-ce utile?

La solution

C'est une réponse relativement laide et je me sens coupable de le dire. Cela dit, cela pourrait vous aider à résoudre votre problème.

Une façon de déterminer le pourcentage consiste à compter toutes les lignes et à compter le nombre de lignes supérieures au nombre fourni. Vous pouvez calculer plus ou moins que et prendre l’inverse si nécessaire.

Créez un index sur votre numéro. total = nombre de sélections (); less_equal = select count () où valeur > numéro indexé;

Le pourcentage serait quelque chose comme: less_equal / total ou (total - less_equal) / total

Assurez-vous que les deux utilisent l'index que vous avez créé. S'ils ne le sont pas, modifiez-les jusqu'à ce qu'ils le soient. La requête explicitée doit avoir & Quot; utiliser l'index & Quot; dans la colonne de droite. Dans le cas du compte choisi (*), il devrait utiliser index pour InnoDB et quelque chose comme const pour MyISAM. MyISAM saura cette valeur à tout moment sans avoir à la calculer.

Si vous aviez besoin de stocker le pourcentage dans la base de données, vous pouvez utiliser la configuration ci-dessus pour améliorer les performances, puis calculer la valeur de chaque ligne en utilisant la seconde requête comme sélection interne. La valeur de la première requête peut être définie comme une constante.

Est-ce que cela vous aide?

Jacob

Autres conseils

Voici une approche différente qui ne nécessite pas de jointure. Dans mon cas (une table avec 15 000+) lignes, il s'exécute en environ 3 secondes. (La méthode JOIN prend un ordre de grandeur plus long).

Dans l'exemple, supposons que mesure soit la colonne sur laquelle vous calculez le pourcentage de classement et que id ne soit qu'un identifiant de ligne (non requis):

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

Le crédit pour cette méthode va à Shlomi Noach. Il écrit à ce sujet en détail ici:

http://code.openark.org/blog/mysql/sql-ranking -sans-auto-rejoindre

J'ai testé cela avec MySQL et cela fonctionne très bien; aucune idée sur Oracle, SQLServer, etc.

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;

Si vous combinez votre code SQL avec un langage procédural tel que PHP, vous pouvez procéder comme suit. Cet exemple décompose les temps de vol en bloc excédentaires dans un aéroport, en centiles. Utilise la clause LIMIT x, y dans MySQL en combinaison avec ORDER BY. Pas très joli, mais fait le boulot (désolé du formatage):

$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 a enfin introduit les fonctions de fenêtre, et parmi elles, le PERCENT_RANK() fonction que vous recherchiez. Alors, écrivez simplement:

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

Votre question mentionne & "Centiles &", qui sont une chose légèrement différente. Par souci d'exhaustivité, il existe PERCENTILE_DISC et PERCENTILE_CONT des fonctions de distribution inverse dans le standard SQL et dans certains RBDMS (Oracle, PostgreSQL, SQL Server, Teradata), mais pas dans MySQL. Avec MySQL 8 et les fonctions de fenêtre, vous pouvez émuler PERCENT_RANK, mais vous utilisez à nouveau les fonctions FIRST_VALUE et <=> de la fenêtre .

Pour obtenir le classement, je dirais que vous devez (à gauche) externe rejoindre la table sur lui-même, par exemple:

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 

Pour chaque ligne, vous allez compter combien de lignes (le cas échéant) de la même table ont une valeur inférieure.

Notez que je suis plus familier avec sqlserver, la syntaxe est peut-être incorrecte. En outre, le distinct peut ne pas avoir le bon comportement pour ce que vous voulez réaliser. Mais c'est l'idée générale.
Ensuite, pour obtenir le rang de centile réel, vous devez d’abord obtenir le nombre de valeurs d’une variable (ou des valeurs distinctes selon la convention à adopter) et calculer le rang de centile à l’aide du rang réel indiqué ci-dessus.

Supposons que nous ayons une table de vente du type:

id_utilisateur, unités

alors la requête suivante donnera le centile de chaque utilisateur:

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 ;

Notez que cela va aller pour une jointure croisée donc aboutir à une complexité O (n2) donc peut être considéré comme une solution non optimisée mais semble simple étant donné que nous n'avons aucune fonction dans la version mysql.

Vous ne savez pas exactement ce que signifie "rang en centile", mais vous obtenez un pourcentage donné pour un ensemble de valeurs, voir http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html Le calcul de SQL pourrait facilement être modifié pour produire un autre ou plusieurs centiles.

Une remarque: je devais modifier légèrement le calcul, par exemple le 90e percentile - & "; 90/100 * COUNT (*) + 0,5 &"; au lieu de " 90/100 * COUNT (*) + 1 " ;. Parfois, il ignorait deux valeurs après le point de centile dans la liste ordonnée, au lieu de choisir la valeur immédiatement supérieure pour le centile. Peut-être que la méthode d'arrondi de nombres entiers fonctionne dans mysql.

c'est-à-dire:

.... SUBSTRING_INDEX (SUBSTRING_INDEX (GROUP_CONCAT (valeur de zone ORDER BY: valeur de champ SEPARATOR ',') ',', ',', ', 90/100 * COUNT (*) + 0.5 ),', ', -1) en tant que 90e percentile ....

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top