按类别商品排序必须用ROW_NUMBER()、RANK()或DENSE_RANK()配合PARTITION BY category,因排名是行级计算且需按类重置计数器;PARTITION BY后仅支持列名,ORDER BY在OVER中必填,NULL值会被归为同一组。

SQL里用PARTITION BY做分组排名,核心是配合窗口函数
直接说结论:想按类别给商品排序,必须用ROW_NUMBER()、RANK()或DENSE_RANK() + PARTITION BY category,不能只靠GROUP BY或ORDER BY。因为排名是行级计算,需要保留原始行数,同时按类别“重置计数器”。
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC)怎么写才不出错
这是最常用的写法,但容易漏掉关键点:
-
PARTITION BY后面只能跟列名,不能是表达式(比如PARTITION BY UPPER(category)在部分数据库如MySQL 8.0+支持,但PostgreSQL会报错) -
ORDER BY在OVER子句里是必需的——没有它,ROW_NUMBER()语法不合法 - 如果价格相同,
ROW_NUMBER()仍会给不同序号(比如并列第1和第2),要并列排名得换RANK() - 示例:
SELECT name, category, price,<br> ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn<br>FROM products;
为什么RANK()和DENSE_RANK()结果不一样
三者对重复值的处理逻辑差异直接影响业务含义:
-
ROW_NUMBER():严格递增,不跳号也不并列(1,2,3,4) -
RANK():并列且跳号(两个最高价→都是1,下一个是3) -
DENSE_RANK():并列但不跳号(两个最高价→都是1,下一个是2) - 选哪个取决于需求:电商“销量Top 3”通常用
DENSE_RANK(),因为用户不接受“没有第2名”;而“取每类前3个ID”适合ROW_NUMBER(),避免重复ID挤占名额
MySQL 5.7不支持PARTITION BY?替代方案只有子查询
MySQL 5.7及更早版本确实不支持窗口函数,硬上PARTITION BY会报错ERROR 1064。此时必须用变量或关联子查询:
- 变量方式(依赖执行顺序,有风险):
SELECT name, category, price,<br> @rn := IF(@prev = category, @rn + 1, 1) AS rn,<br> @prev := category<br>FROM products p, (SELECT @rn := 0, @prev := '') AS _<br>ORDER BY category, price DESC;
- 子查询方式(通用但慢):
(SELECT COUNT(*) FROM products p2 WHERE p2.category = p1.category AND p2.price > p1.price) + 1 - 强烈建议升级到MySQL 8.0+,否则维护成本远高于性能收益
真正容易被忽略的是:PARTITION BY字段如果有NULL值,会被当作同一组处理——比如category IS NULL的所有商品会挤在同一个“未分类”组里排名,上线前务必检查空值分布。

















