可靠的数据迁移校验必须分层:先用COUNT(*)=COUNT(字段)验证关键字段全非空,再用LEFT JOIN+HAVING COUNT(右表主键)=0查孤儿记录,接着用HAVING替代WHERE筛选聚合结果,最后显式转浮点防整数除法失真。

直接比行数或 SUM 是最危险的校验方式——它会漏掉 NULL、重复、字段错位等关键不一致问题。真正可靠的校验必须分层:先查存在性,再查值一致性,最后看分布逻辑。
用 COUNT(*) = COUNT(字段) 检查关键字段是否全非空
迁移后最常见的“隐形失败”是本该非空的字段(如 order_id、user_id)混入了 NULL。只比 COUNT(*) 会误判通过。
-
COUNT(*)统计所有行,含该字段为 NULL 的行 -
COUNT(字段)自动跳过 NULL,只统计非空值 - 若
COUNT(*) != COUNT(order_id),说明存在脏数据;再补一句WHERE order_id IS NULL就能定位具体记录 - 别用
COUNT(order_id)替代COUNT(*)做总行数校验——它天然漏数据
用 LEFT JOIN + HAVING COUNT(右表主键) = 0 找孤儿记录
主表有数据、从表没对应明细(比如 orders 存在但 order_items 中找不到对应 order_id),这种关联断裂靠行数比对完全无法发现。
- 正确写法:
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 -
COUNT(oi.id)在 LEFT JOIN 下匹配不到时返回 0;而COUNT(*)仍为 1(左表行还在) - 注意:
oi.order_id IS NULL的记录不会参与 JOIN,需单独用WHERE order_id IS NULL检查“悬空”明细 - 如果右表字段允许 NULL 且业务上不应为空,得额外加
WHERE oi.order_id IS NOT NULL
用 HAVING 替代 WHERE 筛选聚合结果
想查“哪些邮箱重复注册”或“哪些订单没关联明细”,直接在 WHERE 里写 COUNT(*) > 1 会报错 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字段有索引,否则扫描开销陡增
整数除法导致比例计算失真,必须显式转浮点
做“有效订单占比”这类计算时,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,得用1.0 * COUNT(...)强制浮点
聚合函数校验真正的难点不在语法,而在你是否清楚每一步在验证什么业务假设——比如 COUNT(*) = COUNT(id) 隐含的前提是“id 不应为空”,而这个前提一旦被打破,整个校验就失去意义。

















