SQL練習室
← SQLガイド

SQLのウィンドウ関数とは?順位・累計・移動平均とPARTITION BY

ウィンドウ関数は、明細行を残したまま、全体平均・部署内順位・累計などを添える機能です。GROUP BYのように行をまとめず、OVER句で計算範囲を指定します。区切り、計算順序、フレームを分けて考えると、複雑な分析も組み立てられます。

ウィンドウ関数の基本構文

sql
SELECT name, salary, AVG(salary) OVER ()
FROM employees;

OVERの基本:明細に全体平均を添える

AVG(salary) OVER ()は、対象の全行で求めた平均を各行へ付けます。社員が5人なら結果も5行です。通常のAVGだけのSELECTは1行になるため、明細を保つことがウィンドウ関数の特徴です。WHEREで絞った場合は残った行が計算対象になります。

sql
SELECT name, salary, AVG(salary) OVER ()
FROM employees;

PARTITION BY:部署別に計算範囲を分ける

PARTITION BY departmentで平均の対象を同じ部署の社員に限定します。部署が切り替わると集計範囲も切り替わりますが、明細行数は減りません。GROUP BYと異なり、部署名・社員名・給与を同じSELECTにそのまま表示できます。

sql
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で指定します。

sql
SELECT id, name, salary,
  ROW_NUMBER() OVER (ORDER BY salary DESC, id)
FROM employees
ORDER BY salary DESC, id;

RANK:同順位を付け、次の順位を飛ばす

RANKは同じ給与なら同順位にし、1位が2人なら次を3位にします。同額を同順位にしたい場合、OVERORDER BYに一意なidを入れてはいけません。idまで比較すると同順位がなくなります。表示順だけをidで安定させましょう。

sql
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順で同額を区別)RANKDENSE_RANK
610000111
610000211
520000332
sql
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行かを確認します。

sql
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行ずつの累計を安定させます。

sql
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か月分とは限りません。

sql
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人を超えることがあります。

sql
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を求め、外側で表示月を絞りましょう。

sql
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()

各社員のnamesalaryに全社員の平均給与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公式ドキュメントを参照し、初めて学ぶ人向けに日本語で整理しています。