今回は、MySQL 8のウィンドウ関数を使ってランキングを出す方法を、新人エンジニア向けに解説します。

特に、ROW_NUMBER、RANK、DENSE_RANKを使って、売上ランキング、点数ランキング、カテゴリ別ランキング、上位3件だけを取得するSQLを書けるようにしていきます。

ランキングをSQLで出したい場面は、実務でもよくあります。

売上金額が高い商品ランキングを出したい
ユーザーのポイントランキングを出したい
カテゴリごとに売上上位3件を出したい
部署ごとの成績ランキングを出したい
同点の場合の順位をどう扱うか決めたい





このような処理で便利なのが、MySQL 8から使えるウィンドウ関数です。

MySQL公式ドキュメントでは、ウィンドウ関数は集計のような操作を行うものの、GROUP BYのように行を1行へまとめず、各行ごとに結果を生成すると説明されています。さらに、ROW_NUMBER、RANK、DENSE_RANKなどはウィンドウ関数として使われ、OVER句が必須です。

ウィンドウ関数とは何か

ウィンドウ関数とは、検索結果の行を見ながら、各行に対して順位や集計結果を付けられる関数です。

少し難しく聞こえますよね。

たとえるなら、クラスのテスト結果一覧を見ながら、1人ずつに順位を書き込んでいく作業です。

生徒の行は消えません。

各生徒の行に「あなたは1位」「あなたは2位」という情報が追加されます。

GROUP BYとの違いを見てみましょう。

機能何をするか行数はどうなるか
GROUP BY行をグループにまとめる少なくなる
ウィンドウ関数各行に順位や集計値を付ける基本的に元の行数のまま

GROUP BYは、複数の行を1つにまとめる処理です。

ウィンドウ関数は、行を残したまま順位や集計値を追加する処理です。

ここが非常に重要です。

今回使うサンプルテーブル

今回は、商品ごとの売上を記録するsalesテーブルを使います。

CREATE TABLE sales (
    sale_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    category_name VARCHAR(100) NOT NULL,
    sales_amount INT NOT NULL
);

サンプルデータを入れます。

INSERT INTO sales (product_name, category_name, sales_amount) VALUES
('ノートPC', '家電', 300000),
('スマートフォン', '家電', 300000),
('イヤホン', '家電', 120000),
('キーボード', '家電', 80000),
('デスクチェア', '家具', 200000),
('デスク', '家具', 200000),
('本棚', '家具', 150000),
('ライト', '家具', 70000),
('Java入門書', '書籍', 90000),
('SQL入門書', '書籍', 90000),
('Spring Boot本', '書籍', 120000);

データのイメージです。

sale_idproduct_namecategory_namesales_amount
1ノートPC家電300000
2スマートフォン家電300000
3イヤホン家電120000
4キーボード家電80000
5デスクチェア家具200000
6デスク家具200000
7本棚家具150000
8ライト家具70000
9Java入門書書籍90000
10SQL入門書書籍90000
11Spring Boot本書籍120000

このデータには、同じ売上金額の商品があります。

たとえば、ノートPCとスマートフォンはどちらも300000です。

デスクチェアとデスクも同じ200000です。

同点の扱いを学ぶには、ちょうどよいデータです。

ランキングの基本構文

ウィンドウ関数でランキングを出す基本構文は、次の形です。

ランキング関数() OVER (
    ORDER BY 並び替えたい列 DESC
)

実際のSQLでは、次のように書きます。

SELECT
    product_name,
    sales_amount,
    ROW_NUMBER() OVER (
        ORDER BY sales_amount DESC
    ) AS ranking
FROM sales;

ROW_NUMBER()がランキング関数です。

OVER句の中で、どの順番で順位を付けるかを指定します。

ORDER BY sales_amount DESCは、売上金額が大きい順という意味です。

部分意味
ROW_NUMBER()行番号を付ける関数
OVERどの範囲・順番で関数を使うか指定する
ORDER BY sales_amount DESC売上金額が高い順に並べて順位を付ける
AS ranking順位列に名前を付ける

