SQLのウィンドウ関数とは?順位・累計・移動平均とPARTITION BY
ウィンドウ関数は、明細行を残したまま、全体平均・部署内順位・累計などを添える機能です。GROUP BYのように行をまとめず、OVER句で計算範囲を指定します。区切り、計算順序、フレームを分けて考えると、複雑な分析も組み立てられます。
ウィンドウ関数の基本構文
SELECT name, salary, AVG(salary) OVER ()
FROM employees;OVERの基本:明細に全体平均を添える
AVG(salary) OVER ()は、対象の全行で求めた平均を各行へ付けます。社員が5人なら結果も5行です。通常のAVGだけのSELECTは1行になるため、明細を保つことがウィンドウ関数の特徴です。WHEREで絞った場合は残った行が計算対象になります。
SELECT name, salary, AVG(salary) OVER ()
FROM employees;PARTITION BY:部署別に計算範囲を分ける
PARTITION BY departmentで平均の対象を同じ部署の社員に限定します。部署が切り替わると集計範囲も切り替わりますが、明細行数は減りません。GROUP BYと異なり、部署名・社員名・給与を同じSELECTにそのまま表示できます。
SELECT name, department, salary,
AVG(salary) OVER (PARTITION BY department)
FROM employees;ROW_NUMBER:同額でも一意の連番を付ける
ROW_NUMBERは1、2、3と重複しない番号を付けます。同じ給与の社員の順番も固定するなら、計算用ORDER BYにidを追加します。OVER内は番号を計算する順番で、表示順はSQL末尾のORDER BYで指定します。
SELECT id, name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC, id)
FROM employees
ORDER BY salary DESC, id;RANK:同順位を付け、次の順位を飛ばす
RANKは同じ給与なら同順位にし、1位が2人なら次を3位にします。同額を同順位にしたい場合、OVERのORDER BYに一意なidを入れてはいけません。idまで比較すると同順位がなくなります。表示順だけをidで安定させましょう。
SELECT id, name, salary,
RANK() OVER (ORDER BY salary DESC)
FROM employees
ORDER BY salary DESC, id;DENSE_RANK:同順位の次を詰める
DENSE_RANKは1位が2人でも次を2位にします。上位2種類の給与帯を選ぶならDENSE_RANK、競技順位のように人数分を飛ばすならRANK、必ず2人に絞るならROW_NUMBERが向きます。同順位を含めるかが選択基準です。
| 給与 | ROW_NUMBER(id順で同額を区別) | RANK | DENSE_RANK |
|---|---|---|---|
| 610000 | 1 | 1 | 1 |
| 610000 | 2 | 1 | 1 |
| 520000 | 3 | 3 | 2 |
SELECT id, name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC)
FROM employees
ORDER BY salary DESC, id;LAG:前の行の値を取得する
LAG(amount)は指定順の1行前の金額を返し、先頭行では既定でNULLになります。前月売上との比較に使えますが、月に欠損があると「前の行」は「前月」と一致しません。まずデータが1担当者1か月1行かを確認します。
SELECT salesperson, sold_month, amount,
LAG(amount) OVER (
PARTITION BY salesperson ORDER BY sold_month
)
FROM sales
ORDER BY salesperson, sold_month;SUM OVERとROWS:先頭から現在行までの累計
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWは、先頭から現在行までの範囲です。SUMと組み合わせると売上を順に積み上げます。日付が同じ明細が複数ある場合はidも計算順序に足し、1行ずつの累計を安定させます。
SELECT id, amount,
SUM(amount) OVER (
ORDER BY sold_month, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
FROM sales
ORDER BY sold_month, id;AVG OVER:直前行と現在行の移動平均
ROWS BETWEEN 1 PRECEDING AND CURRENT ROWは直前の1行と現在行を対象にします。1か月1行なら2か月移動平均で、先頭では存在する1行だけで平均を計算します。ROWSは日付の期間ではなく行数なので、欠損月がある場合は2か月分とは限りません。
SELECT salesperson, sold_month, amount,
AVG(amount) OVER (
PARTITION BY salesperson ORDER BY sold_month
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
)
FROM sales
ORDER BY salesperson, sold_month;部署別上位N件:サブクエリの外側で順位を絞る
ウィンドウ関数は同じ階層のWHEREに書けません。内側で部署内の連番を作り、外側で2以下を選ぶと各部署の上位2人になります。RANKへ変えると同順位を含められますが、返る人数は2人を超えることがあります。
SELECT *
FROM (
SELECT id, name, department, salary,
ROW_NUMBER() OVER (
PARTITION BY department ORDER BY salary DESC, id
) AS position
FROM employees
) ranked
WHERE position <= 2
ORDER BY department, position;ウィンドウ関数でよくある間違い
WHEREで1か月だけに絞ってからLAGを計算する
WHEREはウィンドウ計算より前に評価されます。対象月だけを残すと前月が計算対象から消えます。過去月も含めて内側でLAGを求め、外側で表示月を絞りましょう。
SELECT *
FROM (
SELECT salesperson, sold_month, amount,
LAG(amount) OVER (
PARTITION BY salesperson ORDER BY sold_month
) AS previous_amount
FROM sales
) history
WHERE sold_month = DATE '2026-03-01';実際に1問解いてみる
OVER()
各社員のname、salaryに全社員の平均給与AVG(salary) OVER ()を添えて取得してください。行順は問いません。
テーブル
読み込み中…
実行結果
関連構文
ウィンドウ関数についてよくある質問
- PARTITION BYとGROUP BYの違いは?
- PARTITION BYは計算対象を分けても各明細行を残します。GROUP BYはグループごとの行へまとめます。部署内順位を社員一覧に付けるならPARTITION BYです。
- ウィンドウ関数にORDER BYは必須ですか?
- 全体平均のように順序が不要な計算では省略できます。意味のある連番、前後比較、累計では順序を指定します。
- RANKの同順位を安定させるためidを加えてよいですか?
- 同額を同順位にしたいなら、OVER内には加えません。表示を安定させるidは外側のORDER BYに置きます。ROW_NUMBERで同額の優先順を決めたい場合はOVER内にもidを加えます。
参考文献
内容はPostgreSQL公式ドキュメントを参照し、初めて学ぶ人向けに日本語で整理しています。
- PostgreSQL公式:ウィンドウ関数入門 — 集約との違い、評価段階、順位の絞り込み
- PostgreSQL公式:ウィンドウ関数 — 順位、LAG、LAST_VALUE
- PostgreSQL公式:ウィンドウ式 — ROWS、RANGE、既定フレーム