◀ 23.

MySQLにおけるパーセンタイルの導き方

▶
この記事の要点
  • MySQL には 2026 年時点でも PERCENTILE_CONT / PERCENTILE_DISC 関数がない(MariaDB 10.3.3 以降・PostgreSQL にはある)
  • MySQL 8.0 以降は ROW_NUMBER() と COUNT(*) OVER() のウィンドウ関数で計算するのが確実
  • MySQL 5.7 以前は GROUP_CONCAT + SUBSTRING_INDEX で求められるが、group_concat_max_len(既定 1024 バイト)を超えると黙って切り捨てられ、誤った値になる
  • パーセンタイルには「最近順位法(実在する値を返す)」と「線形補間(Excel の PERCENTILE.INC 相当)」など複数の定義がある。どちらで計算するかを先に決める
  • NULL の扱いに注意。COUNT(*) は NULL も数えるので COUNT(列) を使う

パーセンタイルとは・定義の違い

パーセンタイルは、データを小さい順に並べたとき「下から p% の位置にある値」です。50 パーセンタイルは中央値、90 パーセンタイルは「90% のデータがこの値以下」という目安で、レスポンスタイムの監視(p90・p99)などでよく使われます。

ただし計算方法には複数の流儀があり、データ件数が少ないと結果が変わります。

方式計算相当する関数
最近順位法(nearest rank)件数 N のとき、小さい順で CEIL(p × N) 番目の値。必ず実在する値を返すPERCENTILE_DISC(MariaDB / PostgreSQL)
線形補間位置 h = 1 + p × (N − 1) を求め、前後 2 つの値を按分するPERCENTILE_CONT、Excel の PERCENTILE.INC、pandas の quantile() 既定

例えば値が 1〜10 の 10 件で 90 パーセンタイルを求めると、最近順位法は 9 番目の「9」、線形補間は h = 9.1 なので「9.1」になります。

MySQL 8.0 以降:ウィンドウ関数で求める(推奨)

最近順位法

WITH ranked AS (
    SELECT
        val,
        ROW_NUMBER() OVER (ORDER BY val) AS rn,
        COUNT(*)     OVER ()             AS cnt
    FROM test_table
    WHERE val IS NOT NULL
)
SELECT MIN(val) AS p90
FROM ranked
WHERE rn >= CEIL(0.9 * cnt);

0.9 を 0.5 にすれば中央値(下側)、0.99 にすれば 99 パーセンタイルです。

線形補間(PERCENTILE_CONT と同じ結果)

WITH ranked AS (
    SELECT
        val,
        ROW_NUMBER() OVER (ORDER BY val) AS rn,
        COUNT(*)     OVER ()             AS cnt
    FROM test_table
    WHERE val IS NOT NULL
),
pos AS (
    SELECT 1 + 0.9 * (MAX(cnt) - 1) AS h FROM ranked
)
SELECT
    MAX(CASE WHEN rn = FLOOR(h) THEN val END)
    + (h - FLOOR(h))
      * (MAX(CASE WHEN rn = CEIL(h)  THEN val END)
       - MAX(CASE WHEN rn = FLOOR(h) THEN val END)) AS p90
FROM ranked
CROSS JOIN pos
GROUP BY h;

グループごとに求める

カテゴリ別・日別などのパーセンタイルは、ウィンドウ関数に PARTITION BY を付けます。

WITH ranked AS (
    SELECT
        category,
        val,
        ROW_NUMBER() OVER (PARTITION BY category ORDER BY val) AS rn,
        COUNT(*)     OVER (PARTITION BY category)              AS cnt
    FROM test_table
    WHERE val IS NOT NULL
)
SELECT category, MIN(val) AS p90
FROM ranked
WHERE rn >= CEIL(0.9 * cnt)
GROUP BY category;

MySQL 5.7 以前:GROUP_CONCAT と SUBSTRING_INDEX で求める

