🧠 一、什么是窗口函数?
窗口函数是 SQL 中一种在保留原始行的基础上,对行进行分组排序后执行聚合、排名、累计等计算的方法。
与传统的 GROUP BY 聚合不同的是:
👉 窗口函数不会把多行聚成一行,而是为每一行都保留详细信息并加上一个"窗口内"的计算结果。
🧾 二、窗口函数的语法
sql
WINDOW_FUNCTION() OVER (
PARTITION BY column1 ORDER BY column2 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
🧱 组成部分详解:
| 语法部分 | 说明 |
|---|---|
WINDOW_FUNCTION() |
执行的函数,如 SUM()、AVG()、ROW_NUMBER() 等 |
PARTITION BY |
窗口分组,像 GROUP BY,将数据按这个字段分成一个一个"窗口" |
ORDER BY |
窗口内的排序逻辑,很多函数必须指定排序顺序 |
ROWS BETWEEN |
控制窗口的范围(行数范围) |
🧰 三、常见窗口函数
基本上就是我们日常使用的一些函数
| 函数名 | 用途 |
|---|---|
ROW_NUMBER() |
每行编号(分组后从1开始) |
RANK() / DENSE_RANK() |
排名 |
SUM(col) |
累加 |
AVG(col) |
移动平均 |
LAG(col) / LEAD(col) |
前一行/后一行值 |
🧪 四、实战讲解
4.1 小例子
首先我们先搞一个基础的测试表,类型顺序打乱,
基础需求就是 每一个类型在每一时刻都有一个score,我们始终以id最新的为准,然后我们想要计算,某一时刻,score的累计值 , 表如下:

窗口函数如下
sql
WITH test_sum AS(
SELECT id ,
TYPE,
sum(score) OVER (PARTITION BY TYPE ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS col_score
FROM test
)
SELECT * FROM test_sum;
1. WITH 子句(公用表表达式)
WITH test_sum AS (...) 是一个 公用表表达式 (CTE),它用于定义一个临时的结果集,并将其命名为 test_sum。然后,我们可以在后续的查询中引用这个临时结果集。这个查询的 主要作用 是计算一个分组中的每个记录的滚动平均 score。
2. 内部 SELECT 查询的部分解释
sql
SELECT id, TYPE, AVG(score) OVER
(PARTITION BY TYPE ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS col_score FROM test
这部分查询涉及到以下几个部分:
(1)字段选择:
-
id和TYPE:查询的字段是id和TYPE,这些字段会出现在最终的结果中。 -
AVG(score):AVG()是一个聚合函数,用于计算某一列的平均值。在这里它用于计算每个分组内的score平均值。
(2)窗口函数 SUM() OVER (...):
-
SUM(score):计算窗口范围内的score总值。 -
OVER (...):这里的OVER关键字表示窗口函数,用于在指定的窗口范围内执行 SUM()聚合计算。
(3)PARTITION BY TYPE:
-
通过
PARTITION BY TYPE,我们将数据分成不同的组 (即按TYPE列分组)。每个分组内的数据会独立进行窗口函数的计算。- 举例 :如果
TYPE有值A和B,那么AVG(score)会分别计算TYPE = A和TYPE = B两个分组中的平均值。
- 举例 :如果
(4)ORDER BY id:
-
ORDER BY id指定了每个分组内部的排序规则,按照id字段进行排序。排序后,窗口函数会根据这个排序进行计算。- 举例 :假设分组后的数据按
id排序(从小到大),AVG()会根据这个顺序进行滚动计算。
- 举例 :假设分组后的数据按
(5)ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW定义了窗口的范围:-
UNBOUNDED PRECEDING:表示从分组的第一行开始。 -
CURRENT ROW:表示窗口的结束是当前行。
-
4.2 计算交易流水
需求 按照id 从小到大, 计算每一个账户的最终 balance ,并体现在该账户id最大的那条数据的balance中 ,期间每一个balance都要 计算出来当时的 balance,credit是加 , debit是减

有了上面的铺垫,所以这里就直接上SQL
sql
WITH balance_calculation AS (
SELECT
id,
account_code,
credit_amount,
debit_amount,
-- 计算余额:按账户分组,按流水创建时间排序,前一行余额 + 当前行的 credit_amount - debit_amount
SUM(credit_amount - debit_amount) OVER (PARTITION BY account_code ORDER BY create_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS balance
FROM tbl_posting_line
)
-- 更新最终的 balance
UPDATE tbl_posting_line t
JOIN balance_calculation bc ON t.id = bc.id
SET t.balance = bc.balance;
📌 五、窗口函数 VS 聚合函数
| 对比点 | 聚合函数(GROUP BY) |
窗口函数(OVER(...)) |
|---|---|---|
| 是否保留原始行 | ❌ 会合并 | ✅ 会保留 |
| 适合做什么 | 汇总统计报表 | 排名、累计、滑动统计 |
| 支持列 | 限制较多 | 更灵活 |
| 复杂分析 | 一般 | 更强大 |
📦 六、窗口函数常用场景
| 场景 | 示例函数 |
|---|---|
| 排名 | RANK(), ROW_NUMBER() |
| 累计金额 | SUM(...) OVER(...) |
| 环比分析 | LAG(), LEAD() |
| 分组内排序 | ROW_NUMBER() |
| 分组内前N | ROW_NUMBER() + WHERE |
✅ 七、使用注意事项
-
需要 MySQL 8.0+;
-
在
UPDATE JOIN中要小心更新逻辑(务必用唯一标识如id); -
OVER()不能用于WHERE,但可以用于CTE或子查询; -
如果性能是关键,建议先试试窗口函数效率 vs 存储过程。