rank()和dense_rank()的根本区别在于并列后是否跳过后续名次:rank()跳过(如两个第1名后为第3名),dense_rank()不跳过(两个第1名后为第2名);两者均需order by,按部门排名须用partition by dept_id。

为什么RANK()和DENSE_RANK()会给出不同结果?
根本区别在「并列后是否跳过后续名次」:当有重复值时,RANK()会跳过被占用的名次(比如两个第1名后直接是第3名),而DENSE_RANK()紧接上一名次(两个第1名后是第2名)。这不是bug,是设计逻辑差异。
常见错误现象:用RANK()做“前N名”筛选时漏掉本该入选的记录——比如取前3名,但因并列导致实际返回5条;用DENSE_RANK()却误以为它能处理「按组内排名」而没加PARTITION BY。
-
RANK()适合强调「名次断层」场景,如赛事颁奖(并列冠军后是季军,没有亚军) -
DENSE_RANK()适合需要连续序号的业务,如分档位(S/A/B/C档,不能跳档) - 两者都必须配合
ORDER BY使用,否则报错:Window function 'RANK' requires an ORDER BY clause
怎么写才不会触发“窗口函数未定义分区”的错误?
如果要按部门分别排名,PARTITION BY不是可选的——漏写会导致全表统一排序,而非组内独立计算。典型错误是把PARTITION BY和GROUP BY混淆,或把它放在WHERE之后(语法不允许)。
正确写法必须放在OVER()括号内,且顺序固定:OVER (PARTITION BY dept_id ORDER BY salary DESC)。注意PARTITION BY字段类型需与SELECT中一致,否则可能隐式转换失败。
- MySQL 8.0+、PostgreSQL、SQL Server 支持完整语法;SQLite 3.25+ 支持,但旧版不支持
- Oracle 中两者行为一致,但历史版本对
NULL排序默认为最大值,可能影响并列判断 - 若
ORDER BY字段含NULL,不同数据库默认行为不同:PostgreSQL 默认NULLS LAST,MySQL 默认NULL排最前
如何用RANK()选出每个部门薪资前2的员工?
关键不是套函数,而是理解「排名函数返回的是行级别结果,不能直接在WHERE里引用别名」。必须用子查询或CTE包裹,否则报错:Unknown column 'rnk' in WHERE clause。
SELECT emp_name, dept_id, salary, rnk
FROM (
SELECT emp_name, dept_id, salary,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk
FROM employees
) t
WHERE rnk
- 用
RANK()时,若第2和第3名薪资相同,rnk = 2会返回3人(两个第2名);用DENSE_RANK()则只返回前2个名次对应的所有人 - 性能上,窗口函数比自关联或相关子查询快得多,但大数据量时仍建议在
PARTITION BY和ORDER BY字段建联合索引 - 避免在
ORDER BY里用表达式(如ORDER BY ABS(salary)),部分数据库无法利用索引
并列值太多时,DENSE_RANK()真的更“省名额”吗?
是的,但代价是丧失名次间隔信息。比如5人薪资全相同:RANK()全返回1,后续名次从6开始;DENSE_RANK()也全返回1,但下一名次是2——看起来“省”,实则是压缩了排名维度。
真正要注意的是:当业务要求「同一档位人数上限」时,DENSE_RANK()可能让大量人挤在第1档,而RANK()至少能靠名次断层暴露数据集中度问题。
- 若需控制每档人数,应结合
ROW_NUMBER()(严格不并列)或手动分桶(NTILE(4)) -
DENSE_RANK()对NULL的处理与RANK()一致:默认视为相等,一起排同一名次 - 某些报表工具(如Tableau)会自动把
RANK()结果渲染成带空隙的序号,而DENSE_RANK()渲染为紧凑序列,前端无需额外处理











