用MIN/MAX查时间字段只能得到时间戳,无法获取整行记录;正确做法是用窗口函数ROW_NUMBER()或子查询+LIMIT 1分别获取最早和最新完整行,并注意NULL、索引以及时区问题。

用 MIN/MAX 直接查时间字段只能拿到值,拿不到整行记录
很多人试过 SELECT MIN(created_at), MAX(created_at) FROM orders,结果只得到两个时间戳,没法关联到对应订单的 id、user_id 等字段。这是因为 MIN 和 MAX 是聚合函数,会折叠行,原始行信息就丢了。
真正要“获取最新和最早记录”,本质是查两条完整行:一条 created_at 最小的,一条最大的。不能靠单个 GROUP BY 或简单聚合解决。
推荐做法:用窗口函数 + 条件过滤(兼容 PostgreSQL / SQL Server / MySQL 8.0+)
给每条记录按时间排序打上序号,再筛出第一和最后一条。比子查询更清晰,性能也通常更好。
- 用
ROW_NUMBER() OVER (ORDER BY created_at)得最早记录(序号 = 1) - 用
ROW_NUMBER() OVER (ORDER BY created_at DESC)得最新记录(序号 = 1) - 把两个结果
UNION ALL合并,避免去重误删重复时间的记录
SELECT id, user_id, created_at, amount
FROM (
SELECT *,
ROW_NUMBER() OVER (ORDER BY created_at) AS rn_asc,
ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn_desc
FROM orders
) t
WHERE rn_asc = 1 OR rn_desc = 1;
兼容老版本 MySQL(5.7 及以下):用子查询 + LIMIT 1 拼 UNION
老 MySQL 不支持窗口函数,但可以用两个独立子查询分别取最早和最新。注意必须用 UNION ALL,否则如果最早和最新是同一条记录(比如表里只有一行),UNION 会去重导致漏数据。
- 第一个子查询加
ORDER BY created_at LIMIT 1 - 第二个子查询加
ORDER BY created_at DESC LIMIT 1 - 两个子查询字段顺序和类型必须一致,否则
UNION报错
(SELECT id, user_id, created_at, amount FROM orders ORDER BY created_at LIMIT 1) UNION ALL (SELECT id, user_id, created_at, amount FROM orders ORDER BY created_at DESC LIMIT 1);
容易踩的坑:NULL 值、索引缺失、时区隐含问题
created_at 字段如果有 NULL,MIN/MAX 默认忽略,但 ORDER BY 在不同数据库中对 NULL 的默认排序位置不一致(MySQL 默认 NULL 排最前,PostgreSQL 默认最末)。实际查最新/最早时可能拿到意外结果。
- 确保
created_at加了NOT NULL约束,或显式过滤:WHERE created_at IS NOT NULL - 在
created_at上建索引,否则ORDER BY ... LIMIT 1可能全表扫描 - 如果应用写入时用了本地时区而数据库存的是 UTC,
MAX(created_at)对应的记录未必是业务意义上“最新下单”的那条
真正难的不是语法,而是确认“最新”和“最早”在你业务里到底由哪个字段定义、这个字段是否可靠、有没有被人为修改过。










