ROW_NUMBER()按组排序取前3名最可靠,因其严格按顺序唯一编号、不跳号;需配合PARTITION BY分组和ORDER BY指定升降序,外层用WHERE rn<=3筛选。

用 ROW_NUMBER() 按组排序取前3名最可靠
直接用 ROW_NUMBER() 是最常用、语义最清晰的方式。它严格按指定顺序为每组内行编号,不会跳号,适合“第1、2、3名”这种精确排名需求。
常见错误是误用 RANK() 或 DENSE_RANK():当存在并列成绩时,RANK() 会跳过后续名次(如 1,1,3),导致实际返回超过3行;而 ROW_NUMBER() 强制唯一编号,确保每组恰好最多3行(即使分数相同)。
- 必须搭配
PARTITION BY明确分组字段,比如按department分组 -
ORDER BY要写在OVER子句里,且需明确升降序(如score DESC) - 外层查询必须用
WHERE rn 过滤,不能在 <code>WHERE中直接调用窗口函数(会报错WINDOW FUNCTION NOT ALLOWED HERE)
SELECT name, department, score
FROM (
SELECT name, department, score,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY score DESC) AS rn
FROM employees
) ranked
WHERE rn <= 3;
处理并列情况时改用 DENSE_RANK()
如果业务要求“并列第2名之后还是第3名”,比如分数为 [95,95,90,88],希望前三名是三人(两个第1、一个第2),那就该用 DENSE_RANK()。它不跳号,但无法保证结果行数 ≤3 —— 若多人并列第3,会返回超过3行。
-
DENSE_RANK()对相同值给相同名次,后续名次连续(1,1,2,3) - 仍需子查询或 CTE 封装,不能在
WHERE中直接写DENSE_RANK() OVER (...) - 若必须限制总行数为3,得额外加
LIMIT(但会破坏分组边界,慎用)
SELECT name, department, score
FROM (
SELECT name, department, score,
DENSE_RANK() OVER (PARTITION BY department ORDER BY score DESC) AS dr
FROM employees
) d
WHERE dr <= 3;
性能关键:分组字段和排序字段要有联合索引
窗口函数本身不走索引,但 PARTITION BY + ORDER BY 的组合会显著影响执行计划。PostgreSQL 14+ 支持对 PARTITION BY a ORDER BY b 场景利用 (a, b) 联合索引减少排序开销。
- 没有索引时,每组数据都要临时排序,大数据量下
EXPLAIN显示WindowAgg节点耗时陡增 - 建索引示例:
CREATE INDEX idx_dept_score ON employees (department, score DESC); - 注意:单列索引(如只在
department上)对窗口排序基本无帮助
别在 HAVING 或 GROUP BY 后直接用窗口函数
窗口函数只能出现在 SELECT 列表或 ORDER BY 子句中,不能用于 WHERE、HAVING、GROUP BY 或聚合函数内部。这是初学者高频报错点,典型错误信息是 ERROR: WINDOW FUNCTION NOT ALLOWED HERE。
- 正确做法永远是子查询或 CTE 封装,先算出排名,再在外层过滤
- 试图写成
SELECT ... HAVING ROW_NUMBER() OVER (...) 一定失败 - CTE 写法更易读,但和子查询性能一致,无需强求:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn FROM products ) SELECT name, category, sales FROM ranked WHERE rn <= 3;实际业务中,“前3名”是否允许并列、是否需要稳定排序(如分数相同时按插入时间保序)、以及数据量级,会决定你选
ROW_NUMBER() 还是 DENSE_RANK(),也会影响索引设计。这些细节不写进 SQL 里,结果就可能和预期差一截。

















