在业务报表开发中,经常需要按某个维度分组做统计,并在每个分组内部根据指标排名。传统写法容易写出多层嵌套,而合理利用现代SQL能力可以大幅简化逻辑并提升性能。

常见低效写法的问题
很多初学者会用关联子查询实现分组内排名,例如统计每个部门的薪资前三名:
SELECT dept_id, emp_name, salary FROM employee e1 WHERE ( SELECT COUNT(*) FROM employee e2 WHERE e2.dept_id = e1.dept_id AND e2.salary >= e1.salary ) <= 3 ORDER BY dept_id, salary DESC;
上述语句对每一行都要执行一次子查询,时间复杂度接近 O(n²),数据量大时非常缓慢。
使用窗口函数优化
窗口函数可以在不折叠行的前提下完成分组与排序,只需一次扫描:
SELECT dept_id, emp_name, salary
FROM (
SELECT
dept_id,
emp_name,
salary,
ROW_NUMBER() OVER (
PARTITION BY dept_id
ORDER BY salary DESC
) AS rn
FROM employee
) t
WHERE rn <= 3
ORDER BY dept_id, salary DESC;
关键语法说明
PARTITION BY对应分组统计中的分组列ORDER BY决定排名顺序ROW_NUMBER()生成连续序号,相同值也会排出不同名次
索引与执行计划建议
为让数据库避免额外排序,应建立复合索引覆盖分区与排序列:
CREATE INDEX idx_emp_dept_sal ON employee(dept_id, salary DESC);
通过 EXPLAIN 查看是否出现 Window 或 Sort 操作,若数据倾斜严重,可先按部门聚合预处理再排名。
不同排名函数对比
| 函数 | 相同值处理 | 适用场景 |
|---|---|---|
| ROW_NUMBER | 不同序号 | 严格取前 N 条 |
| RANK | 相同名次,后续跳号 | 允许并列且保留间隙 |
| DENSE_RANK | 相同名次,不跳号 | 并列且连续排名 |
总结
分组统计与排名分析应优先采用窗口函数替代关联子查询,配合合适索引与执行计划分析,能显著降低资源消耗并提升可维护性。