SQL Window Functions

窗口函數 (SQL Window Functions)

這篇 SQL Window Functions 教學將帶你認識窗口函數(Window Functions)的基本概念與用法。窗口函數可以在不改變查詢結果列數的情況下,對每一列進行計算,例如排名、編號、取前後列的值等。與聚合函數不同的是,窗口函數不會將多列合併為一列,而是保留每一列的原始資料,並在旁邊新增計算結果。

窗口函數常用於排名、分組排序、計算累計值、取得前後列資料等場景。搭配 OVER() 子句來定義「窗口」的範圍,可以指定分組(PARTITION BY)與排序(ORDER BY)方式。常見的窗口函數包括 ROW_NUMBER()、RANK()、DENSE_RANK()、LEAD()、LAG() 等。

Window Functions 語法 (SQL Window Functions Syntax)

SELECT column_name,
       window_function() OVER (
           PARTITION BY column_name
           ORDER BY column_name
       ) AS alias
FROM table_name;

說明:

關鍵字說明
window_function()窗口函數名稱,例如 ROW_NUMBER()、RANK()、LEAD() 等
OVER()定義窗口的範圍,是窗口函數必備的子句
PARTITION BY(選填)指定分組欄位,類似 GROUP BY,但不會合併資料列
ORDER BY(選填)指定窗口內的排序方式

Window Functions 用法 (Example)

假設我們有一個員工資料表 employees 如下(若需了解如何建立資料表,請參考 CREATE TABLE 教學):

NameDepartmentSalary
張一業務部35000
王二資訊部42000
李三業務部38000
趙四資訊部45000
陳五人資部40000
林六業務部36000
黃七資訊部48000

ROW_NUMBER() — 依序編號

ROW_NUMBER() 會為每一列依照指定的排序方式,從 1 開始依序給予不重複的編號。即使有相同的值,編號也不會重複。

SELECT Name, Department, Salary,
       ROW_NUMBER() OVER (ORDER BY Salary DESC) AS row_num
FROM employees;

查詢結果如下:

NameDepartmentSalaryrow_num
黃七資訊部480001
趙四資訊部450002
王二資訊部420003
陳五人資部400004
李三業務部380005
林六業務部360006
張一業務部350007

RANK() — 排名(有間隔)

RANK() 會依照指定的排序方式給予排名。如果有相同的值,會給予相同的排名,但下一個排名會跳過。例如有兩個第 1 名,下一個就是第 3 名(跳過第 2 名)。

為了更清楚地展示 RANK() 的特性,假設員工資料中王二與陳五的薪資都是 42000:

SELECT Name, Department, Salary,
       RANK() OVER (ORDER BY Salary DESC) AS rank_num
FROM employees;

查詢結果如下:

NameDepartmentSalaryrank_num
黃七資訊部480001
趙四資訊部450002
王二資訊部420003
陳五人資部420003
李三業務部380005
林六業務部360006
張一業務部350007

可以看到王二與陳五薪資相同,都是第 3 名,而下一個排名直接跳到第 5 名(跳過第 4 名)。

DENSE_RANK() — 排名(無間隔)

DENSE_RANK() 與 RANK() 類似,也會對相同值給予相同排名,但不同的是,下一個排名不會跳過,而是連續的。

SELECT Name, Department, Salary,
       DENSE_RANK() OVER (ORDER BY Salary DESC) AS dense_rank_num
FROM employees;

同樣假設王二與陳五的薪資都是 42000,查詢結果如下:

NameDepartmentSalarydense_rank_num
黃七資訊部480001
趙四資訊部450002
王二資訊部420003
陳五人資部420003
李三業務部380004
林六業務部360005
張一業務部350006

與 RANK() 相比,DENSE_RANK() 在王二與陳五(都是第 3 名)之後,下一個排名是第 4 名而不是第 5 名。

ROW_NUMBER vs RANK vs DENSE_RANK 比較

以下整理三個排名函數的差異:

函數相同值的處理排名是否連續
ROW_NUMBER()即使值相同,編號也不重複連續(1, 2, 3, 4…)
RANK()相同值給予相同排名不連續,會跳過(1, 2, 2, 4…)
DENSE_RANK()相同值給予相同排名連續,不跳過(1, 2, 2, 3…)

