ROW_NUMBER()需配合PARTITION BY分组和ORDER BY定义“最新”,如PARTITION BY user_id ORDER BY created_at DESC;WHERE rn=1须在子查询或CTE中使用,不可直接在窗口函数层过滤。

ROW_NUMBER() 必须配合 PARTITION BY 和 ORDER BY 才能取每组最新
单独写 ROW_NUMBER() 不会自动分组,也不懂哪条是“最新”——它只按你指定的排序规则给整张表连续编号。真正实现“每组最新”,得靠 PARTITION BY 切分组、ORDER BY 定义“新”的标准(比如按 created_at DESC 或 id DESC)。
常见错误现象:
– 写了 ROW_NUMBER() OVER (ORDER BY created_at DESC) 却没加 PARTITION BY,结果只返回全表最新一条
– ORDER BY 用了升序(ASC),却以为能拿到最新记录
- 使用场景:查每个用户最近一次订单、每个商品最新价格、每个设备最后上报状态
- 关键点:分组字段必须是业务上定义“一组”的依据,比如
user_id、product_id - 性能影响:如果
ORDER BY字段没索引,大表上窗口函数会明显变慢
示例(PostgreSQL/MySQL 8.0+):
SELECT * FROM (<br> SELECT *, ROW_NUMBER() OVER (<br> PARTITION BY user_id ORDER BY created_at DESC, id DESC<br> ) AS rn<br> FROM orders<br>) t WHERE rn = 1;
WHERE rn = 1 不能写在窗口函数同一层
窗口函数不能出现在 WHERE 子句里——因为 WHERE 执行时,ROW_NUMBER() 还没计算。必须用子查询或 CTE 把带 rn 的结果先算出来,再过滤。
常见错误现象:
– 直接写 WHERE ROW_NUMBER() OVER (...) = 1 → 报错 invalid window function in WHERE clause
– 在 GROUP BY 后尝试用 ROW_NUMBER() → 语法不合法
- MySQL 8.0+、PostgreSQL、SQL Server 都遵循这个执行顺序限制
- SQLite 3.25+ 同样不允许,但错误信息可能更模糊
- CTE 写法更易读,但子查询在简单场景下性能通常无差别
ORDER BY 里加第二字段防并列,避免漏数据
如果分组内有多个记录时间戳完全相同,ROW_NUMBER() 会按不确定顺序分配 1/2/3……导致每次执行结果不一致,甚至漏掉本该选中的记录。
使用场景:日志表只有 DATE 精度、批量导入导致 created_at 重复、业务未严格控制唯一性
- 补救方式:在
ORDER BY末尾加一个确定性字段,如主键id DESC或自增字段 - 不要用
RAND()或无意义字段,会导致不可复现 - 如果业务上允许任意一条,可用
ROW_NUMBER() OVER (PARTITION BY x ORDER BY y DESC, ctid)(PostgreSQL)或%%physloc%%(SQL Server)作为兜底
替代方案:用 DISTINCT ON(PostgreSQL)或相关子查询更直观
DISTINCT ON 是 PostgreSQL 特有语法,语义更贴近“每组取第一条”,且通常比窗口函数快一点;其他数据库只能靠子查询或 JOIN 自关联,但逻辑更绕、容易写错关联条件。
常见错误现象:
– 在非 PostgreSQL 环境误用 DISTINCT ON → 语法错误
– 子查询里漏写外层表别名,导致 correlated subquery returns more than one row
-
DISTINCT ON (user_id) ORDER BY user_id, created_at DESC要求ORDER BY开头必须是DISTINCT ON字段 - 子查询方案中,
WHERE o2.user_id = o1.user_id AND o2.created_at > o1.created_at漏掉等号会丢掉时间相同的情况 - 窗口函数仍是跨数据库兼容性最好的通用解法
容易被忽略的是:当“最新”依赖多个条件(比如状态为 active 且时间最新),所有筛选必须放在窗口函数的子查询里完成,而不是在外层 WHERE 加,否则可能把本该入选的记录提前过滤掉。

















