应选RANK()当需并列排名(相同值同名次且后续跳号),选ROW_NUMBER()当需唯一连续编号;业务中若要求“并列不跳号”实为误述,正确应为DENSE_RANK(),RANK()典型场景是体育排名,ROW_NUMBER()适用于分页、去重。

MySQL 8.0+ 用 RANK() 还是 ROW_NUMBER()?
选哪个取决于你是否允许并列。比如分数相同都算第1名,后续跳过第2名——这是 RANK();如果强制连续编号(相同分数也分第1、第2),就得用 ROW_NUMBER()。实际业务中多数场景要的是“并列不跳号”,所以优先试 RANK()。
示例:按部门分组查薪资前三高的员工
SELECT dept, name, salary
FROM (
SELECT dept, name, salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
FROM employees
) t
WHERE rk <= 3;
PostgreSQL 或 SQL Server 怎么写?
语法几乎一致,RANK() 和 ROW_NUMBER() 都支持,但注意 PostgreSQL 对 NULL 的排序默认是 NULLS LAST,如果薪资字段可能为空,又想让空值排在末尾,得显式写:
ORDER BY salary DESC NULLS LAST
SQL Server 同样支持,但旧版本(2012+)才支持窗口函数,低于 2012 的必须用自关联或子查询模拟,性能差、易出错,建议升级。
常见坑:
-
PARTITION BY字段写错(比如写成GROUP BY习惯,漏掉OVER) - ORDER BY 里混用 ASC/DESC 导致排名逆序(比如想取最高分却写了
ORDER BY salary ASC) - 没加括号包裹子查询,直接在外部 WHERE 里引用
rk报错:“column does not exist”
SQLite 或老版本 MySQL(5.7)怎么兼容?
没有窗口函数就只能靠相关子查询,但性能随数据量增长急剧下降,万级数据就明显卡顿。
核心思路:对每条记录,统计同组中比它分数高(或等于且 ID 更小)的记录数 ≤ 2。
SELECT e1.dept, e1.name, e1.salary
FROM employees e1
WHERE (
SELECT COUNT(*)
FROM employees e2
WHERE e2.dept = e1.dept
AND (e2.salary > e1.salary
OR (e2.salary = e1.salary AND e2.id < e1.id))
) < 3;
注意点:
- 必须用
AND e2.id 处理并列情况,否则相同分数全挤进结果 - 索引一定要建在
(dept, salary, id)上,否则慢得没法用 - MySQL 5.7 默认不支持在子查询里引用外层表别名(报错 “Unknown column”),得改写成 JOIN 形式或升级到 8.0
为什么 LIMIT 3 在 GROUP BY 里不管用?
因为 GROUP BY 是聚合操作,LIMIT 是最终结果限制,不是每组限3条。写成 GROUP BY dept LIMIT 3 只会返回任意3个部门的聚合结果,不是每个部门的前3条。
有人试过 GROUP_CONCAT + SUBSTRING_INDEX 拼接再拆解,看似能绕过,但有长度限制(group_concat_max_len 默认 1024)、无法保留原始字段类型、排序不稳定,纯属自找麻烦。
真正要注意的其实是数据倾斜:如果某个部门有上万员工,而其他部门只有几个,RANK() 窗口计算仍会扫描全量,但执行计划里看不到瓶颈在哪——得看 EXPLAIN ANALYZE 的 actual rows 是否远超预期。

















