COUNT(*)统计所有行(包括含NULL的行),COUNT(字段名)只统计该字段非NULL的行;前者用于统计物理行数,后者用于统计有效业务值数量。
直接说结论:COUNT(*) 统计所有行(包括含 NULL 的行),而 COUNT(字段名) 只统计该字段非 NULL 的行。如果你要“统计非空数据的实际行数”,必须用 COUNT(字段名),且字段名不能是 *。
常见误解是以为 COUNT(*) 会跳过 NULL —— 它不会。哪怕整行全是 NULL,COUNT(*) 也会计 1。
COUNT(*):按行计数,无视任何列值COUNT(email):只算 email IS NOT NULL 的行COUNT(COALESCE(email, phone)) 不合法 —— COUNT 不接受表达式(除非是标准 SQL:2003+ 的某些实现,但 MySQL/PostgreSQL/SQL Server 均不支持)没有原生的 COUNT 语法能直接统计“至少一个字段非空”或“所有字段都非空”的行数。得靠条件转换。
比如想统计 email 和 phone 中至少有一个非空的行数:
SELECT COUNT(*) FROM users WHERE email IS NOT NULL OR phone IS NOT NULL;
如果要统计两者都非空的行:
SELECT COUNT(*) FROM users WHERE email IS NOT NULL AND phone IS NOT NULL;
COUNT(email AND phone) —— 这是语法错误,AND 是逻辑运算符,不能放进 COUNT()
COUNT(email, phone) —— 多参数 COUNT 在标准 SQL 和主流数据库中不存在WHERE;需要在同一查询里分维度统计,就用 SUM(CASE WHEN ... THEN 1 ELSE 0 END)
在 GROUP BY 查询里,COUNT(字段名) 对每组分别跳过 NULL,这本身没问题。但容易出错的是:你可能以为某组没结果是因为数据为空,其实是该组里对应字段全为 NULL,导致 COUNT(字段) 返回 0,而非 NULL。
例如:
SELECT dept, COUNT(manager_name) FROM staff GROUP BY dept;
若某部门所有 manager_name 都是 NULL,这行仍会返回 (dept_x, 0),不是被过滤掉,也不是 NULL。
COUNT 永远返回 INT,不会是 NULL
COUNT(*) 对比:COUNT(*) > 0 AND COUNT(field) = 0
0,不是 NULL,这点常被忽略COUNT(*) 在多数引擎(如 InnoDB)上可走索引元数据或最小索引树扫描,速度较快;而 COUNT(字段名) 必须实际读取该字段值判断是否为 NULL,尤其当字段没索引、或类型是 TEXT/JSON 时,I/O 开销明显上升。
NULL 且没索引,COUNT(字段) 可能比 COUNT(*) 慢几倍COUNT(字段) 的方式是给该字段建普通索引(不含 NULL 的索引条目会被跳过,但引擎仍需检查是否为 NULL)COUNT(字段) 在覆盖索引下有优化,但 PostgreSQL 和 SQL Server 仍基本依赖全字段扫描真正要注意的不是语法怎么写,而是你到底想统计“物理行数”还是“有效业务值数量”——前者用 COUNT(*),后者必须明确指定字段,并接受它带来的 I/O 和语义复杂度。