Rank函数必须配合OVER()使用,不可裸用在JOIN条件中;正确做法是先用CTE或子查询计算排名再关联,且PARTITION BY不可遗漏,否则无法实现分组内排名。

Rank函数在关联前必须加窗口定义
直接在 JOIN 条件里写 RANK() 会报错,因为 RANK() 是窗口函数,必须配合 OVER() 子句。常见错误是试图这样写:ON a.id = b.id AND RANK() OVER (ORDER BY b.score DESC) = 1 —— 这语法非法,RANK() 不能裸用在 ON 中。
正确做法是先用子查询或 CTE 给右表打上排名,再关联:
WITH ranked_b AS ( SELECT *, RANK() OVER (PARTITION BY category ORDER BY score DESC) AS rnk FROM b ) SELECT a.*, b.score, b.rnk FROM a JOIN ranked_b b ON a.category = b.category AND b.rnk = 1;
注意 PARTITION BY 很关键:如果漏掉,整个表只排一次名,不是“每类取 Top 1”。
Rank vs Row_Number:重复值处理逻辑不同
当多条记录分数相同时,RANK() 和 ROW_NUMBER() 行为差异直接影响筛选结果。比如两个并列最高分,RANK() 都给 1,下一名是 3;ROW_NUMBER() 则强制编号为 1 和 2。
选择依据取决于业务需求:
- 要保留所有并列最优项(如“所有满分学生”)→ 用
RANK() - 严格只取一条(如“随机选一个最高分”)→ 用
ROW_NUMBER(),但需加ORDER BY ... , RANDOM()或主键避免不确定性 - 想跳过并列、按顺序紧排(如“第二高分一定排 2”)→ 用
DENSE_RANK()
误用 ROW_NUMBER() 替代 RANK() 是线上取数偏差的常见原因。
Join时Rank条件放ON还是WHERE?性能差别很大
把排名过滤写在 ON 里(如 ON ... AND b.rnk = 1)和写在 WHERE 里,对 LEFT JOIN 结果完全不同,但对 INNER JOIN 表面等价,底层执行计划可能差很多。
实际建议:
- 优先在子查询/CTE 中完成排名 + 过滤(即先
WHERE rnk = 1),再JOIN—— 这样驱动表小,索引更易生效 - 避免在
ON中写b.rnk = 1同时又没提前过滤:数据库可能先全量关联再过滤,内存和 IO 暴涨 - 如果右表数据量大,确保
PARTITION BY字段和ORDER BY字段上有联合索引(如(category, score))
MySQL 8.0+ 和 PostgreSQL 支持一致,但旧版 MySQL 不行
MySQL 在 8.0 之前不支持窗口函数,强行用会报 ERROR 1064:This version of MySQL doesn't yet support 'RANK() OVER'。PostgreSQL 从 8.4 就支持,无兼容问题。
旧版 MySQL 替代方案只有自连接或变量法,但极易出错:
-- 变量法(不推荐,依赖执行顺序,5.7 已警告废弃)
SELECT * FROM (
SELECT *,
@rnk := IF(@cat = category, @rnk + 1, 1) AS rnk,
@cat := category
FROM b
ORDER BY category, score DESC
) t
WHERE rnk = 1;
变量法在并发查询、优化器重排时行为不可控,上线前务必压测验证。能升级到 MySQL 8.0+ 就别硬扛。
真正容易被忽略的是:排名字段参与 JOIN 时,如果左表有重复 category,而右表多个 rnk = 1,就会产生笛卡尔积——得靠业务逻辑确认是否允许一对多,否则得加 DISTINCT 或二次去重。










