MySQLには非常に大きな測定データのテーブルがあり、これらの値のすべてのパーセンタイルランクを計算する必要があります。 Oracleにはpercent_rankという関数があるようですが、MySQLに似たものは見つかりません。確かに、Pythonでブルートフォースすることはできますが、1つのサンプルに200.000の観測値がある可能性があるため、これは非常に非効率的だと思います。
これは比較的醜い答えであり、私はそれを言って罪悪感を感じます。そうは言っても、それはあなたの問題に役立つかもしれません。
パーセンテージを決定する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番目のクエリを内部選択として使用して各行の値を計算できます。最初のクエリの値は定数として設定できます。
これは役に立ちますか?
ヤコブ
これは、結合を必要としない別のアプローチです。私の場合(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などについてはわかりません。
これを行う簡単な方法はありません。 http://rpbouman.blogspot.com/2008/07/calculating-nth-percentile-in-mysql.html を参照してください。
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;
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";
}
}
}
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
ウィンドウ関数を使用して 。
ランクを取得するには、(左)テーブル自体を次のように外部結合する必要があると思います。
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は、達成したいことに対して正しい動作をしない場合があります。しかし、それが一般的な考え方です。
次に、実際のパーセンタイルランクを取得するには、最初に変数の値の数(または、使用する規則に応じて個別の値)を取得し、上記の実際のランクを使用してパーセンタイルランクを計算する必要があります。
次のような販売テーブルがあるとします。
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バージョンに関数がないため単純に見えます。