SQL練習室
← SQLガイド

SQLのウィンドウ関数とは?順位・累計を元の表と結果で解説

ウィンドウ関数は、元の行を残したまま、順位や合計などの計算結果を各行に付ける機能です。月別の売上をそのまま表示しながら、担当者別の合計や累計を添えられます。同じ5行の売上で、一つずつ計算を加えましょう。

例で使う表

salesの1行は、同じ年の「担当者1人・1か月」の売上です。青木さんは1〜3月、北野さんは1〜2月のデータがあります。青木さんの2月と3月は同額なので、順位の違いも確認できます。各例の末尾のORDER BYは、結果表の表示順を決めるために付けています。

sales:担当者ごとの月間売上(amountは万円)
id(行ID)salesperson(担当者)month_no(月)amount(売上・万円)
1青木110
2青木220
3青木320
4北野130
5北野240

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

sql
SELECT id, salesperson, month_no, amount,
  SUM(amount) OVER () AS total_amount
FROM sales
ORDER BY id;

OVER ()で各行に全体の合計を付ける

売上は10 + 20 + 20 + 30 + 40で合計120万円です。SUM(amount) OVER ()は、この合計を5行すべてに付けます。SUM(amount)だけで集計すると1行になりますが、OVER ()を付けた結果では月別の5行が残ります。

sql
SELECT id, salesperson, month_no, amount,
  SUM(amount) OVER () AS total_amount
FROM sales
ORDER BY id;
実行結果
idsalespersonmonth_noamounttotal_amount
1青木110120
2青木220120
3青木320120
4北野130120
5北野240120

PARTITION BYで担当者ごとに計算する

OVERの中にPARTITION BY salespersonを足すと、合計を求める範囲が担当者ごとに分かれます。青木さんの行には50万円、北野さんの行には70万円が付きます。GROUP BYのように担当者を1行にまとめるのではなく、月別の5行を残します。

sql
SELECT id, salesperson, month_no, amount,
  SUM(amount) OVER (PARTITION BY salesperson) AS person_total
FROM sales
ORDER BY id;
実行結果
idsalespersonmonth_noamountperson_total
1青木11050
2青木22050
3青木32050
4北野13070
5北野24070

ROW_NUMBERで売上の高い順に連番を付ける

ROW_NUMBERは1から始まる連番を付けます。OVER内のORDER BY amount DESC, idで、売上の高い順、同額なら行IDの小さい順に番号を決めます。20万円の行にも3と4の別の番号が付きます。末尾のORDER BYも同じ順にして、番号と表示を見比べられるようにしています。

sql
SELECT id, salesperson, month_no, amount,
  ROW_NUMBER() OVER (ORDER BY amount DESC, id) AS position
FROM sales
ORDER BY amount DESC, id;
実行結果
idsalespersonmonth_noamountposition
5北野2401
4北野1302
2青木2203
3青木3204
1青木1105

RANKで同じ売上に同じ順位を付ける

同額を同順位にしたいときはRANKを使います。40万円が1位、30万円が2位、20万円の2行はどちらも3位です。その次の10万円は5位になります。同順位を判定するOVER内では売上だけを比較し、idは表示順にだけ使います。

sql
SELECT id, salesperson, month_no, amount,
  RANK() OVER (ORDER BY amount DESC) AS position
FROM sales
ORDER BY amount DESC, id;
実行結果
idsalespersonmonth_noamountposition
5北野2401
4北野1302
2青木2203
3青木3203
1青木1105

DENSE_RANKで次の順位を飛ばさずに付ける

RANKをDENSE_RANKに変えると、同じ売上に同順位を付けつつ、その次を飛ばしません。20万円の2行が3位なのは同じですが、10万円は4位になります。前のRANKの結果と、最後の行の番号を比べてください。

sql
SELECT id, salesperson, month_no, amount,
  DENSE_RANK() OVER (ORDER BY amount DESC) AS position
