GROUP BY 后不能直接用 ROW_NUMBER() 或 RANK(),因聚合后行已消失;须先子查询或 CTE 聚合,再在外层用窗口函数排序打标,如 CTE + ROW_NUMBER() 实现每类 Top-N。

GROUP BY 本身不支持排名,必须配合窗口函数
直接在 GROUP BY 后用 ROW_NUMBER() 或 RANK() 会报错,因为聚合后行已“消失”,而排名需要逐行计算。真正可行的路径是:先按分类聚合销量,再用窗口函数对聚合结果排序打标。
典型错误写法:SELECT category, SUM(sales), ROW_NUMBER() OVER (ORDER BY SUM(sales) DESC) FROM orders GROUP BY category —— 多数数据库(如 MySQL 5.7、PostgreSQL 12 以前)会拒绝执行,提示“column "sales" must appear in the GROUP BY clause or be used in an aggregate function”之类错误。
- 正确顺序:子查询或 CTE 先做
GROUP BY category汇总,外层用窗口函数处理该结果集 - MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+、Oracle 8i+ 支持窗口函数;SQLite 3.25+ 也支持
- 如果目标环境不支持窗口函数(如旧版 MySQL),只能用自连接或变量模拟,但逻辑复杂且不可靠
用 CTE + ROW_NUMBER() 实现每类销量 Top-N
这是最清晰、可读性高、兼容主流现代数据库的写法。例如查每个分类下销量最高的前 3 个商品:
WITH category_sales AS (
SELECT
category,
product_name,
SUM(quantity * price) AS total_sales
FROM orders o
JOIN products p ON o.product_id = p.id
GROUP BY category, product_name
)
SELECT
category,
product_name,
total_sales,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rank_in_cat
FROM category_sales
WHERE rank_in_cat <= 3;注意:PARTITION BY category 是关键,它让排名在每个分类内独立重置;ORDER BY total_sales DESC 决定谁排第一。
- 若要并列不跳名次,改用
RANK();若并列后跳过后续名次(如 1,1,3),用DENSE_RANK() WHERE rank_in_cat 必须写在外层,不能放在 CTE 内——窗口函数结果在 CTE 中不可过滤- 聚合字段(如
SUM(quantity * price))必须出现在 CTE 的SELECT中,否则外层无法引用
避免 GROUP BY 字段遗漏导致的逻辑错误
常见翻车点:想按分类排名商品,却漏了 product_name 在 GROUP BY 中,导致多个商品被强行合并。
错误示例:GROUP BY category(只分组分类,没分商品)→ 结果只剩一行/类,product_name 值非确定(MySQL 可能随机取,PostgreSQL 直接报错)。
- 只要最终要展示商品粒度的数据(如商品名、SKU),
product_name或唯一标识字段就必须进GROUP BY - 如果表里有
product_id,优先用它分组,比依赖名称更安全(防重名) - 聚合时别漏掉单位换算:比如订单表存的是数量和单价,需
SUM(quantity * price),而非只SUM(quantity)
性能敏感时注意索引与数据量
当订单表超百万行,GROUP BY category, product_id + 窗口函数可能变慢,尤其 PARTITION BY 分区多、每区数据不均时。
- 确保
(category, product_id)或(product_id, category)有联合索引,加速分组扫描 - 如果只查 Top-1(如每类卖得最好的商品),可用
DISTINCT ON(PostgreSQL)或相关子查询替代窗口函数,有时更快 - 在 MySQL 中,
GROUP BY默认强制排序,加ORDER BY NULL可禁用(配合显式窗口排序更高效)
真正卡住的往往不是语法,而是没意识到:排名对象是「聚合后的商品」,不是原始订单行;一旦混淆粒度,GROUP BY 就会默默吞掉你想要的数据。

















