2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 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,
       row_number() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
  depname  | empno | salary | row_number
-----------+-------+--------+------------
 develop   |     8 |   6000 |          1
 develop   |    10 |   5200 |          2
 develop   |    11 |   5200 |          3
 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 |          3
(10 rows)

如圖所示,row_number視窗函式根據ORDER BY子句定義的順序為每個分割槽內的行分配順序編號(具有相同值的行按未指定順序編號)。row_number不需要顯式引數,因為其行為完全由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,
     row_number() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
     FROM empsalary
  ) AS ss
WHERE pos < 3;

上面的查詢僅顯示內部查詢中row_number小於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 節

提交更正

如果您在文件中看到任何不正確的內容、與您對特定功能的經驗不符或需要進一步澄清的內容,請使用此表格報告文件問題。