web-dev-qa-db-ja.com

MySQLでパーセンタイルランクを計算する

MySQLには非常に大きな測定データのテーブルがあり、これらの値のすべてのパーセンタイルランクを計算する必要があります。 Oracleにはpercent_rankという関数があるようですが、MySQLに似たものは見つかりません。確かに、Pythonでブルートフォースすることはできますが、1つのサンプルに200.000の観測値がある可能性があるため、これは非常に非効率的だと思います。

19
lhahne

これは比較的醜い答えであり、私はそれを言って罪悪感を感じます。そうは言っても、それはあなたの問題に役立つかもしれません。

パーセンテージを決定する1つの方法は、すべての行をカウントし、指定した数よりも多い行の数をカウントすることです。大きいか小さいかを計算し、必要に応じて逆をとることができます。

あなたの番号にインデックスを作成します。 total = select count(); less_equal = select count()where value> indexed_number;

パーセンテージは次のようになります:less_equal/totalまたは(total-less_equal)/ total

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

パーセンテージをデータベースに保存する必要がある場合は、パフォーマンスのために上記の設定を使用してから、2番目のクエリを内部選択として使用して各行の値を計算できます。最初のクエリの値は定数として設定できます。

これは役に立ちますか?

ヤコブ

2
TheJacobTaylor

これは、結合を必要としない別のアプローチです。私の場合(15,000以上のテーブル)、約3秒で実行されます。 (JOINメソッドは1桁長くかかります)。

サンプルでは、​​measureがパーセントランクを計算している列であり、idは単なる行識別子です(必須ではありません):

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

この方法の功績はShlomiNoachにあります。彼はそれについてここに詳細に書いています:

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

私はこれをMySQLでテストしましたが、うまく機能します。 Oracle、SQLServerなどについてはわかりません。

19
mattstuehler

これを行う簡単な方法はありません。 http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html を参照してください。

4
Nir Levy
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;
4
Conor

SQLをPHPなどの手続き型言語と組み合わせる場合は、次のことができます。この例では、超過飛行ブロック時間を空港とパーセンタイルに分解します。 MySQLのLIMITx、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";
        }
    }
}
2
Pete

MySQL 8はついにウィンドウ関数を導入しました、そしてそれらの中で、あなたが探していた PERCENT_RANK() 関数。だから、ただ書く:

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

あなたの質問は、わずかに異なるものである「パーセンタイル」について言及しています。完全を期すために、SQL標準および一部のRBDMS(Oracle、PostgreSQL、SQL Server、Teradata)にはPERCENTILE_DISCおよびPERCENTILE_CONT逆分布関数がありますが、MySQLにはありません。 MySQL 8およびウィンドウ関数を使用すると、 PERCENTILE_DISCをエミュレートできますが、PERCENT_RANKおよびFIRST_VALUEウィンドウ関数を使用して

1
Lukas Eder

ランクを取得するには、(左)テーブル自体を次のように外部結合する必要があると思います。

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に精通しているため、構文が正しくない可能性があることに注意してください。また、distinctは、達成したいことに対して正しい動作をしない場合があります。しかし、それが一般的な考え方です。
次に、実際のパーセンタイルランクを取得するには、最初に変数の値の数(または、使用する規則に応じて個別の値)を取得し、上記の実際のランクを使用してパーセンタイルランクを計算する必要があります。

0
PatRedway

次のような販売テーブルがあるとします。

user_id、units

次に、次のクエリは各ユーザーのパーセンタイルを示します。

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 ;

これはクロス結合に適用されるため、結果はO(n2)複雑になるため、最適化されていないソリューションと見なすことができますが、mysqlバージョンに関数がないため単純に見えます。

0