PostgreSQLのWindow関数

Window関数とは

  • PostgreSQL 8.4から導入
  • SELECT の結果を維持したまま、集約関数の結果を後付けできる関数
  • 通常の集約関数( GROUP BY )とは異なり、Window関数では複数の行がまとめられることはなく、行それぞれが返却される
  • 常に集約関数の直後に OVER 句を含む

Window関数の構文

  • 関数(...) OVER (PARTITION BY ...) : 区間に分割
  • 関数(...) OVER (ORDER BY ...) : 区間ごとに並び替え
  • PARTITION BYORDER BY は同時に使用することも可能

利用可能なWindow関数の一覧

  • Window関数では、下記に列挙された関数と、通常の集約関数 ( countsum など) が使用できる
利用可能なWindow関数リスト
関数 説明
row_number() 行番号
rank() ランキング (同率で番号を飛ばす)
dense_rank() ランキング (同率で番号を飛ばさない)
percent_rank() ランキング (%で表示) : (rank - 1) / (全行数 - 1)
cume_dist() percent_rank に類似 : (現在の行の位置) / (全行数)
ntile(N) ランキング (1..N に分割)
lag(value, offset, default) ソート状態での前の行の値
lead(value, offset, default) ソート状態での後の行の値
first_value(value) 最初の値
last_value(value) 最後の値
nth_value(value, N) N番目の値 (1から数える)

Window関数を使ったクエリ例

部署の平均給与とそれぞれの従業員の給与を使った例

サンプルのテーブル

depname | empno | salary | avg ----------|-------|--------|----------------------- develop | 11 | 5200 | 5020.0000000000000000 develop | 7 | 4200 | 5020.0000000000000000 develop | 9 | 4500 | 5020.0000000000000000 develop | 8 | 6000 | 5020.0000000000000000 develop | 10 | 5200 | 5020.0000000000000000 personnel | 5 | 3500 | 3700.0000000000000000 personnel | 2 | 3900 | 3700.0000000000000000 sales | 3 | 4800 | 4866.6666666666666667 sales | 1 | 5000 | 4866.6666666666666667 sales | 4 | 4800 | 4866.6666666666666667

rank関数の例

  • rank() 関数は、それぞれの明確な ORDER BYの値に対する現在行のパーティション内における順位を、 ORDER BY 句で定義された順序で生成する
  • depname 毎に salary の高い順にランクを付ける場合のSQLクエリ例
SELECT depname, empno, salary,
  rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
depname | empno | salary | rank ----------|-------|--------|------ develop | 8 | 6000 | 1 develop | 10 | 5200 | 2 develop | 11 | 5200 | 2 develop | 9 | 4500 | 4 develop | 7 | 4200 | 5 personnel | 2 | 3900 | 1 personnel | 5 | 3500 | 2 sales | 1 | 5000 | 1 sales | 4 | 4800 | 2 sales | 3 | 4800 | 2

連番付与

  • row_number() OVER () を使って、連番付与のSQLクエリ例
  • ソートした後に番号をふるのがポイント
  • PARTITION BY が無い場合は、テーブル全体が対象となる
SELECT row_number() OVER (), *
  FROM (SELECT * FROM tbl ORDER BY sortkey) AS t;

全体に対する割合で取得

  • 上位10%を取得する場合ののSQLクエリ例
  • 全体に対する割合を計算するには、結局は全体を取得する必要があることに注意
SELECT ...
FROM (SELECT *, cume_dist() OVER (ORDER BY i) AS rank FROM tbl) AS t
WHERE rank <= 0.1;

PARTITION BYORDER BY の組み合わせ

  • PARTITION BYORDER BY は、同時に使用することも可能
  • 地域ごとに身長の低い順に番号付けする場合のSQLクエリ例
SELECT id, 地域, 身長,
  rank() OVER (PARTITION BY 地域 ORDER BY 身長)
FROM tbl;

歯抜けIDの検索

  • id でソートした際に1つ前の行と比較して、連番になっていない行を抜き出す場合のSQLクエリ例
  • 前の行を取得する lag() 関数を使うと、ただ1回のテーブルスキャンで処理することが可能
  • NOT INNOT EXISTS などコストの高いSQLを使う必要がなくなる
SELECT id, prev_id
FROM (
  SELECT id,
    lag(id, 1, 0) OVER (ORDER BY id) AS prev_id
  FROM tbl
) AS t
WHERE id <> prev_id + 1;

Window演算が行われた後、行にフィルタ処理を行ったりグループ化を行う場合

  • Window演算が行われた後、行にフィルタ処理を行ったりグループ化を行う必要が生じた場合、副問い合わせを使用する
  • 3 より小さい rank を持った内部問い合わせからの行のみを表示するSQLクエリ例
SELECT depname, empno, salary, enroll_date
FROM (
  SELECT depname, empno, salary, enroll_date,
    rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
  FROM empsalary
) AS ss
WHERE pos < 3;

複数の関数で同じWindow処理動作が必要な場合

  • WINDOW 句でWindow処理動作に名前を付け、これを OVER 内で参照することが可能
SELECT sum(salary) OVER w, avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);

Window関数の動き

  • OVER 句の中で明確に条件を指定して、どのカラムをグループの単位とするのか、グループ内のソート順などを設定する
  • Window関数で定義したグループは「ウィンドウフレーム」と呼ばれ、その単位で OVER 句の前に定義した関数が実行される

Window関数のポイント

  • 特定カラムのグループとみなして集約関数が利用できる
  • 集約関数の対象となった行はそのまま残る
  • SELECTORDER BY 内でのみ利用可能

参照