MySQL公式ドキュメントでは、ウィンドウ関数を使うには関数呼び出しの後にOVER句を含めると説明されています。また、ORDER BYを含めない場合、ROW_NUMBERの番号付けは非決定的になるため、順位を出すときはORDER BYを指定するのが基本です。

ROW_NUMBERでランキングを出す

まず、ROW_NUMBERを使ってランキングを出します。

SELECT
    product_name,
    category_name,
    sales_amount,
    ROW_NUMBER() OVER (
        ORDER BY sales_amount DESC
    ) AS row_number_rank
FROM sales;




結果のイメージです。

product_namecategory_namesales_amountrow_number_rank
ノートPC家電3000001
スマートフォン家電3000002
デスクチェア家具2000003
デスク家具2000004
本棚家具1500005
イヤホン家電1200006
Spring Boot本書籍1200007
Java入門書書籍900008
SQL入門書書籍900009
キーボード家電8000010
ライト家具7000011

ROW_NUMBERは、同じ売上金額でも必ず別々の番号を振ります。

ノートPCとスマートフォンは同じ300000ですが、1位と2位に分かれています。

MySQL公式ドキュメントでも、ROW_NUMBERはパーティション内の現在行の番号を返し、同じ値を持つピアにも異なる行番号を割り当てると説明されています。

ROW_NUMBERは「整理番号」と考える

ROW_NUMBERは、厳密には「同点を考慮した順位」というより、並べた結果に上から順番に番号を振る関数です。

たとえるなら、人気ランキングというより、受付番号に近いです。

同じ点数の人がいても、列に並んだ順番で1番、2番、3番と番号を付けます。

特徴ROW_NUMBER
同点の扱い同点でも別番号
順位の重複しない
番号の飛びしない
向いている用途上から順に1件ずつ番号を振りたい場合

同点でも必ず1件だけ1位にしたい場合は、ROW_NUMBERが向いています。

ただし、同点の順番を安定させるためには、ORDER BYに追加条件を入れることが大切です。

ROW_NUMBERで同点時の順番を安定させる

次のSQLでは、sales_amountだけで並べています。

ROW_NUMBER() OVER (
    ORDER BY sales_amount DESC
)




同じsales_amountの行がある場合、どちらが先になるかが分かりにくくなることがあります。

そこで、sale_idもORDER BYに追加します。

SELECT
    sale_id,
    product_name,
    sales_amount,
    ROW_NUMBER() OVER (
        ORDER BY sales_amount DESC, sale_id ASC
    ) AS row_number_rank
FROM sales;




このSQLでは、まず売上金額が高い順に並べます。

同じ売上金額なら、sale_idが小さい順に並べます。

ORDER BY sales_amount DESC, sale_id ASC

ランキングSQLでは、同点時の並び順まで決めておくと結果が安定します。

新人エンジニアは、ROW_NUMBERを使うときは「同点のとき何で並べるか」まで考えてください。

RANKで同点を同じ順位にする

次に、RANKを使います。

RANKは、同じ値の行に同じ順位を付けます。

SELECT
    product_name,
    category_name,
    sales_amount,
    RANK() OVER (
        ORDER BY sales_amount DESC
    ) AS rank_no
FROM sales;




結果のイメージです。

product_namecategory_namesales_amountrank_no
ノートPC家電3000001
スマートフォン家電3000001
デスクチェア家具2000003
デスク家具2000003
本棚家具1500005
イヤホン家電1200006
Spring Boot本書籍1200006
Java入門書書籍900008
SQL入門書書籍900008
キーボード家電8000010
ライト家具7000011

ノートPCとスマートフォンは同じ300000なので、どちらも1位です。

ただし、その次は2位ではなく3位になります。

これがRANKの特徴です。

同点が2人いると、その人数分だけ次の順位が飛びます。

たとえば、テストで1位が2人いたら、次の人は3位になるイメージです。

RANKは「競技の順位」と考える

RANKは、スポーツ大会やテスト順位に近いです。

