count()统计所有行,count(字段)仅统计该字段非null值;二者结果可能不同,如100行中12个email为null,则count()=100、count(email)=88。

COUNT(*) 和 COUNT(字段) 的行为差异必须分清
直接用 COUNT(*) 统计行数,和用 COUNT(字段名) 统计非 NULL 值数量,结果可能完全不同。比如某张用户表里有 100 行记录,但 email 字段有 12 条是 NULL,那么 COUNT(*) 返回 100,而 COUNT(email) 只返回 88。
常见错误是想查“有多少用户填了邮箱”,却写了 COUNT(*),结果把空邮箱也全算进去了。
-
COUNT(*):统计所有行,不管字段值是否为 NULL,性能通常最优 -
COUNT(字段名):只统计该字段值不为 NULL 的行数 -
COUNT(1)和COUNT(*)在绝大多数数据库(MySQL、PostgreSQL、SQL Server)中等价,语义上推荐用COUNT(*)
带 WHERE 条件时 COUNT 必须写在 SELECT 主体里
不能把 COUNT() 和普通列混着选却不加 GROUP BY,否则会报错或返回意外结果。例如想查“北京用户的总数”,写成 SELECT name, COUNT(*) FROM users WHERE city = '北京' 是错的——name 没聚合,数据库不知道该取哪一行的 name。
正确做法只有两种:
- 纯统计:用
SELECT COUNT(*) FROM users WHERE city = '北京' - 分组统计:如查每个城市的用户数,写成
SELECT city, COUNT(*) FROM users GROUP BY city
注意:WHERE 在 GROUP BY 之前执行,所以 WHERE city = '北京' GROUP BY city 最终只有一组;HAVING 则是在分组后过滤,不能替代 WHERE。
NULL 值和空字符串对 COUNT 的影响常被误判
COUNT() 只忽略真正的 NULL,不忽略空字符串 '' 或数值 0。比如一个 status 字段类型是 VARCHAR,其中存了 ''、'active'、NULL 三种值,那么 COUNT(status) 会把 '' 和 'active' 都算进去,只跳过 NULL。
如果业务上认为空字符串也代表“未设置”,需要显式排除:
COUNT(CASE WHEN status != '' AND status IS NOT NULL THEN 1 END)- 或者更直白:
SUM(CASE WHEN status != '' THEN 1 ELSE 0 END)
别依赖 COUNT(status) 自动过滤空字符串——它不会。
在 JOIN 场景下 COUNT 容易重复计数
LEFT JOIN 后直接 COUNT(*),很可能把主表一行对应从表多行的情况重复计算。比如用户表 LEFT JOIN 订单表,一个用户有 3 个订单,那这用户在 JOIN 结果里就占 3 行,COUNT(*) 就会算成 3,而不是用户数 1。
要统计“有多少用户下过订单”,得用子查询或 DISTINCT:
SELECT COUNT(DISTINCT u.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NOT NULL- 或者更清晰:
SELECT COUNT(*) FROM users WHERE id IN (SELECT user_id FROM orders)
DISTINCT 在 COUNT 里开销不小,大数据量时优先考虑 EXISTS 或半连接写法,避免先 JOIN 再去重。











