COUNT(*)统计所有行(含全NULL行),COUNT(列名)仅统计该列非NULL行;LEFT JOIN后对右表字段COUNT需用CASE WHEN或COALESCE预处理NULL;COALESCE位置影响语义:外层兜底空结果,内层填充缺失值参与计算。

COUNT(*) 和 COUNT(列名) 的行为差异必须分清
统计结果偏低,八成是因为误用了 COUNT(列名) 替代 COUNT(*)。前者只数该列非 NULL 的行,后者才真正统计所有行——哪怕整行全是 NULL 也算一个。比如查用户表总人数,却写 COUNT(email),那所有未填邮箱的用户就直接从总数里消失了。
常见错误现象:COUNT(*) 返回 1000,COUNT(phone) 却只有 620,差值 380 就是 phone IS NULL 的行数。
- 要统计“这张表一共有多少条记录”,无条件用
COUNT(*) - 要统计“有多少人填了手机号”,才用
COUNT(phone) - 想同时知道空值数量?直接算差值:
COUNT(*) - COUNT(phone)
LEFT JOIN 后对右表字段 COUNT 必须预处理 NULL
LEFT JOIN 未匹配到右表数据时,对应字段天然为 NULL。这时若直接 COUNT(right_table.status),等于把“没关联上”的记录全过滤掉了——不是数据丢了,是被聚合函数主动跳过了。
典型场景:订单主表 orders 左连物流表 shipments,想统计“每个订单是否已发货”。如果写 COUNT(shipments.tracking_no),那所有未发货订单(tracking_no IS NULL)就不计入任何分组,导致 COUNT 结果偏小甚至为 0。
- 正确做法:用
COUNT(COALESCE(shipments.tracking_no, 'unshipped'))或更稳妥的COUNT(*)配合条件判断 - 更推荐逻辑分离:先用
CASE WHEN shipments.tracking_no IS NOT NULL THEN 1 ELSE 0 END标记,再SUM()计数 - 别碰
COUNT(IF(...))这种写法——MySQL 支持,但 PostgreSQL/SQL Server 不认,跨库迁移会崩
COALESCE 放在聚合内还是外,业务含义天差地别
COALESCE(SUM(col), 0) 和 SUM(COALESCE(col, 0)) 看着像,但结果可能完全相反。关键在 NULL 是在聚合前被填充,还是聚合后被兜底。
错误示例:AVG(COALESCE(salary, 0)) 把 [NULL, 10000] 算成 5000;而真实情况可能是两人均未发薪,不该拉低平均值。
- 想表达“某组完全没数据,显示 0 而非空白” → 用外层
COALESCE(AVG(col), 0) - 想表达“每条记录缺失就当 0 算,再参与汇总” → 用内层
SUM(COALESCE(col, 0)) -
WHERE子句里慎用COALESCE(col, 'default')——可能让索引失效,尤其当col上有 B-tree 索引时
空字符串 '' 和 NULL 完全不是一回事
聚合函数只忽略 NULL,不忽略 ''。但业务代码常把 '' 当“空值”用,导致统计口径撕裂:比如 COUNT(status) 会把 '' 计入,而 WHERE status IS NULL 却查不到它。
最麻烦的从来不是 NULL 本身,而是同一列里有的 NULL 表示“未发生”,有的表示“不适用”,有的表示“录入失败”。这时候统一 COALESCE(col, 0) 反而掩盖了数据质量问题。
- 查真正缺失的数据,得分开写:
WHERE status IS NULL OR status = '' -
COUNT(DISTINCT col)中,多个NULL被视为相同值(只计一次),但''是独立值,会被单独去重 - 字符串列上做
SUM(LEN(col)),''算长度 0 参与计算,NULL仍被跳过

















