支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4

3.5. 視窗函數 #

視窗函數會在一組與目前列相關的表格列上執行計算。 這種類似於使用彙總函數可以完成的計算類型。 但是,視窗函數不會像非視窗彙總呼叫那樣導致列分組為單一輸出列。 相反地,這些列會保留其各自的識別身分。 在幕後,視窗函數能夠存取的不僅僅是查詢結果的目前列。

以下範例顯示如何將每位員工的薪水與其部門的平均薪水進行比較

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)

前三個輸出欄直接來自表格 empsalary,並且表格中的每一列都有一個輸出列。 第四個欄代表在所有與目前列具有相同 depname 值的表格列上取得的平均值。 (這實際上與非視窗 avg 彙總函數相同,但 OVER 子句會使其被視為視窗函數並在視窗框架中計算。)

視窗函數呼叫始終包含緊隨視窗函數名稱和引數之後的 OVER 子句。 這是在語法上將其與普通函數或非視窗彙總區分開來的原因。 OVER 子句確定查詢的列如何分割以供視窗函數處理。 OVER 中的 PARTITION BY 子句將列分成群組或分割區,這些群組或分割區共享 PARTITION BY 運算式的相同值。 對於每一列,視窗函數會在與目前列屬於同一分割區的列上計算。

您也可以使用 OVER 中的 ORDER BY 控制視窗函數處理列的順序。 (視窗 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 子句決定。

視窗函數考慮的列是由查詢的 FROM 子句產生的虛擬表格,並由其 WHEREGROUP BYHAVING 子句(如果有的話)進行篩選。 例如,由於不符合 WHERE 條件而被移除的列,任何視窗函數都看不到。 查詢可以包含多個視窗函數,這些函數使用不同的 OVER 子句以不同的方式分割資料,但它們都在這個虛擬表格定義的同一組列上運作。

我們已經看到,如果列的排序不重要,則可以省略 ORDER BY。 也可以省略 PARTITION BY,在這種情況下,會有一個包含所有列的單一分割區。

還有另一個與視窗函數相關的重要概念:對於每一列,在其分割區中都有一組列稱為視窗框架。 某些視窗函數僅在視窗框架的列上運作,而不是在整個分割區上運作。 預設情況下,如果提供了 ORDER BY,則框架包含從分割區的開頭到目前列的所有列,以及根據 ORDER BY 子句等於目前列的任何後續列。 當省略 ORDER BY 時,預設框架包含分割區中的所有列。 [5] 以下是使用 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)

此處的總和是從第一個(最低)薪水到目前薪水取得的,包括目前薪水的任何重複項(請注意重複薪水的結果)。

視窗函數僅允許在查詢的 SELECT 清單和 ORDER BY 子句中使用。 它們在其他地方是被禁止的,例如在 GROUP BYHAVINGWHERE 子句中。 這是因為它們在邏輯上是在處理這些子句之後執行的。 此外,視窗函數在非視窗彙總函數之後執行。 這表示在視窗函數的引數中包含彙總函數呼叫是有效的,但反之則不然。

如果需要在視窗計算執行後篩選或分組資料列,您可以使用子查詢。 例如:

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;

上述查詢僅顯示內部查詢中 rank 小於 3 的資料列。

當查詢涉及多個視窗函數時,可以為每個函數分別寫出單獨的 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 節第 9.22 節第 7.2.5 節SELECT 參考頁面。



[5] 可以使用其他方式定義視窗框架,但本教學課程不涵蓋這些方式。 有關詳細資訊,請參閱 第 4.2.8 節

提交更正

如果您在文件中發現任何不正確、與您特定功能的使用經驗不符或需要進一步澄清的地方,請使用 此表格 回報文件問題。