同じ点数なら同じ順位。

その次の順位は飛びます。

特徴RANK
同点の扱い同じ順位
順位の重複する
番号の飛びする
向いている用途同点を同じ順位として扱うランキング

売上ランキングや成績ランキングで、「同じ点数なら同じ順位にしたい」という場合はRANKが向いています。

DENSE_RANKで順位を飛ばさない

RANKと似ている関数に、DENSE_RANKがあります。

DENSE_RANKも同点を同じ順位にします。

ただし、次の順位が飛びません。

SELECT
    product_name,
    category_name,
    sales_amount,
    DENSE_RANK() OVER (
        ORDER BY sales_amount DESC
    ) AS dense_rank_no
FROM sales;




結果のイメージです。

product_namecategory_namesales_amountdense_rank_no
ノートPC家電3000001
スマートフォン家電3000001
デスクチェア家具2000002
デスク家具2000002
本棚家具1500003
イヤホン家電1200004
Spring Boot本書籍1200004
Java入門書書籍900005
SQL入門書書籍900005
キーボード家電800006
ライト家具700007

ノートPCとスマートフォンは1位です。

次のデスクチェアとデスクは2位です。

順位が飛んでいません。

MySQL公式ドキュメントでは、DENSE_RANKはパーティション内の現在行のランクをギャップなしで返し、同じ値のピアには同じランクを割り当てると説明されています。

ROW_NUMBER、RANK、DENSE_RANKの違い

ここで3つの違いを整理します。

関数同点の扱い順位の飛び
ROW_NUMBER同点でも別順位なし1, 2, 3, 4
RANK同点は同じ順位あり1, 1, 3, 3, 5
DENSE_RANK同点は同じ順位なし1, 1, 2, 2, 3

この違いは、ランキング機能を作るときにとても重要です。

どれを使うべきかは、業務ルールによって変わります。

やりたいことおすすめ関数
同点でも必ず1件ずつ順位を付けたいROW_NUMBER
同点は同じ順位にし、次の順位は飛ばしたいRANK
同点は同じ順位にし、次の順位を飛ばしたくないDENSE_RANK

新人エンジニアは、まず「同点をどう扱うか」を確認してから関数を選びましょう。

SQLを書く前に、ランキングのルールを決めろ!

PARTITION BYでカテゴリ別ランキングを出す

次に、カテゴリごとのランキングを出します。

カテゴリ別ランキングでは、PARTITION BYを使います。

SELECT
    product_name,
    category_name,
    sales_amount,
    RANK() OVER (
        PARTITION BY category_name
        ORDER BY sales_amount DESC
    ) AS category_rank
FROM sales;




PARTITION BY category_nameは、「カテゴリごとに順位を付ける」という意味です。

家電は家電の中でランキング。

家具は家具の中でランキング。

書籍は書籍の中でランキング。

このように、グループごとの順位を出せます。

MySQL公式ドキュメントでは、OVER句内のPARTITION BYはクエリ行をグループに分割し、ウィンドウ関数の結果は現在行が属するパーティションの行に基づいて計算されると説明されています。

カテゴリ別ランキングの結果イメージ

product_namecategory_namesales_amountcategory_rank
ノートPC家電3000001
スマートフォン家電3000001
イヤホン家電1200003
キーボード家電800004
デスクチェア家具2000001
デスク家具2000001
本棚家具1500003
ライト家具700004
Spring Boot本書籍1200001
Java入門書書籍900002
SQL入門書書籍900002

カテゴリごとに1位が存在しています。

全体ランキングではなく、カテゴリの中での順位です。

たとえるなら、学校全体の成績順位ではなく、クラスごとの順位です。

PARTITION BYを使わない場合と使う場合

PARTITION BYを使わない場合です。

RANK() OVER (
    ORDER BY sales_amount DESC
)




全体で1つのランキングになります。

PARTITION BYを使う場合です。

RANK() OVER (
    PARTITION BY category_name
    ORDER BY sales_amount DESC
)




category_nameごとにランキングされます。

