必须用 ROW_NUMBER() 而非 RANK() 或 DENSE_RANK(),因其为每行分配唯一连续序号,确保“前3名”严格返回最多3条记录;RANK() 跳号、DENSE_RANK() 并列不跳号但可能超量,均无法满足精确取前N条的业务需求。

直接用 ROW_NUMBER() 配合 PARTITION BY 和子查询,是筛选分组后排名靠前数据最可靠、最可控的方式。其他方法要么语义模糊(如 RANK() 可能返回超量行),要么兼容性差(如旧版 MySQL 不支持窗口函数)。
为什么必须用 ROW_NUMBER() 而不是 RANK() 或 DENSE_RANK()
业务说“前3名”,通常指物理上最多取3条记录,而不是“所有排名 ≤ 3 的行”。RANK() 遇到并列会跳号(比如两个第1名后直接第3名),DENSE_RANK() 不跳号但可能让一组返回5条(三个95分 → 全是第1名)。只有 ROW_NUMBER() 严格按排序顺序给唯一序号,WHERE rn 才真能卡死数量。
- 误用
RANK()的典型现象:WHERE rk 返回6行,只因前4行分数全相同 -
ROW_NUMBER()必须写ORDER BY,否则报错:Window function 'ROW_NUMBER' requires an OVER clause - 排序字段建议补唯一键防不稳定,例如:
ORDER BY total_amount DESC, order_id ASC
WHERE 里不能直接写 ROW_NUMBER() 的原因
窗口函数在 SQL 执行顺序中晚于 WHERE,所以 WHERE ROW_NUMBER() OVER (...) 会直接报错:<code>window function is not allowed in WHERE clause。这是标准限制,不是某数据库的 bug。
- 唯一解法是套一层子查询或
WITHCTE - 子查询写法兼容性最好,MySQL 5.7+(有窗口函数)、PostgreSQL、SQL Server 都能跑
- CTE 更易读,但 MySQL 5.7 不支持;若基础数据需过滤(如
WHERE status = 'completed'),CTE 逻辑更清晰
性能和 NULL 值容易被忽略的坑
大表上跑分组 Top N 最慢的环节不是计算排名,而是分组 + 排序本身。没索引时,数据库得把整张表拉进内存或临时文件排序,Created_tmp_disk_tables 状态值会飙升。
-
PARTITION BY和ORDER BY字段必须有联合索引,例如:(category, total_amount) -
total_amount含NULL时,MySQL 默认把NULL当最大值排最前——你可能无意中把一堆空值当成了“最高分” - 显式控制
NULL位置:用ORDER BY total_amount DESC NULLS LAST(PostgreSQL/Oracle/MySQL 8.0+ 支持),或曲线救国:ORDER BY total_amount IS NULL, total_amount DESC
真正卡住的往往不是语法,而是数据规模上来后分组排序的资源消耗。上线前先确认数据量级,再决定要不要加索引,或者是否接受近似结果(比如用采样估算 Top N)。

















