row_number()需配合partition by和order by使用:按主表id分组、时间倒序排序后取rn=1;不可在where/on中直接使用,须用cte预计算;注意null处理及mysql版本兼容性。

ROW_NUMBER() 必须配合 PARTITION BY 和 ORDER BY 才能分组取最新
单独写 ROW_NUMBER() 不会自动“按主表分组”,它默认对全表编号。你真正需要的是:对每个主表 ID(比如 order_id)划分一组,再在每组内按时间(如 created_at)倒序排,最后只取序号为 1 的那条。
常见错误是漏掉 PARTITION BY,结果得到全局编号;或者 ORDER BY 写成升序,取到的是最早记录而非最新。
-
PARTITION BY的字段必须是主表唯一标识(如user_id、product_id),不能用从表的自增 ID -
ORDER BY推荐用带索引的时间字段(如updated_at),避免用无索引字段导致性能骤降 - 如果时间可能重复,建议追加一个确定性字段(如
id DESC)保证排序稳定
JOIN 主表时别直接在 ON 条件里用 ROW_NUMBER()
不能写 ON child.id = (SELECT id FROM (...) WHERE rn = 1) 这类子查询,更不能把 ROW_NUMBER() 放在 WHERE 子句里——窗口函数不能在 WHERE 或 ON 中直接使用,会报错 Window function is not allowed in WHERE clause。
正确做法是先用子查询或 CTE 把带序号的结果算出来,再和主表 JOIN:
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC, id DESC
) AS rn
FROM order_items
)
SELECT o.*, r.item_name, r.quantity
FROM orders o
LEFT JOIN ranked r ON o.id = r.order_id AND r.rn = 1;
LEFT JOIN vs INNER JOIN 取决于是否允许主表无从记录
用 LEFT JOIN 能保留所有主表行(即使某订单还没任何子项),对应从表字段为 NULL;用 INNER JOIN 则只返回至少有一条从记录的主表行。
注意:如果从表有逻辑删除(如 is_deleted = 0),必须在 CTE 的 SELECT 里就过滤掉,否则被删记录仍参与排序,可能把 rn = 1 占掉:
- ✅ 正确:在 CTE 内加
WHERE is_deleted = 0 - ❌ 错误:在外部
LEFT JOIN后再加AND r.is_deleted = 0,此时r.rn已按含已删数据算出
MySQL 8.0+、PostgreSQL、SQL Server 都支持,但旧版 MySQL 不行
如果你用的是 MySQL 5.7 或更早版本,ROW_NUMBER() 直接报错 FUNCTION xxx.ROW_NUMBER does not exist。此时只能用变量模拟(不稳定)或改用相关子查询(性能差)。
PostgreSQL 用户注意:ROW_NUMBER() 在 ORDER BY 中对 NULL 默认排最前,若时间字段允许为空,需显式写 ORDER BY updated_at DESC NULLS LAST,否则最新非空记录可能被挤到第 2 名。
真正容易被忽略的是排序字段的 NULL 行为和旧版本兼容性——这两个点不提前确认,上线后才发现查不到数据,比性能问题更难排查。











