用 row_number() 实现每组价格降序编号需先按 category 分组、组内按 price desc 排序,再用 row_number() 标记序号;因窗口函数不能在 where 中直接使用,须嵌套子查询后过滤 rn = 1;重复价格时需补充排序字段确保结果稳定。

用 ROW_NUMBER() 给每组价格降序编号
核心思路是:先按 category 分组,再在组内按 price 从高到低排序,用 ROW_NUMBER() 标记序号。这样每个分类里最贵的产品就一定是 rn = 1 的那条。
注意 ROW_NUMBER() 必须搭配 OVER 子句,且 ORDER BY price DESC 要写在 PARTITION BY category 内部,顺序不能反:
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products;
常见错误是漏掉 PARTITION BY,结果变成全表排序;或者把 ORDER BY 写在外面,语法直接报错。
筛选每组第一条:WHERE rn = 1
窗口函数不能直接在 WHERE 中使用,所以必须把带 ROW_NUMBER() 的查询包一层子查询或 CTE,再外层过滤 rn = 1:
SELECT id, name, category, price
FROM (
SELECT id, name, category, price,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn
FROM products
) t
WHERE rn = 1;
如果产品价格相同,ROW_NUMBER() 会强制给唯一序号(比如并列第一的两个,一个得 1、一个得 2),所以它只保证“每组一条”,不保证“所有最高价都返回”。需要全部返回时得换 RANK() 或 DENSE_RANK()。
遇到 NULL 或重复价格该怎么处理
price 为 NULL 的行默认排在最后(因 DESC),若想让 NULL 被排除,加条件 WHERE price IS NOT NULL 在子查询里;若想让它优先被筛掉,可用 ORDER BY price DESC NULLS LAST(PostgreSQL/Oracle 支持,MySQL 8.0+ 不支持该语法)。
重复价格场景下:ROW_NUMBER() 随机决定谁是第 1 名(无稳定排序时),可能每次执行结果不同。要稳定结果,必须补排序字段,比如:
-
ORDER BY price DESC, id ASC—— 价格相同时取 ID 最小的 -
ORDER BY price DESC, created_at DESC—— 取最新上架的
替代方案:用 correlated subquery 更直观但更慢
不用窗口函数也能做,比如:
SELECT p1.* FROM products p1 WHERE p1.price = ( SELECT MAX(p2.price) FROM products p2 WHERE p2.category = p1.category );
这个写法逻辑直白,但性能差——每行都触发一次子查询,数据量大时明显变慢;还可能返回多条(价格并列最高),而 ROW_NUMBER() 版本天然控制为一条。MySQL 5.7 或旧版不支持窗口函数时才考虑它。
真正容易被忽略的是:当分类数量多、数据分布不均时,PARTITION BY 的分组键最好有索引,否则 ROW_NUMBER() 排序开销会陡增。别只盯着 SQL 写法,忘了看 EXPLAIN 里的 Using filesort 提示。










