用case when配合count实现条件聚合,再通过窗口函数row_number()按用户等级分组取购买量最多的品类;null与空字符串在group by中分属不同组,需用coalesce(nullif())统一处理。

GROUP BY 后怎么统计每个等级用户的偏好品类?
直接在 GROUP BY user_level 后用 COUNT 只能算总单数,没法看出“买得最多的是哪类”。真要看出偏好,得靠条件聚合——也就是对同一行数据,按不同品类做多次判断计数。
常见错误是写成多个子查询或自连接,又慢又难维护。正确做法是用 CASE WHEN 配合聚合函数,在一次扫描里完成多维度统计:
SELECT user_level, COUNT(*) AS total_orders, COUNT(CASE WHEN category = 'electronics' THEN 1 END) AS electronics_cnt, COUNT(CASE WHEN category = 'clothing' THEN 1 END) AS clothing_cnt, COUNT(CASE WHEN category = 'books' THEN 1 END) AS books_cnt FROM orders GROUP BY user_level;
注意:COUNT(CASE ...) 比 SUM(CASE ...) 更安全,因为 COUNT 自动忽略 NULL,不用额外写 ELSE 0;而 SUM 遇到全为 NULL 会返回 NULL,还得套 COALESCE。
想看“每个等级里买得最多的品类”,不能只靠 GROUP BY
GROUP BY 本身不支持“组内 Top 1”,硬要在聚合后取最大值,容易掉进 MAX(category) 这种逻辑陷阱——它返回的是字典序最大品类名,不是购买次数最多的那个。
真正可行的路是先聚合计数,再用窗口函数排序取首行:
WITH level_category_cnt AS (
SELECT
user_level,
category,
COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY user_level ORDER BY COUNT(*) DESC) AS rn
FROM orders
GROUP BY user_level, category
)
SELECT user_level, category AS top_category, cnt
FROM level_category_cnt
WHERE rn = 1;
这里关键点:必须先 GROUP BY user_level, category 再开窗,否则 ROW_NUMBER() 作用对象不对;ORDER BY COUNT(*) DESC 要写在窗口定义里,不能写在外层 ORDER BY。
用户等级字段有 NULL 或空字符串时,GROUP BY 会怎么处理?
SQL 标准里,NULL 在 GROUP BY 中自成一组,但很多业务系统把“未分级”存成空字符串 '',这就和 NULL 分属两组——结果里可能同时出现 (NULL)、('')、('vip') 三条记录,看着像数据异常。
排查和修复建议:
- 先查分布:
SELECT user_level, COUNT(*) FROM orders GROUP BY user_level; - 统一清洗:在
GROUP BY前用COALESCE(NULLIF(user_level, ''), 'unknown')把空字符串转成统一标识 - 别依赖应用层补默认值,数据库聚合时的空值行为必须显式控制
大数据量下 COUNT(CASE WHEN ...) 比 JOIN 多次快吗?
快,而且通常显著快。原因很实在:单表扫描一次,CPU 做分支判断的成本远低于磁盘读多次 + Join 哈希建表 + 内存合并。
但有两个现实约束:
- 当品类数超过 20–30 个,
CASE WHEN列表太长,可读性和维护性断崖下降,这时该考虑用PIVOT(SQL Server / Oracle)或crosstab(PostgreSQL 扩展),而不是硬写 - MySQL 8.0 以前不支持窗口函数,上面的 Top 1 写法会报错,得改用相关子查询或变量技巧,性能差一截
真实环境里,宁可多花十分钟重构为带物化 CTE 的语句,也别为了“看起来简洁”写一堆 LEFT JOIN ——执行计划一眼就能看出后者多了几个临时表和文件排序。










