每组 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| name | department | amount |
|---|---|---|
| Alice | Sales | 1200 |
| Bob | IT | 900 |
| Cindy | Sales | 1500 |
| David | HR | 700 |
优化前 / 不推荐
SELECT name, department, amount FROM orders ORDER BY amount DESC LIMIT 1;优化前运行结果
| name | department | amount |
|---|---|---|
| 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;优化后运行结果
| name | department | amount |
|---|---|---|
| David | HR | 700 |
| Bob | IT | 900 |
| Cindy | Sales | 1500 |
为什么这样更好:全局 LIMIT 只返回全表最高的一行;分区排名为每个部门重新编号,因此 HR、IT、Sales 都有各自的第一名。
这篇内容是否清楚?
感谢反馈,我们会把它用于后续内容复核。