書き方ランキングの範囲
PARTITION BYなし全体ランキング
PARTITION BY category_nameカテゴリ別ランキング

PARTITION BYは、ランキングの部屋を分けるイメージです。

家電の部屋、家具の部屋、書籍の部屋を作り、それぞれの部屋の中で順位を付けます。

カテゴリごとの上位3件を取得する

ランキングを出したあと、「上位3件だけ欲しい」ということがあります。

たとえば、カテゴリごとの売上上位3件を取得したい場合です。

この場合、ウィンドウ関数で付けた順位を外側のSELECTで絞り込みます。

SELECT
    product_name,
    category_name,
    sales_amount,
    category_rank
FROM (
    SELECT
        product_name,
        category_name,
        sales_amount,
        ROW_NUMBER() OVER (
            PARTITION BY category_name
            ORDER BY sales_amount DESC, sale_id ASC
        ) AS category_rank
    FROM sales
) AS ranked_sales
WHERE category_rank <= 3
ORDER BY category_name, category_rank;




内側のSELECTで、カテゴリごとに順位を付けています。

外側のSELECTで、category_rankが3以下の行だけを取り出しています。

WHERE category_rank <= 3




ここでサブクエリを使っている理由は、ウィンドウ関数の実行タイミングにあります。

MySQL公式ドキュメントでは、ウィンドウ関数はSELECTリストとORDER BY句でのみ使用でき、WHERE、GROUP BY、HAVINGの処理後にウィンドウ処理が行われると説明されています。

つまり、同じSELECT内のWHEREで直接category_rankを使って絞り込むことはできません。

一度サブクエリで順位を付けてから、外側でWHEREを使う必要があります。

CTEで上位3件を取得する

MySQL 8では、WITH句、つまりCTEを使って書くこともできます。

CTEは、Common Table Expressionの略です。

一時的な名前付きの検索結果を作るようなイメージです。

WITH ranked_sales AS (
    SELECT
        product_name,
        category_name,
        sales_amount,
        ROW_NUMBER() OVER (
            PARTITION BY category_name
            ORDER BY sales_amount DESC, sale_id ASC
        ) AS category_rank
    FROM sales
)
SELECT
    product_name,
    category_name,
    sales_amount,
    category_rank
FROM ranked_sales
WHERE category_rank <= 3
ORDER BY category_name, category_rank;




サブクエリよりも、処理の段階が見えやすくなります。

まず ranked_sales で順位を付ける
        ↓
その結果から category_rank が3以下を取り出す

複雑なランキングSQLでは、CTEを使うと読みやすくなることがあります。

ROW_NUMBERで上位3件にする場合の注意

ROW_NUMBERを使って上位3件を取ると、各カテゴリから必ず最大3行だけ取れます。

ROW_NUMBER() OVER (
    PARTITION BY category_name
    ORDER BY sales_amount DESC, sale_id ASC
)




ただし、同点がある場合でも、3件で切られます。

たとえば、3位が同点で複数件ある場合でも、ROW_NUMBERなら3件までです。

同点も含めて上位3位まで出したい場合は、RANKやDENSE_RANKを使う必要があります。

目的使う関数
カテゴリごとに必ず3件までにしたいROW_NUMBER
同点の3位も含めたいRANKまたはDENSE_RANK

ここも業務ルール次第です。

「3件」なのか、「3位まで」なのかを確認しましょう。

似ているようで、結果が変わります。

RANKで上位3位までを取得する

同点を含めて上位3位まで出したい場合は、RANKを使います。

WITH ranked_sales AS (
    SELECT
        product_name,
        category_name,
        sales_amount,
        RANK() OVER (
            PARTITION BY category_name
            ORDER BY sales_amount DESC
        ) AS category_rank
    FROM sales
)
SELECT
    product_name,
    category_name,
    sales_amount,
    category_rank
FROM ranked_sales
WHERE category_rank <= 3
ORDER BY category_name, category_rank, sales_amount DESC;




RANKでは、同点が同じ順位になります。

