应使用 RANK() 而非 ROW_NUMBER(),因其支持并列排名(如两个第1名后直接第3名),符合“每组前三名”的业务语义;需配合 OVER 子句,排序字段不能为 NULL,且窗口函数须在子查询中计算后再筛选。

为什么用 RANK() 而不是 ROW_NUMBER()?
因为 RANK() 对相同值并列排名(比如两个并列第1,下一个就是第3),而业务中“每组前三名”通常允许并列——例如销售榜上两人同分 95 分,都算第1名,第三名是下一个分数(哪怕只有1人)。如果强行用 ROW_NUMBER(),会把并列者拆成 1、2,破坏真实排名逻辑。
常见错误现象:RANK() 写成 RANK(漏括号)导致语法报错;或没加 OVER 子句直接调用,报 Window function requires OVER clause。
-
RANK()必须配合OVER (PARTITION BY ... ORDER BY ...) - 排序字段不能为
NULL,否则这些行会被排在最前或最后(取决于ORDER BY ... ASC/DESC),影响“前三”结果 - 若需严格取最多3条(即使有并列第3),得换用
ROW_NUMBER();但标题明确要“前三名”,默认按并列语义处理
子查询怎么嵌套才不报错?
核心是:窗口函数不能出现在 WHERE 或 HAVING 中,必须先在子查询里算出排名,再在外层筛选。直接写 WHERE RANK() OVER (...) 会报错 invalid window function reference。
典型写法:
SELECT * FROM ( SELECT *, RANK() OVER (PARTITION BY category ORDER BY score DESC) AS rk FROM products ) t WHERE t.rk
- 子查询必须起别名(如
t),否则外层WHERE引用rk会报unknown column - 别名不能和原表字段冲突,比如原表有
rk字段,子查询里又用AS rk,外层可能歧义 - MySQL 8.0+、PostgreSQL、SQL Server 支持;SQLite 3.25+ 也支持,但旧版不支持窗口函数
GROUP BY 和 PARTITION BY 别混了
PARTITION BY 是窗口函数的分组,不压缩行数;GROUP BY 是聚合,会把多行变一行。想查“每组前三条原始记录”,必须用 PARTITION BY,而不是写 GROUP BY category 后加 ORDER BY score DESC LIMIT 3——后者在大多数数据库里语法非法,且逻辑错误。
- 误用
GROUP BY的典型错误:只返回每组1条(聚合后),还可能因没MAX(score)等聚合函数导致字段报错 -
PARTITION BY category表示“按 category 分区,各自独立排名”,不影响原始行数 - 如果分区字段有
NULL,PARTITION BY会把所有NULL归为同一组——这常被忽略,导致“未知分类”的数据意外挤进某组前三
性能慢怎么办?索引怎么建?
对大表跑 RANK() + 子查询,慢的主因是排序开销。关键不是加索引就能解决,而是索引要匹配 OVER 子句里的顺序:
- 最优索引:
CREATE INDEX idx_cat_score ON table_name (category, score DESC); - 注意:
category必须在前,score在后;反过来或单独建单字段索引效果甚微 - 如果
score经常NULL,考虑加WHERE score IS NOT NULL过滤,避免排序时处理大量空值 - PostgreSQL 可用物化视图缓存结果;MySQL 8.0+ 建议关掉
sort_buffer_size过小导致的磁盘临时表
真正卡住的点往往是分区键基数太高(比如每组平均只有2条,却按 user_id 分区),这时 RANK() 几乎等于全表排序——得先确认“每组”是否合理,而不是硬优化 SQL。










