最稳妥解法是用窗口函数ROW_NUMBER()按分类分区、销量降序编号取rn=1;并列时改用RANK()或DENSE_RANK();MySQL 8.0+支持,旧版需子查询;注意NULL和重复商品名问题。

用窗口函数 ROW_NUMBER() 排序取 Top 1
直接在分组内排序并取第一行,是这类问题最稳妥的解法。关键不是 GROUP BY,而是按分类分区、按销量降序编号:
SELECT category, product_name, sales
FROM (
SELECT category, product_name, sales,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales DESC) AS rn
FROM products
) t
WHERE rn = 1;注意:如果同一分类有多个商品销量并列最高,ROW_NUMBER() 只会任选其一(因为严格编号)。需要“全都要”就得换 RANK() 或 DENSE_RANK()。
遇到并列销量时用 RANK() 替代 ROW_NUMBER()
当分类下存在多个商品销量相同且为最高时,ROW_NUMBER() 会人为拆出先后,而业务上往往要保留全部:
-
RANK()对相同销量赋予相同排名,但会跳过后续名次(比如两个第1名后直接是第3名) -
DENSE_RANK()同样给相同值同名次,但不跳号(两个第1名后是第2名) - 只要把上面 SQL 中的
ROW_NUMBER()换成RANK(),再把WHERE rn = 1改成WHERE rn = 1即可——逻辑不变,结果更符合业务预期
MySQL 8.0+ 才支持窗口函数,旧版本得绕路
MySQL 5.7 或更早版本不识别 ROW_NUMBER(),硬写会报错:ERROR 1064: You have an error in your SQL syntax。此时只能用自连接或相关子查询:
SELECT p1.category, p1.product_name, p1.sales FROM products p1 WHERE p1.sales = ( SELECT MAX(p2.sales) FROM products p2 WHERE p2.category = p1.category );这个写法能兼容所有版本,但性能较差——尤其是数据量大时,子查询对每行都执行一次。另外注意 NULL 销量会导致结果异常,建议提前用
WHERE sales IS NOT NULL 过滤。
别忽略 NULL 和重复商品名带来的歧义
销量字段含 NULL 时,MAX(sales) 返回 NULL,导致整条记录被排除;而窗口函数中 ORDER BY sales DESC 默认把 NULL 排在最前(不同数据库行为不一致,PostgreSQL 和 SQL Server 默认如此,MySQL 8.0 则排最后)。更隐蔽的问题是:同一分类下若商品名重复(比如不同 SKU 都叫“iPhone 15”),仅靠 product_name 无法区分,查出来可能漏数据或聚合错误。真实场景中,应优先用唯一标识字段如 product_id,再关联查名称。

