そのため、3位が複数件あれば、3件を超えて結果が返ることがあります。

これはバグではありません。

「3件まで」ではなく「3位まで」というルールだからです。

ROW_NUMBERとRANKの選び方

上位ランキングでは、ROW_NUMBERとRANKの選び方がとても重要です。

要件SQL上の考え方おすすめ
上位10件を表示したい件数で切るROW_NUMBER
上位10位まで表示したい順位で切るRANKまたはDENSE_RANK
同点でも片方だけ選びたい並び順を追加して1件ずつ番号付けROW_NUMBER
同点は同順位として全員表示したい同点を同じ順位にするRANK

ランキング画面を作るときは、SQLを書く前に仕様を確認してください。

「上位3件」なのか。

「上位3位」なのか。

この違いを確認しないと、後で画面の件数が想定と違うと言われることがあります。

ORDER BYはOVERの中と最後の両方に書くことがある

ウィンドウ関数では、ORDER BYが2種類出てくることがあります。

SELECT
    product_name,
    sales_amount,
    RANK() OVER (
        ORDER BY sales_amount DESC
    ) AS rank_no
FROM sales
ORDER BY rank_no, product_name;




OVERの中のORDER BYは、順位を決めるための並び順です。

最後のORDER BYは、最終的な表示順です。

ORDER BYの場所意味
OVER内のORDER BY順位計算のための順番
SELECT文最後のORDER BY結果表示のための順番

この2つを混同しないでください。

OVER内のORDER BYは「採点順」。

最後のORDER BYは「一覧表の表示順」です。

売上合計を出してからランキングする

実務では、明細データをそのままランキングするのではなく、商品ごとに売上を集計してからランキングしたいことがあります。

たとえば、sales_detailsのような売上明細テーブルがある場合です。

CREATE TABLE sales_details (
    detail_id INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    category_name VARCHAR(100) NOT NULL,
    sales_amount INT NOT NULL,
    sold_at DATE NOT NULL
);




商品ごとの売上合計を出してからランキングするSQLです。

WITH product_sales AS (
    SELECT
        product_name,
        category_name,
        SUM(sales_amount) AS total_sales
    FROM sales_details
    GROUP BY product_name, category_name
),
ranked_product_sales AS (
    SELECT
        product_name,
        category_name,
        total_sales,
        RANK() OVER (
            ORDER BY total_sales DESC
        ) AS sales_rank
    FROM product_sales
)
SELECT
    product_name,
    category_name,
    total_sales,
    sales_rank
FROM ranked_product_sales
ORDER BY sales_rank, product_name;




このSQLでは、2段階に分けています。

1. product_salesで商品ごとの売上合計を作る
2. ranked_product_salesで売上合計にランキングを付ける

集計してからランキングする。

実務では、この形がよく出てきます。

月別ランキングを出す

月ごとのランキングを出したい場合は、PARTITION BYに年月を使います。

WITH monthly_product_sales AS (
    SELECT
        DATE_FORMAT(sold_at, '%Y-%m') AS sales_month,
        product_name,
        SUM(sales_amount) AS total_sales
    FROM sales_details
    GROUP BY
        DATE_FORMAT(sold_at, '%Y-%m'),
        product_name
),
ranked_monthly_sales AS (
    SELECT
        sales_month,
        product_name,
        total_sales,
        RANK() OVER (
            PARTITION BY sales_month
            ORDER BY total_sales DESC
        ) AS monthly_rank
    FROM monthly_product_sales
)
SELECT
    sales_month,
    product_name,
    total_sales,
    monthly_rank
FROM ranked_monthly_sales
ORDER BY sales_month, monthly_rank;




このSQLでは、月ごとに売上合計を出し、月ごとのランキングを付けています。

PARTITION BY sales_month

この指定により、2026-01は2026-01の中でランキング、2026-02は2026-02の中でランキングされます。

月ごとのランキング、カテゴリごとのランキング、部署ごとのランキングなど、グループ別ランキングではPARTITION BYが活躍します。

