2009-06-29 15 views
17

私はMySQLで非常に大きな測定データテーブルを持っており、これらの値ごとにパーセンタイルランクを計算する必要があります。 Oracleにはpercent_rankという機能があるようですが、MySQLの場合はこれと似たものが見つかりません。確かに私はテーブルにデータを入れるために私がとにかく使うPythonで無理矢理強制することができますが、1つのサンプルが200.000回の観測を持つ可能性があるため、非常に非効率的であると思われます。MySQLのパーセンタイルランクの計算

+0

パーセンタイルランクの意味を正確に説明できますか? –

+0

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

+0

すべてのパーセンタイルで機能するMysql関数を作成しました: http://stackoverflow.com/a/40266115/1662956 – dartaloufe

答えて

1

これは比較的醜い答えです。私はそれを言って罪悪感を感じます。それは、あなたの問題に関してあなたを助けるかもしれない、と言った。

割合を判断する1つの方法は、すべての行を数え、提供した数よりも多い行の数を数えることです。より大きいか小さいかを計算し、必要に応じて逆数を取ることができます。

番号にインデックスを作成します。 合計=選択カウント()。 less_equal =選択カウント()ここで、value> indexed_number;

割合は次のようなものになるだろう:less_equal /合計または(合計 - less_equal)/総

それらの両方は、あなたが作成したインデックスを使用していることを確認してください。そうでない場合は、調整してください。 Explain照会には、右側の列に「使用索引」が必要です。 select count(*)の場合は、InnoDBのインデックスとMyISAMのconstのようなものを使用する必要があります。 MyISAMはいつでもこの値を計算しなくても知ることができます。

%をデータベースに格納する必要がある場合は、上記の設定を使用してパフォーマンスを確認し、2番目のクエリを内部セレクトとして使用して各行の値を計算することができます。最初のクエリの値は定数として設定できます。

これは役に立ちますか?

ヤコブ

+0

私は実際に数週間前にそれを試みました、そして、それは非常に遅かったので、私はpythonでパーセンタイルを計算し、その値をデータベースに入れました。 – lhahne

+0

選択カウント(*)と選択カウント(*)<=あなたの値を使用しようとしましたか?それらの両方が必要な列のみを持つ索引によって処理されていることを確認しましたか?ソリューションがデータ行に全く触れなければならない場合は、1〜2桁遅くなることが予想されます。必要な列以上のインデックスが含まれているか、MySQLのメモリ構成が正しく設定されていない場合は、非常に遅いです。もしそうなら、これは速かったはずです。どのくらいの時間が「信じられないほど遅い」ですか?期待される応答の大きさの順序に応じて、私の答えは不穏に遅くなる可能性があります。 – TheJacobTaylor

+0

@TheJacobTaylor正解ですが短いコードです。機能的な 'select distinct'型のクエリを置くと、私の+1が得られます。また、これを修正することができれば、光沢のある+1が得られチェック! ;))http://stackoverflow.com/questions/13689434/update-all-rows-with-countdistinct-only-updates-first-row-the-rest-0 –

4

これを行う簡単な方法はありません。以下のようなもの自体にテーブルを外部結合、私はあなたが(左)に必要と言うだろう、ランクを取得するにはhttp://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html

+0

私が探しているものは実際にはその逆数です。すなわち、数字を与えれば、そのランクを教えてください。私はこれがOracleでより簡単になると確信していますが、残念ながらそれは可能性はありません。 – lhahne

0

を参照してください。行ごとに

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 

、あなたはどのように多くの(もしあれば)カウントされます同じテーブルの行には値が劣ります。

私はsqlserverに詳しいので、構文が正しくない可能性があることに注意してください。また、別名は、あなたが達成したいことに対して正しい行動をしていないかもしれません。しかしそれは一般的な考えです。
実際のパーセンタイルのランクを取得するには、まず変数の値の数を取得し、上記の実数のランクを使用してパーセンタイルのランクを計算する必要があります。

2

あなたはPHPのような手続き型言語を使用してSQLを組み合わせている場合は、次の操作を行うことができます。この例では、超過した飛行ブロック時間を空港に分けてパーセンタイルに分解します。 MySQLのLIMIT x、y句をORDER BYと組み合わせて使用​​します。 (申し訳ありませんフォーマットに苦労し)非常にきれいな、しかし仕事をしていませんではない:

$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

ここで参加を必要としない別のアプローチがあります。私の場合(1万5千以上の行)のテーブルでは、約3秒で実行されます。 (JOINメソッドのほうが長くなります)。このため

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, 
    (1[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 

クレジット:サンプルで

は、 対策はあなたがパーセント・ランクを計算しているどの列があり、かつ IDは(必須ではありません)だけ行識別子であることを前提としてい方法はShlomi Noachに行く。彼はここでは詳細にそれについて書いている:私は、MySQLでこれをテストしてみた、それは素晴らしい作品

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

。 Oracle、SQLServerなどについては考えていません。

+1

これは非常にうまく動作します。 Genius SQL。 –

+2

残念ながら、これは未定義の動作であるユーザー変数の評価の順序によって異なります。そのリンクの最初のコメントはMySQLのマニュアルを引用しています: "ユーザ変数の評価の順序は未定義であり、与えられたクエリに含まれる要素に基づいて変更されるかもしれません....一般的な規則は決してユーザ変数同じステートメントの他の部分で同じ変数を使用すると、期待通りの結果が得られるかもしれませんが、これは保証されていません」 リファレンス: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;