ROW_NUMBER()仅保证分区内唯一,非全局唯一;使用PARTITION BY时各分区独立编号,相同序号可重复;ORDER BY字段不唯一会导致编号非确定;需全局唯一应改用IDENTITY、SEQUENCE或无PARTITION BY的ROW_NUMBER()配合唯一排序。

ROW_NUMBER() 本身不保证全局唯一,只保证分区唯一
它根本就不是为“全局唯一”设计的。只要用了 PARTITION BY,编号就在每个分区内从 1 重新开始,不同分区的行完全可能拿到相同的 ROW_NUMBER() 值。比如两个部门都有一条记录被标为 rn = 1,这完全正常,也符合语义——它们只是各自部门里的“第一条”,不是全表第一。
常见错误现象:把 ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY hire_date) 的结果当主键或去重依据,结果发现多条记录 rn = 1 并存,后续逻辑崩了。
-
PARTITION BY是开关,不是装饰:没写它,才可能得到全局连续编号(但依然不等于“唯一标识”,因为排序字段重复时行为非确定) - 真正需要全局唯一序号,优先考虑
IDENTITY列、SEQUENCE,或用ROW_NUMBER() OVER (ORDER BY ...)(无PARTITION BY)配合唯一排序字段 - 如果业务上既要分组又要全局可区分,得自己拼接:比如
CONCAT(dept_id, '_', ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY hire_date))
ORDER BY 字段不唯一时,ROW_NUMBER() 的编号是随机的
这是最容易被忽略的“非确定性”问题。文档明确说:ROW_NUMBER() 在 ORDER BY 值相同时不保证顺序。哪怕两行 score = 85,下次执行查询,谁得 1 谁得 2 可能就反过来了。
使用场景:做“取最新一条记录”时,仅靠 ORDER BY create_time DESC 不够稳——如果存在毫秒级相同时间戳的并发插入,rn = 1 就不可靠。
- 必须确保
ORDER BY表达式组合是唯一的:例如加一个二级排序ORDER BY create_time DESC, id DESC - 别指望数据库自动补全:它不会用主键隐式去重,你得显式写出来
- 在 Hive 或旧版 MySQL(
别名不能在 WHERE 中直接过滤,否则报错或逻辑错
ROW_NUMBER() 是窗口函数,在 SELECT 阶段计算;而 WHERE 是在它之前执行的。所以写 WHERE rn = 1 会提示 Unknown column 'rn',或者(更危险的)被优化器误解释为其他字段。
错误写法:SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC) AS rn FROM logs WHERE rn = 1;
- 必须用子查询或 CTE 包裹:先算出
rn,再在外层WHERE过滤 - CTE 更易读:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) SELECT * FROM ranked WHERE rn = 1; - 注意性能:大表上套一层子查询不会额外增加计算量,但若漏掉索引,
PARTITION BY+ORDER BY字段无索引,就会拖慢整个查询
PARTITION BY 字段选错,结果完全失真
把 PARTITION BY city 写成 PARTITION BY department,看起来只是换了个字段,实际会让“每个城市的销售员在部门内排名”变成“每个部门的销售员在城市内排名”,语义彻底颠倒。
典型表现:本该返回每个用户最新订单,结果返回了每个产品类别的最新订单——因为 PARTITION BY 错写成了 product_id 而不是 user_id。
- 检查业务口径比调 SQL 更重要:先确认“分组单位”是什么(用户?设备?会话?),再找对应字段
- 字段含义模糊时,宁可
SELECT DISTINCT看一眼取值分布,也不凭名字瞎猜 - 复合分区(如
PARTITION BY user_id, DATE(created_at))要特别小心时区和日期截断逻辑
真正麻烦的从来不是语法写不对,而是 PARTITION BY 和 ORDER BY 的字段选错了业务维度,或者忽略了排序字段本身的重复性——这两点一旦出错,数据看起来“有编号”,实则全是幻觉。

















