前言
SQL 窗口函数(Window Functions)是数据分析师面试和日常工作中最高频考点之一。
很多人会写 GROUP BY,但一碰到「既要分组聚合、又要保留原始行」的需求就懵了——这正是窗口函数的主场。
本文覆盖 5 大高频场景,每段代码都有详细注释,帮你一次学透。
一、什么是窗口函数?
窗口函数和普通聚合函数(SUM/AVG/COUNT)的核心区别:
-
普通聚合:GROUP BY 之后,每组只返回 1 行结果
-
窗口函数:OVER() 之后,每组计算结果,但原始每行都保留
`-- 公主号:船长Talk -- 语法格式: -- 函数名() OVER ( -- PARTITION BY 分组字段 -- 可选:按哪个字段分窗口 -- ORDER BY 排序字段 -- 可选:窗口内按什么排序 -- ROWS/RANGE BETWEEN ... -- 可选:窗口帧范围 -- )
-- 示例:查询每个员工的工资,同时显示其部门平均工资 SELECT emp_name, department, salary, AVG(salary) OVER (PARTITION BY department) AS dept_avg_salary FROM employees;
## 二、场景一:排名函数 RANK / ROW_NUMBER / DENSE_RANK
这三个函数是面试最爱考的,必须搞清楚差异。
`-- 公主号:船长Talk(SQL进阶干货,建议收藏)
-- 数据场景:销售额排名
-- 假设数据:Alice=100, Bob=90, Carol=90, Dave=80
SELECT
emp_name,
sales_amount,
-- ROW_NUMBER:绝对不重复,每行唯一序号(1,2,3,4)
-- 即使销售额相同,排名也不同
ROW_NUMBER() OVER (ORDER BY sales_amount DESC) AS row_num,
-- RANK:允许并列,但会跳号(1,2,2,4)
-- Bob和Carol并列第2,下一个是第4,没有第3
RANK() OVER (ORDER BY sales_amount DESC) AS rank_num,
-- DENSE_RANK:允许并列,不跳号(1,2,2,3)
-- Bob和Carol并列第2,下一个是第3
DENSE_RANK() OVER (ORDER BY sales_amount DESC) AS dense_rank_num
FROM sales_data;
-- 结果:
-- Alice 100 1 1 1
-- Bob 90 2 2 2
-- Carol 90 3 2 2 ← RANK跳到了4,DENSE_RANK是3
-- Dave 80 4 4 3
实战:取每个部门销售额 Top 3
`-- 公主号:船长Talk -- 经典面试题:每个部门取前3名,RANK允许并列 WITH ranked AS ( SELECT emp_name, department, sales_amount, RANK() OVER ( PARTITION BY department -- 按部门分窗口 ORDER BY sales_amount DESC -- 窗口内按销售额倒序 ) AS dept_rank FROM sales_data ) SELECT * FROM ranked WHERE dept_rank
函数
用途
常见场景
`ROW_NUMBER()`
唯一序号,不重复
去重保留最新、分页
`RANK()`
排名,并列跳号
TopN、竞赛排名
`DENSE_RANK()`
排名,并列不跳号
等级评定
`LAG/LEAD`
取前/后行数据
同比/环比计算
`SUM/AVG OVER`
累计/移动计算
累计销售额、移动平均
`NTILE(n)`
等分分桶
用户分层、A/B实验
`PERCENT_RANK`
百分比排名
分位数分析
面试高频组合:
-
去重取最新 →
ROW_NUMBER() + PARTITION BY + ORDER BY 时间 DESC -
分组 TopN →
RANK() + PARTITION BY 分组 + WHERE rank <= N -
环比增长 →
LAG(列, 1) OVER (ORDER BY 时间) -
累计求和 →
SUM() OVER (ORDER BY 时间 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
`-- 公主号:船长Talk(坚持更新数据分析干货,欢迎关注) -- 建议:把本文代码放到 MySQL/Hive/PostgreSQL 环境中实际跑一遍,印象会深很多 -- 有问题欢迎评论区留言,船长看到都会回复