GROUP BY中NULL值默认归为一组,但业务上常需排除或显式标记;应先用WHERE过滤无效值,再结合HAVING校验聚合结果,并用COUNT(CASE WHEN...)统计空值率。

检查分组字段是否为空或无效值
GROUP BY结果为空,十有八九不是数据真没了,而是分组字段本身大量为 NULL 或全是空格。SQL里 GROUP BY column 会把所有 NULL 归为同一组(部分引擎甚至直接跳过),但业务上你往往不希望它们参与统计。
实操建议:
- 先用
SELECT COUNT(*) FROM table WHERE column IS NULL OR TRIM(column) = ''看无效值占比 - 若比例高,后续校验必须加清洗:在
GROUP BY前用WHERE column IS NOT NULL AND TRIM(column) != '' - 注意:MySQL 5.7+ 默认开启
sql_mode=STRICT_TRANS_TABLES,但GROUP BY仍允许NULL分组;PostgreSQL 则更严格,需显式处理
用HAVING快速定位异常分组
HAVING 是校验阶段最趁手的工具——它在聚合后过滤,能直接揪出“不该存在”的分组模式,比如重复ID、超限金额、零订单用户等。
常见校验写法:
- 查重复值:
SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING COUNT(*) > 1 - 查空分组残留:
SELECT status FROM orders GROUP BY status HAVING COUNT(*) = 0(实际不会返回,但配合COUNT(*)检查可发现逻辑矛盾) - 查异常数值:
SELECT product_id, AVG(price) AS avg_p FROM sales GROUP BY product_id HAVING AVG(price) 100000
关键点:别把条件写在 WHERE 里——那是在聚合前筛行,HAVING 才是筛“聚合结果”。
组合COUNT和CASE做多维度通过率统计
单纯看“有没有重复”太粗糙。真实质检需要量化:这个字段空值率多少?那个状态分布是否符合预期?用 COUNT 配合 CASE 能一行输出多个指标。
例如校验用户表必填字段完整率:
SELECT COUNT(*) AS total, COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) AS valid_email_cnt, COUNT(CASE WHEN phone IS NOT NULL AND TRIM(phone) != '' THEN 1 END) AS valid_phone_cnt, ROUND(COUNT(CASE WHEN email IS NOT NULL AND TRIM(email) != '' THEN 1 END) * 100.0 / COUNT(*), 2) AS email_ratio FROM users;
这种写法比跑 3 条独立 SELECT COUNT 更快,也避免了多次全表扫描。注意除零问题:COUNT(*) 为 0 时,分母要加 NULLIF 或前置判断。
警惕GROUP BY + 聚合函数对NULL的静默吞并
SUM、AVG、COUNT(column) 全部自动忽略 NULL,这是设计使然,但容易掩盖数据缺失问题。比如 AVG(score) 返回 85,不代表所有人有分数——可能只有 30% 的记录有值,其余全 NULL,而 AVG 对它们视而不见。
校验时务必同步看基数:
- 用
COUNT(*)得总行数 - 用
COUNT(score)得非空行数 - 两者差值就是丢失样本量,比平均值本身更有质量意义
- 特别当字段定义为
NOT NULL但实际含NULL时,说明写入逻辑或 ETL 流程已出问题
真正难的不是写出 GROUP BY,而是每次执行前,心里清楚哪一列可能藏 NULL、哪个聚合结果其实已被静默截断——这些地方,数据库从不报错,但业务已经失真。

















