必须用 over(partition by category order by click_count desc) 配合 row_number() 才能每类取点击量最高记录;漏 partition by 会导致全局排序,版本低于 mysql 8.0 则不支持。

用 ROW_NUMBER() 按分类分组取点击量最高记录
直接上结论:必须配合 OVER(PARTITION BY category ORDER BY click_count DESC) 使用,否则 ROW_NUMBER() 只是全局编号,起不到“每类取一”的作用。
常见错误是只写 ORDER BY click_count DESC 却漏掉 PARTITION BY,结果返回的是全表点击量 Top N,不是每个分类各自的 Top 1。
-
PARTITION BY category是分组关键,确保编号在每个分类内独立重置 - 排序字段推荐用
click_count DESC,若存在并列(相同点击量),ROW_NUMBER()会强制分配不同序号,不保证稳定(可加id或title作为次级排序避免歧义) - 别名字段如
rn必须在子查询或 CTE 中定义,不能直接在WHERE里引用ROW_NUMBER()
完整写法:子查询 + WHERE rn = 1
因为窗口函数不能出现在 WHERE 子句中,必须先算出序号,再过滤。最稳妥写法是套一层子查询:
SELECT category, title, click_count
FROM (
SELECT
category,
title,
click_count,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY click_count DESC, id ASC) AS rn
FROM articles
) ranked
WHERE rn = 1;
注意点:
- 这里加了
id ASC作为第二排序条件,防止同点击量时结果不可复现 - 如果表很大,
PARTITION BY字段(如category)建议有索引,否则排序开销明显 -
ROW_NUMBER()返回的是整数,WHERE rn = 1安全;别用rn ,语义冗余且可能误导
ROW_NUMBER() vs RANK() vs DENSE_RANK() 怎么选
三者都生成序号,但处理并列的方式不同——这直接影响“最高点击量标题”是否唯一:
-
ROW_NUMBER():严格递增,相同click_count也会分出 1、2、3… 所以rn = 1永远只返回一条(哪怕多个标题点击量并列最高) -
RANK():并列则跳号,比如两个第一,下一个就是第三。若你希望所有并列最高者都入选,就该用RANK() = 1 -
DENSE_RANK():并列不跳号,两个第一后是第二。一般不用在这种“取最高”场景
所以,明确只要一条时用 ROW_NUMBER();允许并列多条时,换 RANK() 并改 WHERE rank_num = 1。
MySQL 8.0+ / PostgreSQL / SQL Server 都支持,但旧版 MySQL 不行
如果你用的是 MySQL 5.7 或更早版本,ROW_NUMBER() 直接报错 FUNCTION xxx.ROW_NUMBER does not exist。此时只能用自连接或变量模拟,但逻辑复杂、性能差、易出错。
迁移建议:
- 确认数据库版本:
SELECT VERSION(); - 旧版 MySQL 强烈建议升级到 8.0+,窗口函数是质变级改进
- 临时 workaround(不推荐长期用):用
(SELECT COUNT(*) FROM articles a2 WHERE a2.category = a1.category AND a2.click_count > a1.click_count) = 0判断是否为最大值,但无索引时是 O(n²) 查询
真正麻烦的不是语法,而是没意识到 PARTITION BY 缺失或版本不兼容——这两个点卡住的人最多。










