SQL学习笔记6:窗口函数
窗口函数是 SQL 进阶的分水岭——面试必考、数据分析必备。它和普通聚合函数最大的区别是:聚合函数把多行压成一行,窗口函数保留每一行,同时还能做跨行计算。
13. 窗口函数基础
13.1 什么是窗口函数
-- 普通聚合:每个部门只返回一行
SELECT dept, AVG(salary)
FROM employees
GROUP BY dept;
-- 窗口函数:每个员工都还在,但旁边多了部门平均工资
SELECT
name,
dept,
salary,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg
FROM employees;
| name | dept | salary | dept_avg |
|---|---|---|---|
| 张三 | 技术 | 15000 | 14000 |
| 李四 | 技术 | 13000 | 14000 |
| 王五 | 销售 | 12000 | 11000 |
| 赵六 | 销售 | 10000 | 11000 |
每个人都在,多了一列”部门平均薪资”——这就是窗口函数的核心价值。
13.2 基本语法
函数名() OVER (
PARTITION BY 列名 -- 分区:按什么分组计算
ORDER BY 列名 -- 排序:分区内按什么排序
窗口帧子句 -- 范围:取分区内的哪几行
)
PARTITION BY 可以理解为 GROUP BY 的窗口版,但它不合并行,只是划定计算范围。
13.3 窗口函数分类
| 类别 | 函数 | 用途 |
|---|---|---|
| 排名 | ROW_NUMBER, RANK, DENSE_RANK, NTILE |
排序、分组编号 |
| 聚合 | SUM, AVG, COUNT, MAX, MIN |
移动求和、累计值 |
| 偏移 | LAG, LEAD, FIRST_VALUE, LAST_VALUE |
前后行对比 |
| 分析 | CUME_DIST, PERCENT_RANK |
百分比排名 |
14. 排名函数
14.1 ROW_NUMBER / RANK / DENSE_RANK 对比
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rank_num,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_num
FROM students;
| name | score | row_num | rank_num | dense_num |
|---|---|---|---|---|
| 张三 | 95 | 1 | 1 | 1 |
| 李四 | 95 | 2 | 1 | 1 |
| 王五 | 88 | 3 | 3 | 2 |
| 赵六 | 88 | 4 | 3 | 2 |
| 孙七 | 72 | 5 | 5 | 3 |
三者的区别一目了然:
| 函数 | 同分处理 | 编号连续性 |
|---|---|---|
ROW_NUMBER |
随机排先后 | 连续(1, 2, 3, 4) |
RANK |
相同排名 | 跳号(1, 1, 3, 3, 5) |
DENSE_RANK |
相同排名 | 不跳号(1, 1, 2, 2, 3) |
💡 面试常见题:”查每个部门工资最高的员工”——用
ROW_NUMBER+PARTITION BY dept ORDER BY salary DESC,然后取row_num = 1。
14.2 分组内排名
-- 每个部门内按工资排名
SELECT
name,
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank
FROM employees;
14.3 NTILE:分桶
-- 把学生按成绩分成 4 组(四分位)
SELECT
name,
score,
NTILE(4) OVER (ORDER BY score DESC) AS quartile
FROM students;
常用场景:按消费金额把用户分成高/中/低价值群体。
15. 聚合窗口函数
15.1 累计求和(Running Total)
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS running_total
FROM sales
ORDER BY date;
| date | amount | running_total |
|---|---|---|
| 01-01 | 100 | 100 |
| 01-02 | 200 | 300 |
| 01-03 | 150 | 450 |
| 01-04 | 300 | 750 |
每一行的 running_total 都是从开头到当前行的总和。
15.2 移动平均
-- 近 3 天的移动平均
SELECT
date,
amount,
AVG(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS ma_3
FROM sales;
| date | amount | ma_3 |
|---|---|---|
| 01-01 | 100 | 100 |
| 01-02 | 200 | 150 |
| 01-03 | 150 | 150 |
| 01-04 | 300 | 216.7 |
移动平均用于平滑波动,是销售预测、异常检测的基础。
15.3 各部门占比
SELECT
name,
dept,
salary,
salary * 100.0 / SUM(salary) OVER (PARTITION BY dept) AS dept_pct
FROM employees;
15.4 窗口帧子句详解
ROWS BETWEEN {起点} AND {终点}
| 写法 | 含义 |
|---|---|
UNBOUNDED PRECEDING |
分区第一行 |
n PRECEDING |
当前行前 n 行 |
CURRENT ROW |
当前行 |
n FOLLOWING |
当前行后 n 行 |
UNBOUNDED FOLLOWING |
分区最后一行 |
常用组合:
-- 累计:从开头到当前
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-- 滑动窗口:前 2 行 + 当前 + 后 2 行
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
-- 累计到分区末尾(少见)
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
💡 聚合窗口函数如果不写
ORDER BY,默认是整个分区;如果写了ORDER BY但不写窗口帧子句,默认从分区开头到当前行。
16. 偏移函数
16.1 LAG:向前看
取当前行前面第 n 行的值。最经典的场景是环比计算。
SELECT
month,
revenue,
LAG(revenue, 1) OVER (ORDER BY month) AS prev_month_revenue,
revenue - LAG(revenue, 1) OVER (ORDER BY month) AS growth
FROM monthly_sales;
| month | revenue | prev_month_revenue | growth |
|---|---|---|---|
| 1月 | 100 | NULL | NULL |
| 2月 | 150 | 100 | +50 |
| 3月 | 130 | 150 | -20 |
| 4月 | 180 | 130 | +50 |
LAG(列名, 偏移量, 默认值) OVER (ORDER BY ...)
-- 默认值用于第一行(前面没有行时填充)
16.2 LEAD:向后看
取当前行后面第 n 行的值。
SELECT
date,
price,
LEAD(price, 1) OVER (ORDER BY date) AS next_price,
LEAD(price, 1) OVER (ORDER BY date) - price AS price_change
FROM stock_prices;
16.3 FIRST_VALUE / LAST_VALUE
取分区内第一个或最后一个值。
SELECT
name,
dept,
salary,
FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY salary DESC) AS highest_paid,
LAST_VALUE(name) OVER (
PARTITION BY dept
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_paid
FROM employees;
⚠️
LAST_VALUE的默认窗口帧是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这意味着它返回的是”到当前行为止的最后一行”而非分区的最后一行。想取分区最后一行,必须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这是最常见的坑。
17. 实战场景
17.1 连续登录天数
表结构:
CREATE TABLE login_log (
user_id INT,
login_date DATE
);
WITH grouped AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn,
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
FROM (SELECT DISTINCT user_id, login_date FROM login_log) t
)
SELECT
user_id,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date,
COUNT(*) AS consecutive_days
FROM grouped
GROUP BY user_id, grp
HAVING COUNT(*) >= 3 -- 至少连续 3 天
ORDER BY consecutive_days DESC;
💡 核心思路:如果日期是连续的,那么
login_date - ROW_NUMBER()得到的值是相同的,这就形成了一个分组标识。
17.2 留存率计算
WITH first_visit AS (
SELECT
user_id,
MIN(visit_date) AS first_date
FROM visits
GROUP BY user_id
),
cohort AS (
SELECT
v.user_id,
f.first_date,
v.visit_date,
DATEDIFF(v.visit_date, f.first_date) AS day_offset
FROM visits v
JOIN first_visit f ON v.user_id = f.user_id
)
SELECT
first_date,
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day0_users,
COUNT(DISTINCT CASE WHEN day_offset = 1 THEN user_id END) * 100.0 /
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day1_retention,
COUNT(DISTINCT CASE WHEN day_offset = 7 THEN user_id END) * 100.0 /
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day7_retention
FROM cohort
GROUP BY first_date
ORDER BY first_date;
17.3 同时在线峰值
-- 直播、游戏等场景 → 计算任意时刻的最大同时在线人数
WITH events AS (
SELECT enter_time AS event_time, 1 AS delta FROM online_log
UNION ALL
SELECT leave_time AS event_time, -1 AS delta FROM online_log
)
SELECT
event_time,
SUM(delta) OVER (ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS concurrent_users
FROM events
ORDER BY concurrent_users DESC
LIMIT 1;
💡 不用
GROUP BY,不用自连接——SQL 就是这么优雅。
18. 常见陷阱与最佳实践
陷阱 🕳️
RANK和DENSE_RANK搞混面试中问”怎么取排名前 10”,用
RANK还是DENSE_RANK?- 如果成绩相同占用名额 →
DENSE_RANK - 如果允许并列导致超过 10 人 →
RANK(取rank <= 10)
- 如果成绩相同占用名额 →
LAST_VALUE的窗口帧问题如前所述,不指定窗口帧会得到错误结果。建议直接用
FIRST_VALUE(... ORDER BY ... DESC)代替。ORDER BY在窗口函数和查询中的区别SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS rk FROM employees ORDER BY name ASC; -- 这里的 ORDER BY 只影响最终结果的显示顺序,不影响窗口函数窗口函数不能用在
WHERE中-- ❌ 错误 SELECT * FROM employees WHERE ROW_NUMBER() OVER (ORDER BY salary DESC) <= 10; -- ✅ 正确:用子查询或 CTE 包一层 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees ) t WHERE rn <= 10;💡 窗口函数在 SQL 执行顺序中位于
WHERE之后、ORDER BY之前,所以不能在WHERE里直接过滤。PARTITION BY+ORDER BY忘了写,窗口变成全表-- 没写 PARTITION BY → 整个表是一个窗口 SUM(salary) OVER () -- 全表总和 -- 写了 PARTITION BY → 每个部门独立窗口 SUM(salary) OVER (PARTITION BY dept) -- 部门总和
最佳实践 ✅
| 建议 | 说明 |
|---|---|
| 复杂查询拆成 CTE 逐步调试 | 窗口函数嵌套难以一眼看出结果 |
ROW_NUMBER + 子查询组合拳 |
取 Top N、去重、分页都很实用 |
优先用 LAG/LEAD 做环比 |
比自连接清晰太多 |
PARTITION BY 别太多列 |
分区太多几乎没有实用价值 |
| 生产环境注意数据量 | 窗口函数需要排序 + 内存,大表注意性能 |
SQL 执行顺序(完整版)
FROM → WHERE → GROUP BY → HAVING → 窗口函数 → SELECT → DISTINCT → ORDER BY → LIMIT
记住:窗口函数在 HAVING 之后,SELECT 之前。所以不能在 WHERE/GROUP BY/HAVING 中使用窗口函数的结果。
结语
窗口函数是 SQL 从”能用”到”好用”的关键一步。掌握了它,以前要写几十行子查询、临时表的复杂统计,现在几行 SQL 搞定。
这套 SQL 学习笔记系列到此涵盖了:
| 笔记 | 内容 |
|---|---|
| 笔记一 | 建库建表、字符类型 |
| 笔记二 | IF EXISTS / IF NOT EXISTS |
| 笔记三 | WHERE、JOIN、GROUP BY、子查询、视图、索引、事务 |
| 笔记四 | 存储过程 |
| 笔记五 | 触发器、自定义函数 |
| 笔记六 | 窗口函数 |
从基础到进阶的 SQL 知识体系基本完整了。后续如果遇到特定场景(性能调优、分库分表等),再单独开篇。
——
title: SQL学习笔记六
date: 2026-07-19 10:00:00
categories:
- SQL
tags: - SQL
- 窗口函数
SQL学习笔记六:窗口函数
窗口函数是 SQL 进阶的分水岭——面试必考、数据分析必备。它和普通聚合函数最大的区别是:聚合函数把多行压成一行,窗口函数保留每一行,同时还能做跨行计算。
13. 窗口函数基础
13.1 什么是窗口函数
-- 普通聚合:每个部门只返回一行
SELECT dept, AVG(salary)
FROM employees
GROUP BY dept;
-- 窗口函数:每个员工都还在,但旁边多了部门平均工资
SELECT
name,
dept,
salary,
AVG(salary) OVER (PARTITION BY dept) AS dept_avg
FROM employees;
| name | dept | salary | dept_avg |
|---|---|---|---|
| 张三 | 技术 | 15000 | 14000 |
| 李四 | 技术 | 13000 | 14000 |
| 王五 | 销售 | 12000 | 11000 |
| 赵六 | 销售 | 10000 | 11000 |
每个人都在,多了一列”部门平均薪资”——这就是窗口函数的核心价值。
13.2 基本语法
函数名() OVER (
PARTITION BY 列名 -- 分区:按什么分组计算
ORDER BY 列名 -- 排序:分区内按什么排序
窗口帧子句 -- 范围:取分区内的哪几行
)
PARTITION BY 可以理解为 GROUP BY 的窗口版,但它不合并行,只是划定计算范围。
13.3 窗口函数分类
| 类别 | 函数 | 用途 |
|---|---|---|
| 排名 | ROW_NUMBER, RANK, DENSE_RANK, NTILE |
排序、分组编号 |
| 聚合 | SUM, AVG, COUNT, MAX, MIN |
移动求和、累计值 |
| 偏移 | LAG, LEAD, FIRST_VALUE, LAST_VALUE |
前后行对比 |
| 分析 | CUME_DIST, PERCENT_RANK |
百分比排名 |
14. 排名函数
14.1 ROW_NUMBER / RANK / DENSE_RANK 对比
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rank_num,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_num
FROM students;
| name | score | row_num | rank_num | dense_num |
|---|---|---|---|---|
| 张三 | 95 | 1 | 1 | 1 |
| 李四 | 95 | 2 | 1 | 1 |
| 王五 | 88 | 3 | 3 | 2 |
| 赵六 | 88 | 4 | 3 | 2 |
| 孙七 | 72 | 5 | 5 | 3 |
三者的区别一目了然:
| 函数 | 同分处理 | 编号连续性 |
|---|---|---|
ROW_NUMBER |
随机排先后 | 连续(1, 2, 3, 4) |
RANK |
相同排名 | 跳号(1, 1, 3, 3, 5) |
DENSE_RANK |
相同排名 | 不跳号(1, 1, 2, 2, 3) |
💡 面试常见题:”查每个部门工资最高的员工”——用
ROW_NUMBER+PARTITION BY dept ORDER BY salary DESC,然后取row_num = 1。
14.2 分组内排名
-- 每个部门内按工资排名
SELECT
name,
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank
FROM employees;
14.3 NTILE:分桶
-- 把学生按成绩分成 4 组(四分位)
SELECT
name,
score,
NTILE(4) OVER (ORDER BY score DESC) AS quartile
FROM students;
常用场景:按消费金额把用户分成高/中/低价值群体。
15. 聚合窗口函数
15.1 累计求和(Running Total)
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS running_total
FROM sales
ORDER BY date;
| date | amount | running_total |
|---|---|---|
| 01-01 | 100 | 100 |
| 01-02 | 200 | 300 |
| 01-03 | 150 | 450 |
| 01-04 | 300 | 750 |
每一行的 running_total 都是从开头到当前行的总和。
15.2 移动平均
-- 近 3 天的移动平均
SELECT
date,
amount,
AVG(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS ma_3
FROM sales;
| date | amount | ma_3 |
|---|---|---|
| 01-01 | 100 | 100 |
| 01-02 | 200 | 150 |
| 01-03 | 150 | 150 |
| 01-04 | 300 | 216.7 |
移动平均用于平滑波动,是销售预测、异常检测的基础。
15.3 各部门占比
SELECT
name,
dept,
salary,
salary * 100.0 / SUM(salary) OVER (PARTITION BY dept) AS dept_pct
FROM employees;
15.4 窗口帧子句详解
ROWS BETWEEN {起点} AND {终点}
| 写法 | 含义 |
|---|---|
UNBOUNDED PRECEDING |
分区第一行 |
n PRECEDING |
当前行前 n 行 |
CURRENT ROW |
当前行 |
n FOLLOWING |
当前行后 n 行 |
UNBOUNDED FOLLOWING |
分区最后一行 |
常用组合:
-- 累计:从开头到当前
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
-- 滑动窗口:前 2 行 + 当前 + 后 2 行
ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
-- 累计到分区末尾(少见)
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
💡 聚合窗口函数如果不写
ORDER BY,默认是整个分区;如果写了ORDER BY但不写窗口帧子句,默认从分区开头到当前行。
16. 偏移函数
16.1 LAG:向前看
取当前行前面第 n 行的值。最经典的场景是环比计算。
SELECT
month,
revenue,
LAG(revenue, 1) OVER (ORDER BY month) AS prev_month_revenue,
revenue - LAG(revenue, 1) OVER (ORDER BY month) AS growth
FROM monthly_sales;
| month | revenue | prev_month_revenue | growth |
|---|---|---|---|
| 1月 | 100 | NULL | NULL |
| 2月 | 150 | 100 | +50 |
| 3月 | 130 | 150 | -20 |
| 4月 | 180 | 130 | +50 |
LAG(列名, 偏移量, 默认值) OVER (ORDER BY ...)
-- 默认值用于第一行(前面没有行时填充)
16.2 LEAD:向后看
取当前行后面第 n 行的值。
SELECT
date,
price,
LEAD(price, 1) OVER (ORDER BY date) AS next_price,
LEAD(price, 1) OVER (ORDER BY date) - price AS price_change
FROM stock_prices;
16.3 FIRST_VALUE / LAST_VALUE
取分区内第一个或最后一个值。
SELECT
name,
dept,
salary,
FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY salary DESC) AS highest_paid,
LAST_VALUE(name) OVER (
PARTITION BY dept
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_paid
FROM employees;
⚠️
LAST_VALUE的默认窗口帧是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这意味着它返回的是”到当前行为止的最后一行”而非分区的最后一行。想取分区最后一行,必须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这是最常见的坑。
17. 实战场景
17.1 连续登录天数
表结构:
CREATE TABLE login_log (
user_id INT,
login_date DATE
);
WITH grouped AS (
SELECT
user_id,
login_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn,
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
FROM (SELECT DISTINCT user_id, login_date FROM login_log) t
)
SELECT
user_id,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date,
COUNT(*) AS consecutive_days
FROM grouped
GROUP BY user_id, grp
HAVING COUNT(*) >= 3 -- 至少连续 3 天
ORDER BY consecutive_days DESC;
💡 核心思路:如果日期是连续的,那么
login_date - ROW_NUMBER()得到的值是相同的,这就形成了一个分组标识。
17.2 留存率计算
WITH first_visit AS (
SELECT
user_id,
MIN(visit_date) AS first_date
FROM visits
GROUP BY user_id
),
cohort AS (
SELECT
v.user_id,
f.first_date,
v.visit_date,
DATEDIFF(v.visit_date, f.first_date) AS day_offset
FROM visits v
JOIN first_visit f ON v.user_id = f.user_id
)
SELECT
first_date,
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day0_users,
COUNT(DISTINCT CASE WHEN day_offset = 1 THEN user_id END) * 100.0 /
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day1_retention,
COUNT(DISTINCT CASE WHEN day_offset = 7 THEN user_id END) * 100.0 /
COUNT(DISTINCT CASE WHEN day_offset = 0 THEN user_id END) AS day7_retention
FROM cohort
GROUP BY first_date
ORDER BY first_date;
17.3 同时在线峰值
-- 直播、游戏等场景 → 计算任意时刻的最大同时在线人数
WITH events AS (
SELECT enter_time AS event_time, 1 AS delta FROM online_log
UNION ALL
SELECT leave_time AS event_time, -1 AS delta FROM online_log
)
SELECT
event_time,
SUM(delta) OVER (ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS concurrent_users
FROM events
ORDER BY concurrent_users DESC
LIMIT 1;
💡 不用
GROUP BY,不用自连接——SQL 就是这么优雅。
18. 常见陷阱与最佳实践
陷阱 🕳️
RANK和DENSE_RANK搞混面试中问”怎么取排名前 10”,用
RANK还是DENSE_RANK?- 如果成绩相同占用名额 →
DENSE_RANK - 如果允许并列导致超过 10 人 →
RANK(取rank <= 10)
- 如果成绩相同占用名额 →
LAST_VALUE的窗口帧问题如前所述,不指定窗口帧会得到错误结果。建议直接用
FIRST_VALUE(... ORDER BY ... DESC)代替。ORDER BY在窗口函数和查询中的区别SELECT name, salary, RANK() OVER (ORDER BY salary DESC) AS rk FROM employees ORDER BY name ASC; -- 这里的 ORDER BY 只影响最终结果的显示顺序,不影响窗口函数窗口函数不能用在
WHERE中-- ❌ 错误 SELECT * FROM employees WHERE ROW_NUMBER() OVER (ORDER BY salary DESC) <= 10; -- ✅ 正确:用子查询或 CTE 包一层 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees ) t WHERE rn <= 10;💡 窗口函数在 SQL 执行顺序中位于
WHERE之后、ORDER BY之前,所以不能在WHERE里直接过滤。PARTITION BY+ORDER BY忘了写,窗口变成全表-- 没写 PARTITION BY → 整个表是一个窗口 SUM(salary) OVER () -- 全表总和 -- 写了 PARTITION BY → 每个部门独立窗口 SUM(salary) OVER (PARTITION BY dept) -- 部门总和
最佳实践 ✅
| 建议 | 说明 |
|---|---|
| 复杂查询拆成 CTE 逐步调试 | 窗口函数嵌套难以一眼看出结果 |
ROW_NUMBER + 子查询组合拳 |
取 Top N、去重、分页都很实用 |
优先用 LAG/LEAD 做环比 |
比自连接清晰太多 |
PARTITION BY 别太多列 |
分区太多几乎没有实用价值 |
| 生产环境注意数据量 | 窗口函数需要排序 + 内存,大表注意性能 |
SQL 执行顺序(完整版)
FROM → WHERE → GROUP BY → HAVING → 窗口函数 → SELECT → DISTINCT → ORDER BY → LIMIT
记住:窗口函数在 HAVING 之后,SELECT 之前。所以不能在 WHERE/GROUP BY/HAVING 中使用窗口函数的结果。
结语
窗口函数是 SQL 从”能用”到”好用”的关键一步。掌握了它,以前要写几十行子查询、临时表的复杂统计,现在几行 SQL 搞定。
这套 SQL 学习笔记系列到此涵盖了:
| 笔记 | 内容 |
|---|---|
| 笔记一 | 建库建表、字符类型 |
| 笔记二 | IF EXISTS / IF NOT EXISTS |
| 笔记三 | WHERE、JOIN、GROUP BY、子查询、视图、索引、事务 |
| 笔记四 | 存储过程 |
| 笔记五 | 触发器、自定义函数 |
| 笔记六 | 窗口函数 |
从基础到进阶的 SQL 知识体系基本完整了。后续如果遇到特定场景(性能调优、分库分表等),再单独开篇。
sunrtnj@163.com