最稳妥方式是用 row_number() 配合 partition by category 和 order by updated_at desc, id desc,再筛选 rn=1;rank/dense_rank 因并列排名可能导致多条结果,不适用;postgresql 可用 distinct on,mysql 8.0+ 支持 qualify;需注意 null 处理和时间字段类型。

用 ROW_NUMBER() 按时间排序取每类第一条
最常用也最稳妥的方式是用 ROW_NUMBER() 配合 ORDER BY created_at DESC(或 updated_at),给每类内的记录编号,再过滤出序号为 1 的行。注意:必须用 DESC 保证最新排第一;如果时间字段有重复,建议加上唯一字段(比如 id)做二级排序,避免结果不稳定:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY updated_at DESC, id DESC
) AS rn
FROM products
) t WHERE rn = 1;
常见错误是只按时间升序排,结果取的是“最早”而非“最新”;或者漏写 PARTITION BY,导致全表只排一次序。
RANK() 和 DENSE_RANK() 为什么不适合取“最新一条”
这两个函数在遇到相同时间戳时会并列排名(比如两个 updated_at = '2024-05-01' 的记录都得 RANK = 1),导致结果可能返回多于一条记录——这和“每个类别只取一条最新”的需求冲突。除非你明确接受并列情况,否则不要用它们替代 ROW_NUMBER()。
-
RANK():并列后跳号(1,1,3) -
DENSE_RANK():并列后不跳号(1,1,2) -
ROW_NUMBER():强制唯一编号(1,2,3)
PostgreSQL 和 MySQL 8.0+ 支持直接 SELECT DISTINCT ON 或 QUALIFY
PostgreSQL 提供更简洁的语法:DISTINCT ON (category),但必须配合 ORDER BY category, updated_at DESC 才能确保取到最新:
SELECT DISTINCT ON (category) * FROM products ORDER BY category, updated_at DESC;
MySQL 8.0+ 可用 QUALIFY 省掉子查询:
SELECT * FROM products QUALIFY ROW_NUMBER() OVER ( PARTITION BY category ORDER BY updated_at DESC, id DESC ) = 1;
注意:DISTINCT ON 是 PostgreSQL 特有,QUALIFY 目前仅 MySQL 8.0+ 和某些数仓(如 BigQuery、Snowflake)支持,别在旧版 MySQL 或 SQLite 里硬套。
时间字段为空或类型不一致会导致排序错乱
如果 updated_at 允许为 NULL,默认排序时 NULLS FIRST(PostgreSQL)或 NULLS LAST(MySQL)行为不同,可能把空值排到最前,误认为“最新”。显式控制更安全:
- PostgreSQL:加
NULLS LAST - MySQL:用
IFNULL(updated_at, '1970-01-01')或COALESCE填充 - 所有数据库:先确认字段类型是
TIMESTAMP或DATETIME,不是字符串——字符串比较 '2024-10-01'
真正容易被忽略的,是时间字段的精度和时区。比如应用写入用 UTC,但数据库配置为本地时区,或者字段存的是秒级时间戳却用毫秒级比较——这类问题不会报错,但结果随机偏差。











