用窗口函数row_number()最可靠,group by+max()只能取金额无法获取对应订单的其他字段;正确写法是子查询中用partition by分组、order by金额降序编号,外层筛选rn=1。

用窗口函数 ROW_NUMBER() 排序取 Top 1 最可靠
直接 GROUP BY + MAX(amount) 只能拿到金额,拿不到对应订单的其他字段(比如 order_id、customer_id)。真要“每组金额最高的那条完整记录”,必须靠窗口函数。
典型写法是用 ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY amount DESC) 给每组按金额降序编号,再外层筛选 rn = 1:
SELECT order_id, customer_id, amount, product
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn
FROM orders
) t
WHERE rn = 1;
- 如果同一客户有多个订单金额并列最高,
ROW_NUMBER()只返回其中一条(不确定哪条),适合“任选一”场景 - 想返回所有并列最高记录,改用
RANK()或DENSE_RANK(),然后过滤rank = 1 - 注意
ORDER BY里加id或created_at可让结果稳定(避免每次执行顺序不同)
WHERE ... IN (SELECT MAX(...)) 写法常见但有隐患
这种写法看着直观,但容易漏数据或重复返回:
SELECT * FROM orders o1 WHERE (o1.customer_id, o1.amount) IN ( SELECT customer_id, MAX(amount) FROM orders GROUP BY customer_id );
- 当存在多条同客户同最高金额记录时,它会全部返回——这有时是想要的,但多数业务要求“只取一条”
- 如果
amount允许为NULL,MAX()返回NULL,而IN对NULL的判断会失效(整个条件变UNKNOWN),导致该组无结果 - 性能上,子查询需对每组重新扫描,大数据量时比窗口函数慢不少
MySQL 8.0+ 和 PostgreSQL 支持直接 SELECT ... FROM ... LATERAL 或 JOIN LATERAL
PostgreSQL 可用 LATERAL 关联子查询,逻辑更贴近“对每个客户查其最高订单”:
SELECT c.customer_id, o.order_id, o.amount FROM customers c JOIN LATERAL ( SELECT * FROM orders o WHERE o.customer_id = c.customer_id ORDER BY o.amount DESC LIMIT 1 ) o ON true;
- MySQL 8.0+ 不支持
LATERAL,但可用类似写法配合JSON_TABLE或变量模拟,不推荐——复杂且不可靠 - PostgreSQL 中此写法在
customer_id有索引时,可利用索引快速定位每组 top 1,比全表窗口排序更快 - 注意:若某客户无订单,
JOIN LATERAL会丢掉该客户;要保留,改用LEFT JOIN LATERAL
别忽略 NULL 和空组问题
实际数据里,amount 是 NULL、某客户没有任何订单、或者分组字段本身为 NULL,都会让上述逻辑行为异常。
-
PARTITION BY customer_id会把所有customer_id IS NULL的行归为一组——如果你不想处理这类脏数据,提前WHERE customer_id IS NOT NULL -
ORDER BY amount DESC中,NULL默认排在最前(PostgreSQL)或最后(MySQL),结果不可控;稳妥做法是显式写ORDER BY amount DESC NULLS LAST(PostgreSQL)或用COALESCE(amount, 0)替换 - 如果某组完全为空(比如左连接后某些客户没订单),窗口函数仍会生成一行带
rn = 1的NULL记录——得靠外层WHERE过滤掉
真正上线前,务必用含 NULL、空组、并列值的真实样本验证结果是否符合业务预期。










