做排行榜和环比还在用子查询?SQL 窗口函数一次搞定
做报表时常遇到三类需求:每人每月业绩排第几、这个月比上个月涨了多少、部门累计销售额到哪了。新手爱用子查询嵌套,写出来又长又慢还容易错。其实 SQL 的窗口函数(Window Function)就是为这种"分组内排名、行间比较"而生的——它不聚合、保留每一行,一行 SQL 全解决。
一、示例表
一张简单的销售表:
CREATE TABLE sales (
emp VARCHAR(20),
dept VARCHAR(20),
month DATE,
amount INT
);
-- 示例数据
INSERT INTO sales VALUES
('张三','销售一部','2024-01-01',8000),
('李四','销售一部','2024-01-01',9500),
('张三','销售一部','2024-02-01',9000),
('李四','销售一部','2024-02-01',7000);
二、组内排名:ROW_NUMBER / RANK
要"每个部门内按业绩排名",窗口函数一行搞定:
SELECT emp, dept, month, amount,
ROW_NUMBER() OVER (
PARTITION BY dept
ORDER BY amount DESC
) AS rn
FROM sales;
PARTITION BY dept 按部门分组(组内独立排名),ORDER BY amount DESC 决定排名顺序。关键点:窗口函数不合并行,原表几行结果就几行,只是多了一列排名。想要并列同名次用 RANK(),并列后跳号用 DENSE_RANK()。
三、环比增长:LAG 取上一期
“这个月比上个月涨了多少”,用 LAG 取同一人上一期的金额:
SELECT emp, month, amount,
LAG(amount) OVER (
PARTITION BY emp
ORDER BY month
) AS prev_amount,
ROUND(
(amount - LAG(amount) OVER (PARTITION BY emp ORDER BY month))
/ LAG(amount) OVER (PARTITION BY emp ORDER BY month) * 100, 1
) AS growth_pct
FROM sales
ORDER BY emp, month;
LAG(amount) 默认取"排序后前一行",配合 PARTITION BY emp 保证只跟自己上一期比,不会串到别人头上。
四、累计求和:SUM OVER
“部门累计销售额”,用 SUM() OVER 做累计:
SELECT emp, month, amount,
SUM(amount) OVER (
PARTITION BY dept
ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS dept_running_total
FROM sales;
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 表示"从组内第一行到当前行"累计。不写这个范围默认就是累计到当前行。
五、要注意的几个关键点
- GROUP BY 和窗口函数混淆:窗口函数不能和
GROUP BY混着当聚合用,它保留明细行。要既聚合又排名,先聚合出结果再套一层窗口函数。 - ORDER BY 缺了排名就乱:
ROW_NUMBER()必须配合ORDER BY,否则排名顺序是未定义的。 - LAG 第一行是 NULL:首期没有"上一期",
prev_amount为 NULL,算环比前先COALESCE兜底。 - 性能:窗口函数比多层子查询快,但
PARTITION BY的列该加索引还是得加,数据量大时差别明显。
| 需求 | 函数 | 说明 |
|---|---|---|
| 组内排名 | ROW_NUMBER/RANK | 不聚合、保留每行 |
| 环比/同比 | LAG / LEAD | 取前/后行 |
| 累计求和 | SUM OVER | 指定累计范围 |

小结
窗口函数把"排名、环比、累计"这类分析从"多层子查询地狱"里救了出来:一句 OVER (PARTITION BY ... ORDER BY ...) 同时搞定分组和排序,还不丢掉明细行。记住三件套——ROW_NUMBER 排名、LAG 取上期、SUM OVER 累计——日常报表八成以上的分析需求都能直接覆盖。比子查询可读、比应用层算得快,属于"早用早舒服"的 SQL 特性。
- 点赞
- 收藏
- 关注作者
评论(0)