用row_number()配合partition by category和order by sales_amount asc可精准获取每类“低销冠军”;min()+group by会丢失非聚合字段,且group by后limit 1是全局取1而非每组各1。

直接用 ROW_NUMBER() 按销量升序排列,再配合 PARTITION BY category,就能精准定位每个类别的“低销冠军”(即销量最低但排名最靠前的那个)。
为什么不能用 MIN() + GROUP BY 直接查低销冠军
因为 MIN(sales_amount) 只返回数值,丢掉了对应的产品名、ID 等关键字段;而 GROUP BY 后再 ORDER BY sales_amount ASC LIMIT 1 是全局取第 1 条,不是每组各取 1 条。
常见错误现象:SELECT category, product_id, MIN(sales_amount) FROM products GROUP BY category ORDER BY sales_amount ASC LIMIT 1 —— 这条语句语法可能报错(product_id 不在 GROUP BY 或聚合函数中),即使能执行,product_id 值也是不确定的(取决于 MySQL 的 SQL mode 或 PostgreSQL 的 strict 模式)。
正确思路是:先保留所有原始行,再按类别分组、按销量**升序**编号,取每组 rn = 1 的那条。
ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales_amount ASC) 怎么写才稳
核心就是把排序方向设为 ASC,而不是惯用的 DESC。很多同学抄模板时漏改这个,结果拿到的是“高销冠军”。
- 必须用子查询或 CTE 包一层,外层才能加
WHERE rn = 1 -
PARTITION BY字段要和业务分组维度严格一致(比如category有 NULL 值?需提前WHERE category IS NOT NULL过滤) - 如果存在
sales_amount为 NULL 的记录,它们会排在最前面(ASC下 NULL 最小),建议加WHERE sales_amount IS NOT NULL显式排除 - 示例(MySQL 8.0+/PostgreSQL):
SELECT category, product_id, sales_amount
FROM (
SELECT category, product_id, sales_amount,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY sales_amount ASC, product_id ASC
) AS rn
FROM products
WHERE sales_amount IS NOT NULL
) t
WHERE rn = 1;
注意第二级排序 product_id ASC:防止同销量时 ROW_NUMBER() 分配顺序不可控(不同数据库物理顺序可能不同)。
遇到并列低销量,该选 ROW_NUMBER() 还是 RANK()
要看业务定义:“低销冠军”是否允许并列。
- 只想要**唯一一个**代表(比如发奖只颁给一个),坚持用
ROW_NUMBER() - 如果两个产品销量同为 1 元,你希望它们都算“低销冠军”,就该换
RANK()或DENSE_RANK(),然后WHERE rnk = 1 -
RANK()在并列时会跳号(如 1,1,3),DENSE_RANK()是 1,1,2 —— 对“取所有最低值”场景没区别,但影响后续筛选逻辑 - 性能上三者几乎无差别,但
ROW_NUMBER()语义最清晰:你要的是“第一个”,不是“所有并列第一”
索引和性能容易被忽略的点
这类查询跑得慢,90% 不是因为窗口函数本身,而是缺索引。
- 最佳复合索引是:
(category, sales_amount)—— 覆盖分区与排序字段 - 如果还经常查
product_id或其他字段,考虑把它们加到索引后缀(成为覆盖索引),避免回表 - 千万级表慎用
ORDER BY ... ASC配合大偏移量(虽然这里没LIMIT,但窗口函数内部仍需全排序);若只关心“最低”,且数据分布倾斜,可先用GROUP BY category找出各组最小值,再JOIN原表过滤,有时更快 - 测试时别只看执行计划里的“窗口函数”节点,重点看
Sort和Partition是否走了索引,以及是否触发了磁盘临时表(Using temporary; Using filesort)
真正卡住的往往不是函数怎么写,而是没意识到 ORDER BY sales_amount ASC 在大数据量下依然要完整排序——哪怕你只要每组第一条。