ウィンドウ関数がないバージョンでは、値を小さい順にカンマ区切りで連結し、N 番目の要素を切り出す方法が使えます。以下の SQL を実行することで、MySQL でパーセンタイルを導き出すことができます(90% を指定した例)。

SELECT
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            GROUP_CONCAT(
                t1.val ORDER BY t1.val SEPARATOR ','
            )
            , ',', 90 / 100 * COUNT(*) + 1
        ), ',', - 1
    ) AS `Percentile`
FROM
    test_table AS t1

内側の SUBSTRING_INDEX が「先頭から指定した個数の要素」を取り出し、外側の SUBSTRING_INDEX(..., ',', -1) がその最後の 1 つを取り出す仕組みです。

上の式は旧版の本記事で紹介していたもので、「p × N + 1 番目」を返します。p × N が整数になる場合(10 件で 90% など)は最近順位法より 1 つ上の値になります。定義をそろえるなら、次のように NULL を除外して CEIL を使います。

SET SESSION group_concat_max_len = 1000000;   -- 切り捨て防止(先に必ず実行)

SELECT
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            GROUP_CONCAT(t1.val ORDER BY t1.val SEPARATOR ','),
            ',', CEIL(0.9 * COUNT(t1.val))
        ), ',', -1
    ) AS p90
FROM test_table AS t1
WHERE t1.val IS NOT NULL;

落とし穴

  • group_concat_max_len による切り捨て: 既定は 1024 バイトで、超えた分は警告だけ出して切り捨てられる。件数が多いと後半の値が消え、エラーにならないまま誤ったパーセンタイルが返る。セッション変数を十分大きくするか、ウィンドウ関数の方法を使う(上限は max_allowed_packet にも制約される)
  • NULL: GROUP_CONCAT は NULL を連結しないが COUNT(*) は数えるため、位置がずれる。COUNT(列) か WHERE 列 IS NOT NULL を使う
  • 戻り値が文字列: SUBSTRING_INDEX の結果は文字列。数値として比較・計算するなら CAST(... AS DECIMAL(10,2)) などで変換する
  • パフォーマンス: どちらの方法も全件の並べ替えが必要。大きなテーブルでは対象期間などで絞り込み、ORDER BY の列にインデックスを用意する
  • NTILE や PERCENT_RANK との混同: NTILE(100) は行を 100 グループに分ける関数、PERCENT_RANK() は各行が何パーセンタイルの位置にあるかを返す関数で、「p% 点の値」を直接返すものではない

確認方法

  1. SELECT VERSION(); で MySQL のバージョンを確認し、8.0 以上ならウィンドウ関数の方法を使う
  2. 1〜10 のような結果が分かっているデータを一時テーブルに入れて実行し、期待値(最近順位法なら 9、線形補間なら 9.1)と一致するか確かめる
  3. GROUP_CONCAT 方式では、実行後に SHOW WARNINGS; で「cut by GROUP_CONCAT()」の警告が出ていないか確認する
  4. 同じデータを Excel(PERCENTILE.INC)や pandas(quantile(0.9))で計算し、線形補間の結果と照合する

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. ダウンロード&インストール方法(Windows)
  2. インストール方法(Linux)
  3. コマンド一覧
  4. SQL
  5. データ型
  6. 関数
  7. 管理ツール
  8. 設定
  9. パフォーマンスチューニング関連
  10. エクスポートおよびインポート
  11. エラー&トラブル
  12. 文字コードの確認
  13. 実行中の SQL の状態確認およびプロセスキルの方法
  14. パスワードの無効化設定
  15. root ユーザーの初期パスワード確認方法
  16. rootユーザーのパスワード変更方法
  17. LIMIT, OFFSET の始まりと挙動
  18. mysqlのバージョン確認方法
  19. MySQLで実行計画を表示する方法
  20. レプリケーションのステータス確認方法
  21. 中央値の導き方(バージョン8未満)
  22. 階層SQL(バージョン8未満)
  23. パーセンタイルの導き方
  24. 特定スキーマの全テーブルの全カラム情報を取得する方法