DAOでランキングSQLを使う例

JavaのDAOでランキングSQLを使う場合も、普通のSELECTと同じようにResultSetから値を取り出します。

まず、DTOを作ります。

public class ProductRankingDto {

    private String productName;
    private String categoryName;
    private int salesAmount;
    private int ranking;

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public int getSalesAmount() {
        return salesAmount;
    }

    public void setSalesAmount(int salesAmount) {
        this.salesAmount = salesAmount;
    }

    public int getRanking() {
        return ranking;
    }

    public void setRanking(int ranking) {
        this.ranking = ranking;
    }
}




DAOの例です。

public List<ProductRankingDto> findProductRanking() {

    String sql =
            "SELECT "
          + "    product_name, "
          + "    category_name, "
          + "    sales_amount, "
          + "    RANK() OVER ( "
          + "        ORDER BY sales_amount DESC "
          + "    ) AS ranking "
          + "FROM sales "
          + "ORDER BY ranking, product_name";

    List<ProductRankingDto> rankingList = new ArrayList<>();

    try (Connection con = getConnection();
         PreparedStatement ps = con.prepareStatement(sql);
         ResultSet rs = ps.executeQuery()) {

        while (rs.next()) {
            ProductRankingDto dto = new ProductRankingDto();

            dto.setProductName(rs.getString("product_name"));
            dto.setCategoryName(rs.getString("category_name"));
            dto.setSalesAmount(rs.getInt("sales_amount"));
            dto.setRanking(rs.getInt("ranking"));

            rankingList.add(dto);
        }

        return rankingList;

    } catch (SQLException e) {
        throw new RuntimeException("商品ランキングの取得に失敗しました。", e);
    }
}




rankingという別名を付けているので、Java側ではrs.getInt("ranking")で取得できます。

RANK() OVER (
    ORDER BY sales_amount DESC
) AS ranking




SQLで計算したランキング列も、ResultSetでは普通の列のように扱えます。

よくあるミス

MySQL 5.7で使おうとしてしまう

ウィンドウ関数はMySQL 8で使う前提です。

MySQL 5.7ではROW_NUMBERやRANKは基本的に使えません。

エラーになる場合は、まずMySQLのバージョンを確認してください。

SELECT VERSION();




研修環境や古い現場では、MySQL 5.7が残っていることがあります。

SQLが正しくても、DBバージョンが対応していなければ動きません。

OVERを書き忘れる

悪い例です。

SELECT
    product_name,
    RANK() AS ranking
FROM sales;

良い例です。

SELECT
    product_name,
    RANK() OVER (
        ORDER BY sales_amount DESC
    ) AS ranking
FROM sales;


ROW_NUMBER、RANK、DENSE_RANKにはOVER句が必要です。

ORDER BYを書かずにROW_NUMBERを使う

悪い例です。

SELECT
    product_name,
    ROW_NUMBER() OVER () AS ranking
FROM sales;




この書き方でも番号は付く場合があります。

しかし、どの順番で番号が付くのかがランキングとして分かりません。

良い例です。

SELECT
    product_name,
    ROW_NUMBER() OVER (
        ORDER BY sales_amount DESC
    ) AS ranking
FROM sales;




ランキングでは、何を基準に順位を付けるのかを明確にしてください。

WHEREでランキング列を直接絞ろうとする

悪い例です。

SELECT
    product_name,
    sales_amount,
    ROW_NUMBER() OVER (
        ORDER BY sales_amount DESC
    ) AS ranking
FROM sales
WHERE ranking <= 3;




この書き方はできません。

rankingはSELECTで作られる列であり、WHEREの時点ではまだ使えません。

良い例です。

SELECT
    product_name,
    sales_amount,
    ranking
FROM (
    SELECT
        product_name,
        sales_amount,
        ROW_NUMBER() OVER (
            ORDER BY sales_amount DESC
        ) AS ranking
    FROM sales
) AS ranked_sales
WHERE ranking <= 3;




ランキングで絞り込みたい場合は、サブクエリやCTEを使いましょう。

