3.5. ウィンドウ関数

ウィンドウ関数は処理しようとしている行にちょっとでも関係したテーブル行の集合の全域にわたり演算を行います。 これは集約関数で可能となっている演算の様式に似ています。 しかし、通常の集約関数とは違って、ウィンドウ関数を使用すると複数の行が1つの出力行にグループ化されません。 それらの複数行はそれぞれ個別の属性を持ち続けます。 ウィンドウ関数は問い合わせ結果によってもたらされた1つの行のみならず、その他の行にアクセス可能です。

これはある部署に所属する従業員全員の平均給与と個別の従業員の給与をどのように比較するかを示した例です。

SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;

  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
(10 rows)

最初の3つの出力列は、テーブルempsalaryから直接取得できます。 そしてテーブル内のそれぞれの行に対応する1つの出力行があります。 4番目の列は、処理する行と同一のdepname値を持つ全てのテーブル行に渡って取得された平均値を表わしています。 (これは実質的に通常のavg集約関数と同じ機能ですが、OVER句によりウィンドウ関数として扱われ、しかるべき行の集合全てに対して演算されます。)

ウィンドウ関数呼び出しには例外なくウィンドウ関数名と引数(複数も可)を伴ったOVER句の記載があります。 これが通常の関数であるか、もしくは集約関数であるかを構文的に識別する部分です。 OVER句によって、ウィンドウ関数がその処理のため問い合わせの行をどのように分割するかを正確に見つけ出します。 OVER句内のPARTITION BYリストはそれらの行をグループ、またはパーティションに分割する指定を行います。 それは、PARTITION BY式の同じ値を共有します。 それぞれの行に対し、対象の行と同じパーティションに分類される行全てに対してウィンドウ関数が演算されます。

avg関数がその中にあるパーティションの行をどんな順序で処理したとしても、同一の結果をもたらします。 必要となった場合、OVER句内でORDER BYを使用することでその順序を制御することができます。 以下に例をあげます。

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
(10 rows)

ここで示したように、rank関数は、ORDER BY句で定義された順序で、それぞれ個別のORDER BY句の値に対して、処理する行のパーティション内における数値の順位づけを生成します。 rank関数は明示的なパラメータを必要としません。 その理由は、OVER句によってその動作が完全に決められてしまうからです。

ウィンドウ関数で検討対象となる行は、WHEREGROUP BY、およびHAVING句があった場合、それらの句で検索条件を追加された問い合わせのFROM句により作成された"仮想テーブル"の行です。 例えば、WHERE条件に一致しないため削除された行はいかなるウィンドウ関数でも見いだすことができません。 問い合わせは、異なったOVER句を用いて、異なった方法でデータ分割をする複数のウィンドウ関数を含んでも構いません。   しかしそれらは全て、この仮想テーブルで定義されたのと同じ行コレクション上で動作します。

ORDER BY句は、行の順序付けが重要でない場合割愛可能であることを既に見てきました。 PARTITION BY句も同様割愛することができます。 この場合、全ての行を含むたった一つのパーティションが存在します。

ウィンドウ関数に関連してその他の重要な概念があります。 それぞれの行に対して、ウィンドウフレームと呼ばれる、そのパーティション内の行の集合が存在します。 (全てではありませんが)多くのウィンドウ関数は、全てのパーティションではなく、ウィンドウフレームの行のみに対して動作します。 デフォルトでは、ORDER BY句が提供されると、フレームはパーティションの始めから現在の行までのすべての行と、ORDER BY句に準じた現在の行に等しい、全てのその後に続く行から構成されます。 ORDER BY句が省略された場合、デフォルトのフレームはそのパーティション内の全ての行で構成されます。 [1] sumを使用した例を示します。

SELECT salary, sum(salary) OVER () FROM empsalary;
 salary |  sum  
--------+-------
   5200 | 47100
   5000 | 47100
   3500 | 47100
   4800 | 47100
   3900 | 47100
   4200 | 47100
   4500 | 47100
   4800 | 47100
   6000 | 47100
   5200 | 47100
(10 rows)

上の例では、OVER句内にORDER BY句が存在しませんので、ウィンドウフレームはパーティションと同一です。 これはPARTITION BY句がない場合テーブル全体となるのと同じです。 言い換えると、総和はそれぞれ、テーブル全体に対して行われ、その結果それぞれの出力行で同じ結果を得ることになります。 しかし以下のように、ORDER BY句を加えると、極めて異なる結果になります。

SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
 salary |  sum  
--------+-------
   3500 |  3500
   3900 |  7400
   4200 | 11600
   4500 | 16100
   4800 | 25700
   4800 | 25700
   5000 | 30700
   5200 | 41100
   5200 | 41100
   6000 | 47100
(10 rows)

  ここで、sumは最初の(最も低い)salaryから現在の行まで、現在のものと重複する全てを含んで計算されます(重複するsalaryに対する結果に注意してください)。

ウィンドウ関数は問い合わせのSELECTリストとORDER BY句に限って許可されます。 GROUP BYHAVING、およびWHEREのような句の中では禁止されています。 その理由は、ウィンドウ関数は論理的に、これらの句が処理された後に実行されるからです。 またウィンドウ関数は通常の集約関数の後に実行されます。 この意味は、ウィンドウ関数の引数に集約関数呼び出しがあっても有効ですが、その逆は成り立たちません。

ウィンドウ演算が行われた後、行にフィルターを掛けたりグループ化を行う必要が生じた場合、副問い合わせを使用します。 例をあげます。

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;

上記問い合わせは3未満のrankを持った内部問い合わせからの行のみを表示します。

問い合わせが複数のウィンドウ関数を伴う場合、各ウィンドウ関数に異なるOVER句を記述することができます。 しかし複数の関数で同じウィンドウ処理動作が必要な場合は重複となり、またエラーを招きがちです。 代わりにWINDOW句でウィンドウ処理動作に名前を付け、これをOVER句内で参照することができます。 以下に例を示します。

SELECT sum(salary) OVER w, avg(salary) OVER w
  FROM empsalary
  WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);

ウィンドウ関数についてより詳細は、 項4.2.8項7.2.4、および SELECT マニュアルページにあります。

注意

[1]

ほかの方法でウィンドウフレームを定義するいくつかのオプションがありますが、このチュートリアルでは扱いません。詳細は、項4.2.8を参照してください。