OVER()は、どの行セットにウィンドウ関数を適用するかをSQLに伝える命令だよ。 ざっくり言うと、ウィンドウ関数を使うための「ウィンドウ」(データの範囲)を決める方法。例えば、人がいっぱい入ってる部屋があって、床の1平方メートルごとに何人いるか数えたいとする。OVER()は、部屋のどの部分に注目するかを指定する感じ。他の言い方をすると、どの行セットで関数が動くかを決めるんだ。
OVER()オペレーターは、ウィンドウ関数と一緒に使う専用で、テーブルの1つまたは複数の行に対してグループ化せずに操作できる。
シンタックス:
ウィンドウ関数() OVER (
[PARTITION BY ...]
[ORDER BY ...]
[ROWS/RANGE ...]
)
各パーツの意味:
PARTITION BY— データセットを論理的なグループに分けるORDER BY— 各グループ内での行の順番を決めるROWS/RANGE— 「ウィンドウ」のサイズを指定(例:現在の行+次の1行)
例:パラメータなしのOVER()
OVER()を追加パラメータなしで使うと、その前に書いた関数が全データセットに対して動くって意味になる。
SELECT
employee_id,
salary,
ROW_NUMBER() OVER () AS row_num -- ROW_NUMBER()は結果の全行に適用される
FROM employees;
何が起きてる?
ROW_NUMBER()は各行にユニークな番号を振る。OVER()に何も指定しないと、employeesテーブルの全行が1つのまとまりとして扱われる。
結果:
| employee_id | salary | row_num |
|---|---|---|
| 1 | 50000 | 1 |
| 2 | 60000 | 2 |
| 3 | 55000 | 3 |
PARTITION BYでグループを指定する
OK、今度は会社全体じゃなくて、各部署ごとに社員をナンバリングしたいとしよう。ここでPARTITION BYの出番!
OVER()の中のPARTITION BYは、データをグループ(または「パーティション」)に分ける。各グループごとに関数が別々に値を計算する。つまり、ROW_NUMBER()がウェイターだったら、各「テーブル」(パーティション)ごとに番号を振り直すイメージ。
例:PARTITION BYを使う
SELECT
department_id,
employee_id,
salary,
ROW_NUMBER() OVER (PARTITION BY department_id) AS row_num
FROM employees;
何が起きてる?
employeesテーブルのデータがdepartment_idの値ごとにグループ分けされる。- 各グループ内で
ROW_NUMBER()で順番が振られる。
結果:
| department_id | employee_id | salary | row_num |
|---|---|---|---|
| 1 | 1 | 50000 | 1 |
| 1 | 3 | 55000 | 2 |
| 2 | 2 | 60000 | 1 |
ORDER BYで順番を指定する
今度はもうちょっと構造を加えよう。例えば、単に行をナンバリングするだけじゃなくて、給料が高い順に番号を振りたい場合。これはORDER BYで解決できる。
ORDER BYは、ウィンドウ関数がどの順番で行を処理するかを決める。
例:OVER()の中でORDER BYを使う
SELECT
department_id,
employee_id,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank
FROM employees;
何が起きてる?
- データがグループ分けされる(
PARTITION BY department_id)。 - 各グループ内で給料の高い順に並べ替え(
ORDER BY salary DESC)。 - 並び順に応じてランクが振られる。
結果:
| department_id | employee_id | salary | rank |
|---|---|---|---|
| 1 | 3 | 55000 | 1 |
| 1 | 1 | 50000 | 2 |
| 2 | 2 | 60000 | 1 |
ウィンドウ関数の組み合わせ
SQLでは、1つのクエリで複数のウィンドウ関数を使えるし、それぞれ独自のルールで動かせる。つまり、同じ部屋で音楽を流しながら人数を数えるみたいな感じで、各プロセスは独立してる!
例:複数のウィンドウ関数
SELECT
department_id,
employee_id,
salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS row_num,
AVG(salary) OVER (PARTITION BY department_id) AS avg_salary
FROM employees;
何が起きてる?
ROW_NUMBER()は各グループで給料が高い順に行をナンバリング。AVG()は各グループの平均給料を計算。
結果:
| department_id | employee_id | salary | row_num | avg_salary |
|---|---|---|---|---|
| 1 | 3 | 55000 | 1 | 52500 |
| 1 | 1 | 50000 | 2 | 52500 |
| 2 | 2 | 60000 | 1 | 60000 |
リアルな現場での例
OVER()付きウィンドウ関数は、いろんな現場で使われてる。例えば:
- 売上分析: 各カテゴリ内で商品の売上数をランキング
- ランキング: 各グループで学生の平均点から順位を決める
- 時系列: 時間ごとの売上の累積合計
売上分析の例:
SELECT
category_id,
product_id,
product_name,
SUM(sales) OVER (PARTITION BY category_id ORDER BY sales DESC) AS cumulative_sales
FROM products;
ウィンドウ関数でよくあるミス
PARTITION BYの指定漏れ
PARTITION BYを使わないと、ウィンドウ関数はテーブル全体に適用される。グループごとに分けたい場合は、思った通りの結果にならないことも。
💡 テーブルをどう分けたいか(ユーザーごと、注文ごと、カテゴリごとなど)をちゃんと指定しよう。
ORDER BYでのデータ型ミス
ウィンドウ関数内のORDER BYはデータ型に敏感。日付をテキスト(VARCHAR)で保存してると、アルファベット順になって時系列順にならないことも。
💡 そういうフィールドは、並べ替える前にちゃんとDATEやINTEGERなどに変換しよう。
ROWS BETWEENの使い方ミス
デフォルトだと、ウィンドウ関数はROWS BETWEENで決まる範囲で動く。フレームを明示しないと、RANGEの動きになって、思ったより多くの行が返ることもある。
💡 ちゃんとコントロールしたいなら、ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWを使って、最初から今の行までの累積を作ろう。
NULLの扱いミス
ウィンドウ関数はNULLをいろんな風に扱う。例えば、RANK()やDENSE_RANK()はNULLも値としてカウントして、別のランクを振る。
💡 ORDER BYでNULLS LASTやNULLS FIRSTを使って、NULLの位置をコントロールしよう。
- 普通の集計でいいのにウィンドウ集計を使う
たまに、普通のGROUP BY集計で十分なのに、ウィンドウ集計関数(SUM() OVER(...)など)を使ってクエリが複雑&遅くなってることがある。
💡 行ごとの詳細を残したい時だけウィンドウ関数を使おう。
GO TO FULL VERSION