SQLPathLab
SQL 实战指南 · MySQL 8.0

每组 Top 1:全局 LIMIT 为什么不够

用窗口函数实现每组 Top 1/Top N,并明确并列、稳定排序与结果行数规则。

问题与判断方法

ORDER BY ... LIMIT 1 只从整个结果集取一行,不能回答“每个部门金额最高的订单”。每组 Top N 的关键是先划分组,再在每个组内独立排序和编号,最后过滤每组需要的名次。

MySQL 8 可以用 ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC, id) 编号。PARTITION BY 定义组,窗口内 ORDER BY 定义组内优先级,唯一主键 id 负责裁决金额相同的记录。

ROW_NUMBER 保证每个序号只有一行,适合“每组恰好一条”。如果并列第一都应保留,应使用 RANK 或 DENSE_RANK 并筛选排名 1。业务规则必须先决定并列者是裁决一条还是全部返回。

窗口函数结果不能直接在同一层 WHERE 中用别名过滤,因此通常放入 CTE 或子查询,再在外层写 WHERE rn <= N。N 大于 1 时仍要保留确定性排序,并验证每组返回行数。

大数据量下可评估与 PARTITION BY、ORDER BY 顺序匹配的复合索引,但窗口计算仍可能读取和排序大量候选行。应先用 WHERE 缩小范围,再用 EXPLAIN ANALYZE 检查真实计划。

示例

示例数据表

表名:orders
orders
namedepartmentamount
Alice Sales 1200
Bob IT 900
Cindy Sales 1500
David HR 700

优化前 / 不推荐

SELECT name, department, amount FROM orders ORDER BY amount DESC LIMIT 1;

优化前运行结果

result
namedepartmentamount
Cindy Sales 1500

优化后 / 推荐

WITH ranked AS (SELECT id, name, department, amount, ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC, id) AS rn FROM orders) SELECT name, department, amount FROM ranked WHERE rn = 1 ORDER BY department;

优化后运行结果

result
namedepartmentamount
David HR 700
Bob IT 900
Cindy Sales 1500

为什么这样更好:全局 LIMIT 只返回全表最高的一行;分区排名为每个部门重新编号,因此 HR、IT、Sales 都有各自的第一名。

这篇内容是否清楚?