用count()=count(字段)可判断字段是否全非空,因count()统计所有行(含null),count(字段)仅统计非空值;两者不等说明存在null脏数据,再用where字段is null定位。

用 COUNT(*) 和 COUNT(字段) 检查字段是否全非空
迁移后最常被忽略的脏数据是 NULL 值混入本该非空的字段(比如 order_id、user_id)。直接比行数会漏掉这类问题。
正确做法是:对关键字段执行 COUNT(*) = COUNT(字段),结果为 true 才说明该字段无 NULL。
-
COUNT(*)统计所有行,包括该字段为NULL的行 -
COUNT(字段)自动跳过NULL,只统计非空值 - 若两者不等,说明存在脏数据;再加
WHERE 字段 IS NULL就能定位具体记录 - 别用
COUNT(字段)替代COUNT(*)做总行数校验——它天然漏数据
用 HAVING 筛选分组异常,而不是 WHERE
想查“哪些订单没关联明细”或“哪些邮箱重复注册”,必须用 HAVING,不是 WHERE。因为聚合函数不能在 WHERE 中使用,否则报错 Invalid use of aggregate function。
- 错误写法:
SELECT email FROM users GROUP BY email WHERE COUNT(*) > 1→ 语法错误 - 正确写法:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 -
HAVING是在分组后过滤,WHERE是在分组前过滤原始行 -
HAVING性能敏感:确保GROUP BY字段有索引,否则扫描开销陡增
LEFT JOIN + COUNT(右表字段) 暴露孤儿记录
迁移后常出现“主表有记录,但从表没对应明细”的情况(比如 orders 表存在,但 order_items 中找不到对应 order_id)。用 LEFT JOIN 配合 COUNT(右表主键) 能精准抓出这类孤儿记录。
-
COUNT(oi.id)在LEFT JOIN下,匹配不到时返回0;而COUNT(*)仍为1(左表行还在) - 典型语句:
SELECT o.id FROM orders o LEFT JOIN order_items oi ON o.id = oi.order_id GROUP BY o.id HAVING COUNT(oi.id) = 0 - 注意:
oi.order_id IS NULL的记录不会参与 JOIN,需单独用WHERE order_id IS NULL检查“悬空”明细 - 如果右表字段允许
NULL,且业务上不应为空,得额外加WHERE oi.order_id IS NOT NULL过滤
整数除法导致比例计算失真
做“有效订单占比”这类计算时,COUNT(flag)/COUNT(*) 在 MySQL/PostgreSQL 默认返回整数,0.95 直接变 0,误判一致性失败。
- 错误写法:
SELECT COUNT(status = 'paid') / COUNT(*) FROM orders→ 结果恒为 0 或 1 - 修复方式:显式转浮点,如
ROUND(COUNT(status = 'paid') * 1.0 / COUNT(*), 4) - 更稳妥写法:
CAST(COUNT(status = 'paid') AS DECIMAL) / COUNT(*) - SQLite 更麻烦:它不支持
DECIMAL,得用CAST(x AS REAL)或乘以1.0
聚合函数本身不慢,但容易踩的坑都在语义理解上:COUNT(*) 和 COUNT(字段) 的 NULL 处理逻辑、HAVING 和 WHERE 的执行阶段、LEFT JOIN 下 COUNT 的行为差异——这些细节一旦写错,校验就形同虚设。











