COUNT(*)统计结果集总行数,COUNT(列名)仅统计该列非NULL值个数;二者差异源于SQL标准对NULL的强制忽略,非bug,只要该列存在NULL值,结果必不相等。
因为 COUNT(*) 统计的是结果集的**总行数**,而 COUNT(列名) 统计的是该列**非 NULL 值的个数**——只要表中存在任意一行该列为 NULL,两者结果就必然不等。这不是 bug,是 SQL 标准强制行为。
那些被 COUNT(列名) 排除的行,其实都还在结果集中,只是该列值为 NULL。常见来源包括:
NULL,且业务插入时显式写了 INSERT ... VALUES (NULL)
LEFT JOIN 后右表无匹配,导致 right_table.id 为 NULL
'N/A' 或 0,但约束没加 NOT NULL,仍可插入 NULL
NULL
别猜,直接查差值:
SELECT COUNT(*) AS total, COUNT(email) AS email_non_null, COUNT(*) - COUNT(email) AS null_count FROM users;
如果 null_count > 0,说明 email 列确实有 NULL;此时用 COUNT(email) 就天然少算这些行。注意:''(空字符串)和 0 都会被计入,只有真正的 NULL 被跳过。
LEFT JOIN 后直接用 COUNT(*) 或 COUNT(右表列名),语义完全不同:
COUNT(*) → 数的是连接后“物理行数”,一对多会膨胀COUNT(users.id) → 只统计右表匹配成功的行,NULL 行全被过滤COUNT(*) FROM orders LEFT JOIN users ...,得用 COUNT(DISTINCT orders.id) 或子查询最常被忽略的一点:COUNT 的作用对象不是原始表,而是当前执行完 JOIN、WHERE、GROUP BY 后的**中间结果集**——在这个结果集里再判断 NULL,不是在源表里判。