同点の扱いを決めずに実装する

ランキング機能では、同点の扱いが重要です。

売上が同じ商品は同順位にするのか
同順位にした場合、次の順位を飛ばすのか
画面には必ず10件だけ表示するのか
10位が複数ある場合は全件表示するのか




このような仕様を決めずにSQLを書くと、後で手戻りになります。

ランキングSQLでは、同点ルールを必ず確認してください。

パフォーマンス上の注意

ウィンドウ関数は便利ですが、大量データに対して使う場合は注意が必要です。

ORDER BYやPARTITION BYに使う列には、インデックスを検討します。

CREATE INDEX idx_sales_category_amount
ON sales (category_name, sales_amount);




ただし、インデックスを作れば必ず速くなるわけではありません。

データ量、条件、ORDER BY、GROUP BY、実行計画によって変わります。

実務では、EXPLAINで実行計画を確認します。

EXPLAIN
SELECT
    product_name,
    category_name,
    sales_amount,
    RANK() OVER (
        PARTITION BY category_name
        ORDER BY sales_amount DESC
    ) AS category_rank
FROM sales;




新人エンジニアの段階では、まず正しいSQLを書けることが大切です。

その次に、インデックス、EXPLAIN、実行計画を学ぶとよいです。

新人エンジニアが最初に覚えるべき型

まず、全体ランキングです。

SELECT
    列名,
    RANK() OVER (
        ORDER BY ランキング基準列 DESC
    ) AS ranking
FROM テーブル名;




次に、グループ別ランキングです。

SELECT
    列名,
    グループ列,
    RANK() OVER (
        PARTITION BY グループ列
        ORDER BY ランキング基準列 DESC
    ) AS ranking
FROM テーブル名;




最後に、上位N件の取得です。

WITH ranked_data AS (
    SELECT
        列名,
        ROW_NUMBER() OVER (
            PARTITION BY グループ列
            ORDER BY ランキング基準列 DESC
        ) AS ranking
    FROM テーブル名
)
SELECT
    *
FROM ranked_data
WHERE ranking <= N;




最初はこの3パターンを覚えてください。

全体ランキング。

グループ別ランキング。

上位N件。

この3つが使えると、ランキングSQLの基本はかなり書けます。

まとめ

MySQL 8のウィンドウ関数を使うと、ランキングをSQLだけで分かりやすく出せます。

ROW_NUMBER、RANK、DENSE_RANKは似ていますが、同点の扱いが違います。

関数特徴使いどころ
ROW_NUMBER同点でも別々の番号を付ける上位10件など件数で切りたい
RANK同点は同じ順位、次の順位は飛ぶ一般的な競技順位に近いランキング
DENSE_RANK同点は同じ順位、次の順位は飛ばない順位番号を詰めたいランキング
PARTITION BYグループごとに順位を付けるカテゴリ別、部署別、月別ランキング
ORDER BY順位を決める基準売上順、点数順、日付順など

一言でまとめるなら、ウィンドウ関数は「行を消さずに、各行へ順位を付けるSQLの機能」です。

新人エンジニアは、まず次の流れを覚えてください。

ランキングの基準を決める

同点の扱いを決める

ROW_NUMBER、RANK、DENSE_RANKから選ぶ

全体ランキングならPARTITION BYなし

カテゴリ別ランキングならPARTITION BYを使う

上位N件で絞るならサブクエリまたはCTEを使う





SQLを書く前に、まず「同点をどう扱うか」「全体ランキングかグループ別ランキングか」「上位N件なのか上位N位なのか」を確認してください。

今後の学習では、ROW_NUMBER、RANK、DENSE_RANK、PARTITION BY、ORDER BY、CTE、サブクエリ、GROUP BY後のランキング、EXPLAINによる実行計画確認を順番に学ぶとよいです。まずは今回のsalesテーブルを使い、全体ランキング、カテゴリ別ランキング、カテゴリ別上位3件を自分で書いてみましょう!

最後までお読みいただきありがとうございます。