LAG() — 取前一列的值

LAG() 可以取得目前列的前 N 列(預設為前 1 列)的值,常用於比較當前資料與前一筆資料的差異,例如計算薪資差距、月營收變化等。

SELECT Name, Department, Salary,
       LAG(Salary, 1) OVER (ORDER BY Salary DESC) AS prev_salary
FROM employees;

查詢結果如下:

NameDepartmentSalaryprev_salary
黃七資訊部48000NULL
趙四資訊部4500048000
王二資訊部4200045000
陳五人資部4000042000
李三業務部3800040000
林六業務部3600038000
張一業務部3500036000

第一列因為沒有前一列,所以 prev_salary 為 NULL。每一列的 prev_salary 就是依照薪資由高到低排序後,前一筆員工的薪資。

LEAD() — 取後一列的值

LEAD() 與 LAG() 相反,可以取得目前列的後 N 列(預設為後 1 列)的值。常用於預測或比較下一筆資料。

SELECT Name, Department, Salary,
       LEAD(Salary, 1) OVER (ORDER BY Salary DESC) AS next_salary
FROM employees;

查詢結果如下:

NameDepartmentSalarynext_salary
黃七資訊部4800045000
趙四資訊部4500042000
王二資訊部4200040000
陳五人資部4000038000
李三業務部3800036000
林六業務部3600035000
張一業務部35000NULL

最後一列因為沒有後一列,所以 next_salary 為 NULL。

PARTITION BY 搭配窗口函數 — 分組內排名

PARTITION BY 可以將資料依照指定欄位分組,讓窗口函數在每個分組內分別計算。這類似 GROUP BY 的分組概念,但不會將資料合併。

例如,依部門分組,在每個部門內依薪資由高到低排名:

SELECT Name, Department, Salary,
       ROW_NUMBER() OVER (
           PARTITION BY Department
           ORDER BY Salary DESC
       ) AS dept_rank
FROM employees;

查詢結果如下:

NameDepartmentSalarydept_rank
李三業務部380001
林六業務部360002
張一業務部350003
陳五人資部400001
黃七資訊部480001
趙四資訊部450002
王二資訊部420003

可以看到每個部門的排名都是從 1 開始重新計算,這就是 PARTITION BY 的作用。

搭配 LAG() 計算薪資差距

結合 PARTITION BY 與 LAG(),可以在每個部門內計算員工與前一位員工的薪資差距:

SELECT Name, Department, Salary,
       LAG(Salary, 1) OVER (
           PARTITION BY Department
           ORDER BY Salary DESC
       ) AS prev_salary,
       Salary - LAG(Salary, 1) OVER (
           PARTITION BY Department
           ORDER BY Salary DESC
       ) AS salary_diff
FROM employees;

查詢結果如下:

NameDepartmentSalaryprev_salarysalary_diff
李三業務部38000NULLNULL
林六業務部3600038000-2000
張一業務部3500036000-1000
陳五人資部40000NULLNULL
黃七資訊部48000NULLNULL
趙四資訊部4500048000-3000
王二資訊部4200045000-3000

每個部門的第一位員工因為沒有前一列,所以 prev_salary 和 salary_diff 都是 NULL。這個查詢可以清楚看出每個部門內員工之間的薪資差距。

常見窗口函數一覽

除了本篇介紹的函數之外,SQL 還提供許多其他窗口函數,以下為常見的窗口函數整理:

函數說明
ROW_NUMBER()依序給予不重複的列編號
RANK()排名,相同值同名次,有間隔
DENSE_RANK()排名,相同值同名次,無間隔
LAG(col, n)取前 n 列的值(預設 n=1)
LEAD(col, n)取後 n 列的值(預設 n=1)
FIRST_VALUE(col)取窗口內第一列的值
LAST_VALUE(col)取窗口內最後一列的值
NTILE(n)將資料平均分成 n 組
SUM() OVER()計算累計加總
AVG() OVER()計算移動平均

窗口函數是 SQL 進階查詢中非常強大的工具,能夠在不改變結果集的情況下進行複雜的計算。搭配 PARTITION BY 和 ORDER BY,可以靈活地對資料進行分組排名、前後比較等操作。如果你需要對分組結果進行篩選,可以參閱 SQL HAVING 教學。

延伸閱讀