始める前に、何千行もの売上テーブルを扱ってると想像してみて。君のタスクは、各カテゴリで誰がナンバーワンのセールスなのか、2番目は誰か、みたいなのを決めること。あるいは、サンプル内の全行に番号を振って順番を追いたい時もあるよね。こういうのは全部、ウィンドウ関数を使えば超簡単!
ウィンドウ関数っていうのは、SQLでデータセットの一部(これを「ウィンドウ」って呼ぶ)に対して動作する関数のこと。集約関数(例えば SUM() や AVG() みたいなやつ)は行を1つにまとめちゃうけど、ウィンドウ関数は行をそのまま残して、計算した値を追加してくれるんだ。
集約関数との違い
集約関数はデータを「圧縮」して、行をグループ化する:
SELECT department, COUNT(*)
FROM employees
GROUP BY department;
結果:部門の数だけ、ほんの数行しか返ってこない。
ウィンドウ関数と比べてみよう — こっちは行がそのまま残るけど、新しいフィールドが追加される。例えば ROW_NUMBER() みたいなやつ:
SELECT employee_name, department,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_within_department
FROM employees;
これだと、元の行は全部そのままで、rank_within_department っていう追加カラムができて、各社員に部門内での番号が割り振られるよ。
基本的なウィンドウ関数
OVER() の構文
どのウィンドウ関数でも一番大事なのは、魔法の言葉 OVER()。これが、どんな「ウィンドウ」(データの範囲)で関数を動かすかを決めるんだ。OVER() の中では、グループ分け(PARTITION BY)や 並び順(ORDER BY)を指定できるよ。
基本の構文:
<ウィンドウ関数>() OVER (
[PARTITION BY <グループ>]
[ORDER BY <順序>]
)
各パーツの意味:
PARTITION BY: 行をグループに分ける。例えば「部門ごとに分けて」みたいな感じ。ORDER BY: 行の処理順を指定する。例えば「給料が高い順に社員を並べて」みたいな。
ROW_NUMBER() 関数
ROW_NUMBER() 関数は、指定した「ウィンドウ」内で1から順番に行に番号を振るよ。仮のテーブルや、レコードの順番を決めたい時に便利。
例。sales(売上)テーブル:
| id | product_category | seller_name | revenue |
|---|---|---|---|
| 1 | Electronics | Alice | 1000 |
| 2 | Electronics | Bob | 850 |
| 3 | Furniture | Alice | 1200 |
| 4 | Furniture | Charlie | 1100 |
| 5 | Electronics | Dana | 750 |
クエリ:
SELECT seller_name, product_category, revenue,
ROW_NUMBER() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS row_number
FROM sales;
結果:
| seller_name | product_category | revenue | row_number |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
| Alice | Furniture | 1200 | 1 |
| Charlie | Furniture | 1100 | 2 |
どう動くか:
- データは
product_categoryごとにグループ分けされる。 - 各グループは
revenue(売上)の降順で並べ替えられる。 - 各グループ内の行に番号が振られる。
RANK() 関数
RANK() 関数は、行をランキングするために使うよ。ROW_NUMBER() と違って、同じ値があるときは同じランクになるし、値がかぶった分だけランク番号が飛ぶ。
例:
SELECT seller_name, product_category, revenue,
RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS rank
FROM sales;
結果:
| seller_name | product_category | revenue | rank |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
| Alice | Furniture | 1200 | 1 |
| Charlie | Furniture | 1100 | 2 |
DENSE_RANK() 関数
DENSE_RANK() は RANK() とほぼ同じだけど、同じ値があってもランク番号が飛ばないのが違い。
例。同じ売上のレコードを追加してみる:
| id | product_category | seller_name | revenue |
|---|---|---|---|
| 6 | Electronics | Alice | 1000 |
| 7 | Electronics | Dana | 750 |
クエリ:
SELECT seller_name, product_category, revenue,
DENSE_RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS dense_rank
FROM sales;
結果:
| seller_name | product_category | revenue | dense_rank |
|---|---|---|---|
| Alice | Electronics | 1000 | 1 |
| Alice | Electronics | 1000 | 1 |
| Bob | Electronics | 850 | 2 |
| Dana | Electronics | 750 | 3 |
使い方例:行のナンバリング
タスク:orders テーブルの全注文を日付順に番号を振る。
SELECT order_id, customer_name, order_date,
ROW_NUMBER() OVER (ORDER BY order_date) AS order_number
FROM orders;
結果:注文リストが、実行順に番号付きで出てくる。
使い方例:各カテゴリのトップ3セラー
タスク:各商品カテゴリでトップ3のセラーを決める。
WITH ranked_sales AS (
SELECT seller_name, product_category, revenue,
RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS rank
FROM sales
)
SELECT seller_name, product_category, revenue
FROM ranked_sales
WHERE rank <= 3;
使い方例:同じ成績の検出
タスク:各カテゴリで同じ売上のセラーがいるかどうか調べる。
SELECT seller_name, product_category, revenue,
DENSE_RANK() OVER (PARTITION BY product_category ORDER BY revenue DESC) AS dense_rank
FROM sales;
これで、同じ値で「くっついてる」ランクが見えるようになるよ。
GO TO FULL VERSION