MariaDBでGROUP BYとROW_NUMBER()関数を組み合わせたクエリが誤動作する理由
MySQLとMariaDBにおけるグループごとの上位N件取得クエリの違い
MySQLとMariaDBは互換性のあるデータベースですが、グループごとの上位N件を取得するクエリにおいて、結果が異なる場合があります。この違いは、両データベースにおけるウィンドウ関数の動作の違いによるものです。
問題
以下のクエリは、group_id
ごとに score
の上位2件を取得するものです。
SELECT
group_id,
score
FROM
(
SELECT
group_id,
score,
ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY score DESC) AS rn
FROM
table
) AS t
WHERE
rn <= 2
このクエリはMySQLでは正しく動作しますが、MariaDBでは誤った結果になる可能性があります。
原因
MariaDBの ROW_NUMBER()
関数は、ORDER BY
句で指定された順序に基づいて行に番号を割り当てます。しかし、PARTITION BY
句も使用されている場合、MariaDBはパーティション内の行のみを考慮して番号を割り当てます。
解決策
この問題を解決するには、MariaDBの ROW_NUMBER()
関数の代わりに RANK()
関数を使用する必要があります。RANK()
関数は、パーティション全体で行に順位を割り当てます。
SELECT
group_id,
score
FROM
(
SELECT
group_id,
score,
RANK() OVER (PARTITION BY group_id ORDER BY score DESC) AS rn
FROM
table
) AS t
WHERE
rn <= 2
以下の方法も、グループごとの上位N件を取得するために使用できます。
- サブクエリを使用する
- ウィンドウ関数
PERCENTILE_CONT()
を使用する
- この問題は、MariaDB 10.2.7以前のバージョンのみに影響します。
- MariaDB 10.3以降では、
ROW_NUMBER()
関数はPARTITION BY
句と組み合わせて使用しても、MySQLと同様に動作します。
-- MySQL and MariaDB 10.3以降
SELECT
group_id,
score
FROM
(
SELECT
group_id,
score,
ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY score DESC) AS rn
FROM
table
) AS t
WHERE
rn <= 2;
-- MariaDB 10.2.7以前
SELECT
group_id,
score
FROM
(
SELECT
group_id,
score,
RANK() OVER (PARTITION BY group_id ORDER BY score DESC) AS rn
FROM
table
) AS t
WHERE
rn <= 2;
説明
table
は、group_id
とscore
という2つの列を持つテーブルです。- クエリはまず、
ROW_NUMBER()
またはRANK()
関数を使用して、group_id
ごとにscore
の順位を計算します。 - その後、
rn
が 2 以下の行のみを選択します。
実行結果
以下の出力は、group_id
1 と 2 の上位2件のレコードを示しています。
group_id | score
------- | --------
1 | 100
1 | 90
2 | 80
2 | 70
SELECT
group_id,
score
FROM
table
WHERE
(group_id, score) IN (
SELECT
group_id,
score
FROM
table
ORDER BY
score DESC
LIMIT
2
)
以下のクエリは、ウィンドウ関数 PERCENTILE_CONT()
を使用してグループごとの上位N件を取得します。
SELECT
group_id,
score
FROM
(
SELECT
group_id,
score,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY score DESC) AS percentile
FROM
table
) AS t
WHERE
score >= percentile
- 外部結合を使用する
- 集計関数を使用する
方法の選択
どの方法を使用するかは、パフォーマンス要件やデータの複雑さに依存します。
- サブクエリを使用する方法は、最もシンプルですが、パフォーマンスが低下する可能性があります。
- ウィンドウ関数を使用する方法は、パフォーマンスが優れていますが、複雑なクエリになる可能性があります。
mysql sql mariadb