因为max()/min()是聚合函数,group by时只返回极值本身,无法保证其他字段来自同一行;应使用row_number()窗口函数按组排序取首行,或在不支持窗口函数时用子查询关联极值并注意null和唯一性问题。

为什么直接用 GROUP BY + MAX/MIN 拿不到整行数据?
因为 MAX() 和 MIN() 是聚合函数,配合 GROUP BY 时只会返回该组的极值本身(比如最大金额),但无法保证这行的其他字段(如订单号、用户ID)来自同一记录。数据库不保证聚合结果和哪一行对齐——这是 SQL 标准行为,不是 bug。
用窗口函数 ROW_NUMBER() 按组排序取首行最可靠
在支持窗口函数的数据库(PostgreSQL、SQL Server、MySQL 8.0+、Oracle、BigQuery)中,这是首选方案:先按分组和目标字段排序,再取每组序号为 1 的整行。
示例:查每个用户的最新订单(按 order_time 最大):
SELECT user_id, order_id, order_time, amount
FROM (
SELECT user_id, order_id, order_time, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn
FROM orders
) t
WHERE rn = 1;
-
PARTITION BY user_id实现分组,ORDER BY order_time DESC确保最新时间排第一 - 用
ROW_NUMBER()而非RANK()或DENSE_RANK():避免并列时多行被选中(除非你明确需要所有并列极值) - 如果要取最小值对应行,把
DESC改成ASC
MySQL 5.7 或 SQLite 等不支持窗口函数时,用自关联或子查询
核心思路是:先算出每组的极值(如最大 order_time),再用这个值去匹配原表中对应行。
示例(MySQL 5.7):
SELECT o1.user_id, o1.order_id, o1.order_time, o1.amount FROM orders o1 INNER JOIN ( SELECT user_id, MAX(order_time) AS max_time FROM orders GROUP BY user_id ) o2 ON o1.user_id = o2.user_id AND o1.order_time = o2.max_time;
- 必须确保
(user_id, order_time)组合唯一,否则可能返回多行(比如两个订单同时间) - 若存在并列,且只想要一行,需额外加限制(如取
order_id最小的那个),此时得嵌套一层MIN(order_id)或改用LIMIT 1配合ORDER BY - 性能上,给
(user_id, order_time)建联合索引能显著加速
用 JOIN 关联极值时容易忽略 NULL 和重复问题
如果用 LEFT JOIN 或没写严等值条件,可能引入 NULL 或意外多行。常见错误包括:
- 漏写分组字段的等值条件(如只连了
order_time,没连user_id),导致跨用户匹配 - 子查询里
GROUP BY字段和外层JOIN字段不一致(大小写、空格、NULL 处理差异) - 极值字段含 NULL:多数数据库中
MAX()忽略 NULL,但匹配时o1.time = o2.max_time在 NULL 时恒为 false,导致丢失整组数据
处理 NULL 安全的方式是显式过滤或用 COALESCE 对齐(例如 COALESCE(o1.order_time, '1970-01-01') = COALESCE(o2.max_time, '1970-01-01')),但更推荐在业务逻辑中约定极值字段非空。










