使用窗口函数ROW_NUMBER()、RANK()或DENSE_RANK()配合PARTITION BY实现分组内排序,保留每行记录;如查各部门工资前3员工:SELECT * FROM (SELECT name,dept,salary,ROW_NUMBER() OVER(PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees) t WHERE rn

SQL 实现分组内排序,主要靠窗口函数 ROW_NUMBER()、RANK() 或 DENSE_RANK() 配合 PARTITION BY 子句,而不是用 GROUP BY —— 因为 GROUP BY 会聚合数据,无法保留每行原始记录来排序。
用 PARTITION BY + 排序窗口函数
这是最常用、最直接的方式:按指定字段分组,在每组内部按另一字段排序,并为每行生成序号。
- PARTITION BY 定义分组依据(类似 GROUP BY 的分组逻辑,但不压缩行)
- ORDER BY 写在窗口函数中,决定组内排序规则
- 例如:查每个部门工资最高的前 3 名员工
SELECT * FROM (
SELECT name, dept, salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
) t WHERE rn
ROW_NUMBER vs RANK vs DENSE_RANK 的区别
三者都支持分组内排序,但处理并列的方式不同:
- ROW_NUMBER():严格按顺序编号,相同值也分配不同序号(1,2,3,4…)
- RANK():并列时给相同序号,跳过后续位次(1,2,2,4…)
- DENSE_RANK():并列时给相同序号,不跳位次(1,2,2,3…)
比如某部门三人薪资为 15000、12000、12000:
ROW_NUMBER → 1,2,3;RANK → 1,2,2;DENSE_RANK → 1,2,2
配合 WHERE 或子查询筛选分组内 Top-N
窗口函数不能直接在 WHERE 中使用(因为执行顺序晚于 WHERE),必须嵌套子查询或 CTE:
- 写法一:用子查询包裹,外层加 WHERE 过滤序号
- 写法二:用 CTE 更清晰(尤其多步计算时)
- 注意:ORDER BY 在窗口函数里只影响排序逻辑,不影响最终结果集顺序;如需整体有序,外层再加 ORDER BY
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS seq
FROM products
)
SELECT name, category, score FROM ranked WHERE seq = 1;
旧版本 MySQL(
只能用自连接或相关子查询模拟,性能较差,且逻辑复杂:
- 自连接:对每行统计同组中比它大的记录数,+1 即为名次
- 示例(找每科最高分学生):对 scores 表,按 subject 分组,用 LEFT JOIN 找出“没有更高分”的记录
- 建议升级到 MySQL 8.0+ 或 PostgreSQL / SQL Server 等原生支持窗口函数的数据库
不复杂但容易忽略:分组内排序本质是“每行独立计算位置”,不是聚合,所以别用 GROUP BY 去尝试实现。
