← 返回 SQL 教程

MySQL 8:ROW_NUMBER() OVER(PARTITION BY ... ORDER BY SUM(...)) 写法

搜索这个问题时,通常想做“每个分组内按汇总值排名”。正确思路是:先按业务维度聚合 SUM(),再用窗口函数 ROW_NUMBER() 排名。

直接答案

在 MySQL 8 中,可以用 ROW_NUMBER() OVER (PARTITION BY 分组列 ORDER BY 聚合结果 DESC) 做分组排名。为了避免别名不可见、执行顺序混乱和筛选窗口函数时报错,推荐使用 CTE:

WITH product_sales AS (
    SELECT
        category_id,
        product_id,
        SUM(amount) AS total_amount
    FROM order_items
    GROUP BY category_id, product_id
),
ranked_products AS (
    SELECT
        category_id,
        product_id,
        total_amount,
        ROW_NUMBER() OVER (
            PARTITION BY category_id
            ORDER BY total_amount DESC, product_id
        ) AS rn
    FROM product_sales
)
SELECT category_id, product_id, total_amount
FROM ranked_products
WHERE rn = 1;

这段 SQL 的含义是:先得到每个分类下每个商品的销售总额,再在每个分类内部按销售总额从高到低编号,最后取每组第一名。

可以写在同一层吗?

如果只是展示排名,不需要在同一层过滤 rn,MySQL 8 的分组查询里也可以把 SUM(amount) 放进窗口函数的 ORDER BY

SELECT
    category_id,
    product_id,
    SUM(amount) AS total_amount,
    ROW_NUMBER() OVER (
        PARTITION BY category_id
        ORDER BY SUM(amount) DESC, product_id
    ) AS rn
FROM order_items
GROUP BY category_id, product_id;

实战建议

如果后面要写 WHERE rn <= 3、要复用 total_amount,或者团队里有人不熟悉窗口函数执行顺序,用 CTE/子查询版本更稳。

Top N:每个分组取前三名

WITH customer_product_spend AS (
    SELECT
        customer_id,
        product_id,
        SUM(quantity * unit_price) AS spend
    FROM order_items
    GROUP BY customer_id, product_id
),
ranked AS (
    SELECT
        customer_id,
        product_id,
        spend,
        ROW_NUMBER() OVER (
            PARTITION BY customer_id
            ORDER BY spend DESC, product_id
        ) AS rn
    FROM customer_product_spend
)
SELECT customer_id, product_id, spend, rn
FROM ranked
WHERE rn <= 3
ORDER BY customer_id, rn;

这里的 product_id 是稳定排序字段:当两个商品消费额相同,MySQL 仍能给出可重复的排名顺序。

ROW_NUMBER、RANK、DENSE_RANK 怎么选?

函数 并列时 适合场景
ROW_NUMBER()强制给不同序号每组只要一条记录,例如最高销量商品
RANK()并列同名次,后续名次跳号竞赛排名,例如 1、1、3
DENSE_RANK()并列同名次,后续名次不跳号报表排名,例如 1、1、2

常见错误

错误 1:在 WHERE 里直接用窗口函数别名

-- 错误:WHERE 看不到 SELECT 里的 rn
SELECT
    category_id,
    product_id,
    ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY SUM(amount) DESC) AS rn
FROM order_items
GROUP BY category_id, product_id
WHERE rn = 1;

改法:把这层查询放进 CTE 或子查询,在外层筛选 rn

错误 2:把窗口函数当成 GROUP BY 的替代品

窗口函数不会把多行合并成一行;SUM(amount) OVER (...) 会给每行返回窗口结果。要先按商品、分类汇总成一行,仍然需要 GROUP BY

错误 3:没有处理并列排序

如果 ORDER BY total_amount DESC 出现并列,ROW_NUMBER() 的谁先谁后可能不符合业务预期。加上主键、日期等第二排序字段可以让结果稳定。

FAQ

MySQL 8 支持 ROW_NUMBER 吗?

支持。MySQL 8.0 提供窗口函数,包括 ROW_NUMBER()RANK()DENSE_RANK() 以及带 OVER 的聚合窗口函数。

搜索 query 中的 order by sum( 应该怎么落地?

SUM(...) 命名为 total_amount,再在外层窗口函数中 ORDER BY total_amount DESC。这样可读性和可维护性最好。

MySQL 5.7 怎么写?

MySQL 5.7 没有窗口函数。可以用自连接或相关子查询模拟排名,但 SQL 更复杂、性能也更难控制;生产项目建议升级到 MySQL 8。

相关学习