ウィンドウ関数入門 — 行をまたいで計算する

SQLのウィンドウ関数(ROW_NUMBER・RANK・SUMのOVER句)を入門から解説します。GROUP BYとの違い、PARTITION BYやORDER BYの使い方を、順位付けや累計の例で整理します。

ウィンドウ関数は、SQL の中でも一段レベルの高い、しかし非常に強力な機能です。「部門ごとの給料順位」「日ごとの売上の累計」のように、行を集約せずに、他の行を参照した計算ができます。集計(GROUP BY)との違いを理解するのが第一歩です。

GROUP BY との違い

GROUP BY は行をまとめてしまうため、集計すると個々の行は消えます。たとえば GROUP BY dept で平均給料を出すと、結果は部門ごとに 1 行になり、社員名は出せません。

ウィンドウ関数は違います。元の行を残したまま、その行に関連する集計値を横に付け足せます。

query.sql
SELECT
  name,
  dept,
  salary,
  AVG(salary) OVER (PARTITION BY dept) AS 
FROM employees;

このクエリは、社員 1 人 1 行を保ったまま、各行に「その人の所属部門の平均給料」を付けます。OVER (...) が付くことで、その関数は集約ではなくウィンドウ関数として振る舞います。

OVER 句の中身

ウィンドウ関数の要は OVER (...) です。中には主に 2 つを書けます。

  • PARTITION BY 列:計算を行うグループを指定する(GROUP BY に相当するが、行はまとめない)
  • ORDER BY 列:グループ内の順序を指定する(順位や累計で使う)

PARTITION BY を省略すると、全行が 1 つのグループとして扱われます。

順位を付ける(ROW_NUMBER / RANK)

代表的なウィンドウ関数が、順位を付けるものです。

query.sql
SELECT
  name,
  dept,
  salary,
  ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS 
FROM employees;

PARTITION BY dept で部門ごとに分け、ORDER BY salary DESC で給料の高い順に並べ、その順番に番号を振ります。似た関数に違いがあります。

関数同点のときの振る舞い
ROW_NUMBER()必ず連番(1,2,3…)。同点でも別の番号
RANK()同点は同じ順位。次はスキップ(1,1,3…)
DENSE_RANK()同点は同じ順位。次はスキップしない(1,1,2…)

「各部門の給料 1 位」を出したいときは、ROW_NUMBER() で 1 位を絞る、といった使い方をします。

累計を出す

ORDER BY と組み合わせると、累計(running total)が計算できます。

query.sql
SELECT
  sale_date,
  amount,
  SUM(amount) OVER (ORDER BY sale_date) AS 
FROM sales;

日付順に、その日までの売上を足し上げていきます。SUM(...) OVER (ORDER BY ...) は「先頭からその行までの合計」を返すため、時系列の累計や進捗の可視化に便利です。

いつ使うか

ウィンドウ関数が向いているのは、次のような場面です。

  • 順位付け:グループごとのトップ N、ランキング
  • 累計・移動平均:時系列データの積み上げ
  • 前後の行との比較LAG / LEAD(前後の行の値を参照)

同じことをサブクエリや自己結合でも書けますが、ウィンドウ関数のほうが簡潔で読みやすくなることが多いです。

まとめ

  • ウィンドウ関数は行を集約せず、行をまたいだ計算を行う(元の行が残る)
  • OVER (PARTITION BY ... ORDER BY ...) でグループと順序を指定する
  • ROW_NUMBER / RANK / DENSE_RANK で順位を付ける(同点の扱いが違う)
  • SUM(...) OVER (ORDER BY ...) で累計が出せる

ウィンドウ関数は最初はとっつきにくいですが、使えるようになると分析系のクエリが一気に書きやすくなります。まずは「行が消えない集計」というイメージから入るとよいでしょう。

手を動かして確認しましょう「サブクエリ」の演習問題で、実際に SQL を書いて自動採点できます。
問題一覧へ →