こんにちは!
9月入社、デジタルエンジニアリング部 SE課 4G 菅野です。
今回はSQLの中でも知っていると得するかもしれない「ウィンドウ関数」についてのテックブログです。
ちょっと難しそう...と思うかもしれませんが、累積計算やランキングなどに役立つ便利な関数です。
ウィンドウ関数を学んで、SQLの楽しみを広げましょう!
ウィンドウ関数の中でよく出てくるのが、PARTITION BY です。
これは「データをどの単位で区切って集計するか」を指定するものです。
例:
PARTITION BY store_name
→ 店舗ごとに区切って、各店舗ごとに累積やランキングを計算する
PARTITION BY product_name
→ 商品ごとに区切って、商品の売上推移を分析する
「パーティション=分析単位のグループ」と考えるとわかりやすいと思います!
ウィンドウ関数に入る前に、まずは「GROUP BY」と「PARTITION BY」の違いを整理します。
両者とも「指定の区分ごとにレコードをまとめる」という点では共通していますが、そのまとめ方が異なります。
GROUP BY
→ 区分ごとに集約し、結果は「1行にまとめられる」
PARTITION BY
→ 区分ごとにグループ化するが、行は保持したままで、それぞれの行に対して集計結果を付与できる
つまり、
GROUP BYがAとBのレコードをそれぞれまとめにしてしまうのに対し、PARTITION BYはAとBを別々に認識しつつ、各行は認識できる状態です。
※画像:菅野作成
ウィンドウ関数は、PARTITION BYを利用して、レコードをパーティションで区切りつつ、各レコードを認識している状態で集計処理することができます。
パーティションとは、ウィンドウ関数を適用する際に、どの範囲のデータに対して計算を行うかを指定するために使われます。
for文でカーソルを当てながら処理していくイメージで良いと思います。
ウィンドウ関数を使うことにより、累積計算や移動平均、ランキングをつけたりなどが容易に行えるようになります。
便利なウィンドウ関数ですが、大きなデータを扱うときは注意も必要です。
CREATE TABLE Sales (
store_name VARCHAR(100) NOT NULL,
product_name VARCHAR(100) NOT NULL,
amount INT NOT NULL,
sale_date DATE NOT NULL
);
INSERT INTO Sales (store_name, product_name, amount, sale_date)
VALUES
('Tokyo', 'Apples', 1200, '2024-10-01'),
('Tokyo', 'Bananas', 800, '2024-10-03'),
('Tokyo', 'Oranges', 1500, '2024-10-05'),
('Osaka', 'Apples', 600, '2024-10-02'),
('Osaka', 'Bananas', 700, '2024-10-03'),
('Osaka', 'Oranges', 900, '2024-10-06'),
('Osaka', 'Apples', 1000, '2024-10-08');
SELECT store_name,
SUM(amount) AS total_sales
FROM Sales
GROUP BY store_name;
store_name | total_sales |
---|---|
Osaka | 3200 |
Tokyo | 3500 |
例:店舗ごとに日付順に並べて、その時点までの累積売上を出す
SELECT store_name,
sale_date,
amount AS daily_sales,
SUM(amount) OVER(PARTITION BY store_name ORDER BY sale_date) AS cum_sales
FROM Sales;
store_name | sale_date | daily_sales | cum_sales |
---|---|---|---|
Osaka | 2024-10-02 | 600 | 600 |
Osaka | 2024-10-03 | 700 | 1300 |
Osaka | 2024-10-06 | 900 | 2200 |
Osaka | 2024-10-08 | 1000 | 3200 |
Tokyo | 2024-10-01 | 1200 | 1200 |
Tokyo | 2024-10-03 | 800 | 2000 |
Tokyo | 2024-10-05 | 1500 | 3500 |
SELECT store_name,
product_name,
amount,
RANK() OVER(PARTITION BY store_name ORDER BY amount DESC) AS ranking
FROM Sales;
RANK()はランキング付けすることができる関数
store_name | product_name | amount | ranking |
---|---|---|---|
Osaka | Apples | 1000 | 1 |
Osaka | Oranges | 900 | 2 |
Osaka | Bananas | 700 | 3 |
Osaka | Apples | 600 | 4 |
Tokyo | Oranges | 1500 | 1 |
Tokyo | Apples | 1200 | 2 |
Tokyo | Bananas | 800 | 3 |
SELECT store_name,
product_name,
amount,
ROUND(
amount * 100.0 / SUM(amount) OVER(PARTITION BY store_name), 2
) AS sales_ratio
FROM Sales;
store_name | product_name | amount | sales_ratio |
---|---|---|---|
Osaka | Apples | 600 | 18.75 |
Osaka | Bananas | 700 | 21.88 |
Osaka | Oranges | 900 | 28.13 |
Osaka | Apples | 1000 | 31.25 |
Tokyo | Apples | 1200 | 34.29 |
Tokyo | Bananas | 800 | 22.86 |
Tokyo | Oranges | 1500 | 42.86 |
「ROWS BETWEEN 2 PRECEDING AND CURRENT ROW」で前2行と今の行を処理することを示しています。
SELECT store_name,
sale_date,
amount AS daily_sales,
ROUND(
AVG(amount) OVER(
PARTITION BY store_name
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
), 2
) AS moving_avg
FROM Sales;
store_name | sale_date | daily_sales | moving_avg |
---|---|---|---|
Osaka | 2024-10-02 | 600 | 600.00 |
Osaka | 2024-10-03 | 700 | 650.00 |
Osaka | 2024-10-06 | 900 | 733.33 |
Osaka | 2024-10-08 | 1000 | 866.67 |
Tokyo | 2024-10-01 | 12000 | 1200.00 |
Tokyo | 2024-10-03 | 800 | 1000.00 |
Tokyo | 2024-10-05 | 1500 | 1166.67 |
今回の記事では、ウィンドウ関数を通して以下のポイントを学びました。
GROUP BYとPARTITION BYの違い
GROUP BY → グループごとに1行にまとめて集計する
PARTITION BY → グループごとに区切りつつ、行を保持したまま集計結果を付与できる
→ 「全体をまとめたいのか、それとも行を残したまま分析したいのか」で使い分ける
ウィンドウ関数でできること
パフォーマンスの注意点
GROUP BYが「まとめて終わり」なら、PARTITION BYを使ったウィンドウ関数は「まとめつつ分析を続けられる」強力なツールです。
ちょっと気味が悪かったOVER(…)の構文も、
「区切る(PARTITION)」「並べる(ORDER)」「カーソルで処理する(OVER)」という流れを意識すれば自然に理解できます。
SQLをデータ抽出だけで終わらせず、分析や可視化する集計を深めたいときにぜひ使ってみてください!
最後までお読みいただき、ありがとうございました!!!