rank()等窗口函数必须配合partition by和order by使用,否则排名失效;需用cte或子查询过滤排名结果;多字段分区、null处理及数据库空值排序差异需特别注意。

PARTITION BY 和 ORDER BY 必须一起用,否则 RANK() 不生效
单独写 PARTITION BY department 不会自动排序,排名函数(如 RANK()、ROW_NUMBER())只在窗口内按 ORDER BY 指定的列排序后才计算。漏掉 ORDER BY 会导致结果无序、排名全为 1 或报错(取决于数据库)。
实操建议:
- 必须写成
OVER (PARTITION BY department ORDER BY salary DESC)这种完整结构 - 如果想按入职时间先后排名,就用
ORDER BY hire_date ASC - PostgreSQL 和 SQL Server 对空值默认排最前,MySQL 8+ 默认排最后,必要时显式写
NULLS LAST或NULLS FIRST
用 RANK() 还是 ROW_NUMBER()?看是否要处理并列
部门内两人薪资相同,RANK() 会给出相同名次,跳过后续名次(比如两个第1名,下一个是第3名);ROW_NUMBER() 强制不重复,按物理顺序给连续编号(两个第1名,下一个是第2名)。
常见场景:
- 绩效评优要体现“并列第1”,选
RANK() - 分页取前 N 条且不能漏人,用
ROW_NUMBER()更稳妥 -
DENSE_RANK()是折中方案:并列不跳号(两个第1名,下一个是第2名)
WHERE 不能直接过滤窗口函数结果,得套子查询或 CTE
写 WHERE rank = 1 会报错,因为窗口函数在 WHERE 之后执行。想查每个部门工资最高的人,不能这么写:
SELECT *, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank FROM employees WHERE rank = 1;
正确做法:
- 用 CTE:
WITH ranked AS (SELECT *, RANK() OVER (...) AS rank FROM employees) SELECT * FROM ranked WHERE rank = 1; - 或嵌套子查询:
SELECT * FROM (SELECT *, RANK() OVER (...) AS rank FROM employees) t WHERE t.rank = 1; - 注意:MySQL 8+ 支持 CTE,但旧版要用子查询;SQLite 3.8.3+ 才支持窗口函数
PARTITION BY 多字段时,注意逻辑是否符合业务“部门”定义
如果真实业务里“部门”由 dept_id 和 region 共同决定(比如华东销售部、华北销售部算不同部门),就得写 PARTITION BY dept_id, region。只写 PARTITION BY dept_id 会把跨区同名部门混在一起排名。
容易踩的坑:
- 字段类型不一致导致分区错误:比如
dept_id一个是字符串'001',一个是数字1,会被当成不同分区 - NULL 值单独成一个分区,可能意外多出一组“未知部门”的排名
- Oracle 对字符集敏感,
UPPER(dept_name)分区前最好确认数据已清洗
实际跑起来之前,先 SELECT department, COUNT(*) FROM employees GROUP BY department 看看分区基数是否合理,比直接套窗口函数更容易发现数据异常。











