count(*)统计所有行,count(列名)仅统计非null行;空字符串''与null不同,前者被count(列名)计入,后者被忽略。

COUNT(*) 统计所有行,但 COUNT(列名) 只算非 NULL 行
这是最常被误读的点:空字符串 '' 和 NULL 完全不是一回事。COUNT(status) 会把 '' 当作有效值计入,但跳过所有 NULL;而 COUNT(*) 对两者都无感,只数行数。比如某列值为 'done'、''、NULL、'pending',那么 COUNT(status) 返回 3,COUNT(*) 返回 4。
常见错误现象:
- 用
COUNT(列名)统计“总人数”,结果比预期少——其实是把NULL的记录漏掉了,不是数据丢了,是语义理解错了 - 在报表里写
WHERE status = ''想查“未填写”,却漏掉真正为NULL的记录,因为NULL不等于任何东西,包括'' - 业务代码里把
''当作“空”,数据库却不认,导致 GROUP BY + HAVING 时统计口径分裂
正确做法是先明确你要统计什么:
- “这一组有多少条记录?”→ 无条件用
COUNT(*) - “这一组里该字段有值的有多少个?”→ 用
COUNT(列名) - “这一组里该字段为 NULL 的有多少个?”→ 用
COUNT(*) - COUNT(列名)或SUM(CASE WHEN 列名 IS NULL THEN 1 ELSE 0 END)
FILTER 子句比 CASE WHEN 更干净地做条件聚合(PostgreSQL / SQLite)
如果你用的是 PostgreSQL 或 SQLite 3.30+,优先用 FILTER 而不是 CASE WHEN 来限定聚合输入范围。例如想算“月薪超 10000 的员工平均工资”,写成:
AVG(salary) FILTER (WHERE salary > 10000)
而不是:
AVG(CASE WHEN salary > 10000 THEN salary END)
区别在于:FILTER 是主动限定参与聚合的行集,CASE 是被动把不满足条件的行转成 NULL 再让 AVG 忽略——后者多了一层隐式转换,语义模糊,且容易误加 ELSE 0 导致结果失真。
注意限制:
-
FILTER仅支持 PostgreSQL 和较新 SQLite;MySQL、SQL Server、Oracle 均不支持 - 语法必须是
聚合函数() FILTER (WHERE 条件),括号不能省,WHERE不能换成AND - 条件中若涉及可能为
NULL的列(如salary),需显式写salary IS NOT NULL AND salary > 10000,否则NULL行会被整个滤掉
用 COALESCE 把 NULL 变成可计数/可计算的值
COALESCE 不是用来“美化输出”的,它是让 NULL 进入聚合逻辑的桥梁。比如你想统计每个部门里“薪资已知”和“薪资未知”的人数,不能靠 COUNT(列名) 直接分,得构造标记:
SELECT dept, COUNT(*) AS total, COUNT(COALESCE(salary, -1)) AS non_null_salary_count, COUNT(*) - COUNT(COALESCE(salary, -1)) AS null_salary_count FROM emp GROUP BY dept;
这里 COALESCE(salary, -1) 的作用是:把 NULL 替换成一个非 NULL 的占位值(-1、0、'unknown' 都可以),这样 COUNT 就会把它当有效值计数。关键点是:第一个参数必须类型兼容,且不能是 NULL;别写 COALESCE(col, col),毫无意义。
其他场景也适用:
- 求平均值时把
NULL当 0:用AVG(COALESCE(salary, 0)) - 字符串最大值比较时排除
NULL干扰:用MAX(COALESCE(name, ''))(注意''在字典序中通常排最前) - 但别对数值列直接
COALESCE(text_col, 'N/A')然后塞进SUM,类型错会报错
真正容易被绕过的点,是业务系统里长期把 '' 当 NULL 用,而数据库不认这个约定。一旦在 WHERE、GROUP BY、HAVING 里混用 = '' 和 IS NULL,统计结果就不可信了——这不是 SQL 有问题,是语义没对齐。











