count(*)统计所有行(含全null行),count(列名)仅统计该列非null行;二者语义不同,不可互换。

统计包含 NULL 的总行数用 COUNT(*)
COUNT(*) 统计的是结果集中的物理行数,不管任何列是否为 NULL,哪怕整行全是 NULL,它也会计 1。这是统计“到底查出来多少行”的唯一可靠方式。
常见错误是写成 COUNT(id) 或 COUNT(email) 却想得到总行数——只要该字段存在 NULL,就会漏掉对应行。
- 正确:
SELECT COUNT(*) FROM users; - 错误(除非你明确要排除 NULL):
SELECT COUNT(email) FROM users;—— 这会少算email IS NULL的行 -
COUNT(1)和COUNT(*)在 PostgreSQL、MySQL、SQL Server 中行为一致,性能无差异,可互换,但语义上COUNT(*)更清晰
统计某列非 NULL 值的行数用 COUNT(列名)
当你关心“这个字段实际填了几个”,比如邮箱填了多少个、手机号录了多少条,就得用 COUNT(列名)。它自动跳过该列为 NULL 的行,不依赖约束定义。
注意:即使该列有 NOT NULL 约束,COUNT(列名) 和 COUNT(*) 结果也一样,但这只是巧合;语义不同,不能混用。
- 统计有邮箱的用户数:
SELECT COUNT(email) FROM users; - 统计邮箱和手机号至少一个不为空的行数?不能写
COUNT(email OR phone)—— 语法错误,得用WHERE:SELECT COUNT(*) FROM users WHERE email IS NOT NULL OR phone IS NOT NULL; - 别用
COUNT(COALESCE(email, phone))—— 大多数数据库(MySQL/PostgreSQL/SQL Server)不支持在COUNT()里放表达式
同一查询里既要总行数又要非 NULL 行数,用 SUM + CASE
聚合函数不能嵌套条件,但你可以把布尔判断转成 0/1 再求和。这是替代多遍扫描或子查询的常用手法。
- 查每个部门的总人数和有邮箱的人数:
SELECT dept, COUNT(*) AS total, SUM(CASE WHEN email IS NOT NULL THEN 1 ELSE 0 END) AS has_email FROM employees GROUP BY dept; - 别写
COUNT(CASE ...)—— 虽然某些数据库允许,但语义模糊;SUM(CASE ...)更直观、兼容性更好 - 如果字段可能为空字符串而非 NULL,记得加额外判断:
CASE WHEN email IS NOT NULL AND email != '' THEN 1 ELSE 0 END
COUNT(DISTINCT 列名) 也会忽略 NULL
COUNT(DISTINCT 列名) 统计的是该列去重后的非 NULL 值个数。NULL 不参与去重,也不计入结果。
例如 SELECT COUNT(DISTINCT manager_id) FROM employees; 不会把 manager_id IS NULL 当作一个独立值来计。
- 想统计“有多少种不同的上级”,NULL 就是“无上级”,不应算一种 —— 这正是
COUNT(DISTINCT 列名)的合理行为 - 如果真要把 NULL 当作一类来统计,得手动补:
COUNT(DISTINCT COALESCE(manager_id, -1))(假设 -1 不是合法 ID),但需谨慎,这改变了业务语义
COUNT(*) 和 COUNT(列名) 当成可互换的写法,其实它们回答的是两个完全不同的问题。











