視窗函數在 SQL:2003 標準引入,SQL Server、Oracle、DB2 均支援(MySQL 8.0+ 才支援)。它解決了傳統聚合函數「每組只能返回一行」的限制,允許在保留明細行的同時計算聚合值,輕鬆解決「各組前 N 名」、「累計總額」、「同比環比」等經典問題。
| 特性 | GROUP BY 聚合 | 視窗函數 |
|---|---|---|
| 結果行數 | 每組 1 行 | 保留原始行數 |
| 明細資訊 | 丟失 | 保留 |
| 計算範圍 | 整個群組 | 可定義滑動視窗 |
函數名(運算式) OVER (
[PARTITION BY 分區欄位]
[ORDER BY 排序欄位]
[ROWS/RANGE 視窗框架]
)
CREATE TABLE T_Person (
FName VARCHAR(20), -- 姓名
FCity VARCHAR(20), -- 城市
FAge INT, -- 年齡
FSalary INT -- 薪資
);
INSERT INTO T_Person VALUES
('Tom','BeiJing',20,3000), ('Tim','ChengDu',21,4000), ('Jim','BeiJing',22,3500),
('Lily','London',21,2000), ('John','NewYork',22,1000), ('YaoMing','BeiJing',20,3000),
('Swing','London',22,2000), ('Guo','NewYork',20,2800), ('YuQian','BeiJing',24,8000),
('Ketty','London',25,8500), ('Kitty','ChengDu',25,3000), ('Merry','BeiJing',23,3500),
('Smith','ChengDu',30,3000), ('Bill','BeiJing',25,2000), ('Jerry','NewYork',24,3300);

需求:查詢薪資 < 5000 的員工,並顯示符合條件的總人數(每行皆顯示)。
SELECT FCity, FAge, (SELECT COUNT(*) FROM T_Person WHERE FSalary < 5000) AS total_count
FROM T_Person WHERE FSalary < 5000;

SELECT FCity, FAge, COUNT(*) OVER() AS total_count
FROM T_Person WHERE FSalary < 5000;

關鍵差異:OVER() 空括號表示視窗包含查詢結果集的所有行,聚合函數在此視窗上計算,結果廣播到每一行。
將結果集劃分為多個分區,視窗函數在各分區獨立計算。
-- 各城市薪資 < 5000 的員工人數
SELECT FCity, FAge, COUNT(*) OVER(PARTITION BY FCity) AS city_count
FROM T_Person WHERE FSalary < 5000;
配合框架子句使用,定義「目前行之前/之後」的語意。
-- 依薪資排序,計算「目前行及之前所有行」的薪資累計
SELECT FName, FSalary,
SUM(FSalary) OVER(ORDER BY FSalary ROWS UNBOUNDED PRECEDING) AS running_total
FROM T_Person;
框架選項:
| 語法 | 含義 |
|---|---|
| ROWS UNBOUNDED PRECEDING | 從分區第一行到目前行 |
| ROWS n PRECEDING | 目前行前 n 行到目前行 |
| ROWS BETWEEN n PRECEDING AND m FOLLOWING | 自定義滑動範圍 |
| RANGE | 按 ORDER BY 值範圍(而非行數)劃分 |
| 類別 | 函數 | 說明 |
|---|---|---|
| 聚合視窗函數 | SUM() OVER() | 累計、移動平均 |
| AVG() OVER() | 移動平均 | |
| COUNT() OVER() | 分區計數 | |
| MAX()/MIN() OVER() | 分區極值 | |
| FIRST_VALUE()/LAST_VALUE() | 分區首/尾值 | |
| 排序視窗函數 | ROW_NUMBER() | 唯一遞增序號 |
| RANK() | 並列同名次,跳過下一名次 | |
| DENSE_RANK() | 並列同名次,不跳過 | |
| NTILE(n) | 分桶(如四分位數) | |
| 分析視窗函數 | LAG()/LEAD() | 存取前/後行值(同比環比神器) |
| PERCENT_RANK() | 相對排名百分位 | |
| CUME_DIST() | 累積分佈 |
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY FCity ORDER BY FSalary DESC) AS rn
FROM T_Person
)
SELECT FName, FCity, FSalary
FROM ranked
WHERE rn <= 3
ORDER BY FCity, FSalary DESC;
下篇將深入講解 排序視窗函數(ROW_NUMBER/RANK/DENSE_RANK 差異陷阱)與 分析視窗函數(LAG/LEAD 同比環比實戰),敬請期待。