group by后不能直接select非分组字段,因为每组对应多行,非分组字段(如name)在组内可能有多个值,数据库无法确定返回哪一个,违反sql标准的函数依赖与结果确定性要求。

GROUP BY 后不能直接 SELECT 非分组字段
这是最常踩的坑:写 SELECT name, COUNT(*) FROM users GROUP BY city,MySQL 5.7+ 或 PostgreSQL 会直接报错 ERROR 1055,因为 name 不在 GROUP BY 中,也不在聚合函数里。SQL 标准要求:SELECT 列要么是 GROUP BY 的字段,要么被聚合函数包裹。
实际清洗中,如果你要保留每个城市的“代表性姓名”,得明确策略:
- 用
MAX(name)或MIN(name)取字典序首尾(注意不是“最早注册”的那个) - 用
ANY_VALUE(name)(MySQL)绕过严格模式——但仅限你确认该字段在组内值一致,或不关心具体取哪个 - 如果真需要“每个城市最新注册的用户姓名”,得先用窗口函数排序,再过滤,
GROUP BY做不了这事
WHERE 和 HAVING 混用导致逻辑错位
清洗时想筛出“订单数 ≥ 5 且平均金额 > 100 的客户”,容易写成 WHERE AVG(amount) > 100 —— 这会报错,因为 WHERE 执行在分组前,无法访问聚合结果。
正确顺序是:
-
WHERE先过滤原始行(比如WHERE status = 'paid') -
GROUP BY分组 -
HAVING再过滤分组结果(HAVING COUNT(*) >= 5 AND AVG(amount) > 100)
漏掉 WHERE 预过滤,会让无效数据(如测试订单、退款单)参与聚合,最终 HAVING 结果失真。
NULL 值让 COUNT(*) 和 COUNT(列名) 行为不同
清洗重复或缺失数据时,COUNT(*) 统计所有行,COUNT(email) 只统计 email IS NOT NULL 的行。如果表里有 100 条记录,其中 12 条 email 为 NULL,那么:
-
COUNT(*)返回 100 -
COUNT(email)返回 88 -
COUNT(DISTINCT email)还会把重复的NULL当作一个值(标准 SQL 中,NULL不参与去重)
所以判断“邮箱缺失率”要写 1 - COUNT(email)/COUNT(*),而不是依赖 COUNT(DISTINCT email)。
ORDER BY 在 GROUP BY 后只能用分组字段或聚合结果
执行 SELECT city, COUNT(*) c FROM users GROUP BY city ORDER BY c DESC 没问题;但 ORDER BY name 就不行——除非 name 在 GROUP BY 里,或者你加了 MAX(name) 并按它排序。
实际清洗中,常想看“用户数最多的前 5 个城市”,必须写成:
SELECT city, COUNT(*) AS cnt FROM users GROUP BY city HAVING COUNT(*) > 0 ORDER BY cnt DESC LIMIT 5
少写 HAVING 不影响结果,但加上能显式排除空分组(比如 LEFT JOIN 后的 NULL city),避免脏数据干扰排序位置。
分组清洗真正的难点不在语法,而在于搞清“每一行输出代表什么语义”——是统计摘要,还是抽样代表,还是为后续 JOIN 准备键值。一旦混淆,清洗结果看着整齐,实际已偏离业务意图。