FROM sales
ORDER BY amount DESC, id;
実行結果
idsalespersonmonth_noamountposition
5北野2401
4北野1302
2青木2203
3青木3203
1青木1104

LAGで前の月の売上を隣に表示する

LAG(amount)は指定順で1行前の売上を返します。担当者ごとに分け、month_no順で計算すると、青木さんの2月には1月の10万円が付きます。各担当者の先頭月は前の行がないのでNULLです。この表は月が連続していますが、欠けた月があると「前の行」は前月とは限りません。

sql
SELECT id, salesperson, month_no, amount,
  LAG(amount) OVER (
    PARTITION BY salesperson ORDER BY month_no
  ) AS previous_amount
FROM sales
ORDER BY id;
実行結果
idsalespersonmonth_noamountprevious_amount
1青木110NULL
2青木22010
3青木32020
4北野130NULL
5北野24030

SUMとROWSで月ごとの累計を求める

担当者ごとに月順で足し上げます。ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWは「先頭行から今の行まで」という計算範囲です。青木さんは10、30、50万円、北野さんは30、70万円となり、担当者が変わると累計も別になります。

sql
SELECT id, salesperson, month_no, amount,
  SUM(amount) OVER (
    PARTITION BY salesperson ORDER BY month_no
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM sales
ORDER BY id;
実行結果
idsalespersonmonth_noamountrunning_total
1青木11010
2青木22030
3青木32050
4北野13030
5北野24070

AVGとROWSで直前の月と今月の平均を求める

前の例の範囲をROWS BETWEEN 1 PRECEDING AND CURRENT ROWに変え、AVGを使います。青木さんの2月は(10 + 20) ÷ 2で15万円、3月は(20 + 20) ÷ 2で20万円です。先頭月は1行しかないため、その月の値が平均になります。

sql
SELECT id, salesperson, month_no, amount,
  AVG(amount) OVER (
    PARTITION BY salesperson ORDER BY month_no
    ROWS BETWEEN 1 PRECEDING AND CURRENT ROW
  ) AS moving_average
FROM sales
ORDER BY id;
実行結果
idsalespersonmonth_noamountmoving_average
1青木11010
2青木22015
3青木32020
4北野13030
5北野24035

順位を条件にするには外側のWHEREで絞る

ウィンドウ関数の結果は、同じSELECTのWHEREでは条件にできません。まず内側で売上順のpositionを付け、その結果にrankedという名前を付けます。外側でposition <= 2とすると、40万円と30万円の2行が残ります。

sql
SELECT id, salesperson, month_no, amount, position
FROM (
  SELECT id, salesperson, month_no, amount,
    ROW_NUMBER() OVER (ORDER BY amount DESC, id) AS position
  FROM sales
) AS ranked
WHERE position <= 2
ORDER BY position;
実行結果
idsalespersonmonth_noamountposition
5北野2401
4北野1302

実際に1問解いてみる

OVER()

各社員のname、salaryに全社員の平均給与AVG(salary) OVER ()を添えて取得してください。行順は問いません。

テーブル

読み込み中…

実行結果

関連構文

ウィンドウ関数についてよくある質問

OVERの中と末尾のORDER BYはどう違いますか?
OVER内は順位や前後関係を計算する順番です。SQL末尾は最終的な表示順です。OVER内にORDER BYを書くだけでは表示順は保証されません。
担当者ごとの順位も付けられますか?
はい。ROW_NUMBERやRANKのOVER内にPARTITION BY salespersonを加えると、担当者ごとに順位が1から始まります。
累計のROWSは省略してよいですか?
省略すると、OVER内のORDER BYで同じ値を持つ行をまとめて範囲に含める既定の動作になります。1行ずつ足す意図ならROWSを明示し、同順位がある場合は行IDなども計算順に加えます。

参考文献

内容はPostgreSQL公式ドキュメントを参照し、初めて学ぶ人向けに日本語で整理しています。