LEFT JOIN 查差异的核心是主表左、对比表右,用 WHERE 右表主键 IS NULL 筛缺失;须注意 NULL 判断、字段类型一致、索引优化,并可选 NOT EXISTS、FULL OUTER JOIN 或哈希比对提升准确性与效率。

用 LEFT JOIN 找出表A有但表B没有的记录
这是最常用的差异比对方式,核心思路是把主表(比如 orders)放在 LEFT JOIN 左侧,对比表(比如 orders_backup)放右侧,再用 WHERE 右表主键 IS NULL 筛出缺失项。
常见错误是写成 WHERE 右表字段 = NULL —— SQL 里 NULL 不能用等号判断,必须用 IS NULL。
示例(查原表有、备份表无的订单):
SELECT o.* FROM orders o LEFT JOIN orders_backup ob ON o.order_id = ob.order_id WHERE ob.order_id IS NULL;
- 确保
JOIN条件字段在两张表中类型一致(比如都是BIGINT,避免隐式转换导致索引失效) - 如果对比字段可能含 NULL,
ON子句里别直接用o.status = ob.status,NULL = NULL 不成立,得改用(o.status = ob.status) OR (o.status IS NULL AND ob.status IS NULL) - 大表慎用,记得给
JOIN字段建索引,否则执行计划容易走全表扫描
用 FULL OUTER JOIN 检查双向差异(仅PostgreSQL/SQL Server支持)
MySQL 不支持 FULL OUTER JOIN,硬要用就得拆成两个 LEFT JOIN 并 UNION ALL。PostgreSQL 和 SQL Server 用户可以直接用,一次查出“仅A有”“仅B有”“两边都有但值不同”三类情况。
关键点在于:只靠 ON a.id = b.id 不够,还得加字段级比对来识别内容差异。
示例(PostgreSQL):
SELECT
COALESCE(a.id, b.id) AS id,
CASE
WHEN a.id IS NULL THEN 'only_in_b'
WHEN b.id IS NULL THEN 'only_in_a'
WHEN a.amount != b.amount OR (a.amount IS NULL) != (b.amount IS NULL) THEN 'amount_diff'
ELSE 'match'
END AS diff_type
FROM table_a a
FULL OUTER JOIN table_b b ON a.id = b.id;
-
COALESCE(a.id, b.id)避免某侧为 NULL 导致结果行丢失 - 判断字段是否“真正不同”,必须同时处理 NULL:不能只写
a.amount != b.amount,要补上(a.amount IS NULL) != (b.amount IS NULL) - Oracle 用户注意:
FULL OUTER JOIN支持,但旧版本性能较差;可考虑用MINUS/UNION ALL组合替代
用 NOT EXISTS 替代 LEFT JOIN 提升可读性与语义准确性
当只关心“是否存在”而非“关联数据”,NOT EXISTS 比 LEFT JOIN ... IS NULL 更贴近业务意图,且多数引擎能更好利用索引(尤其子查询字段有索引时)。
它天然规避了 LEFT JOIN 可能引发的重复行问题(比如一对多关系下,一个 A 记录匹配多个 B 记录,LEFT JOIN 会返回多行,而 NOT EXISTS 只关心真假)。
示例(找没被审核过的订单):
SELECT o.* FROM orders o WHERE NOT EXISTS ( SELECT 1 FROM audits a WHERE a.order_id = o.order_id );
- 子查询里用
SELECT 1即可,不用SELECT *,减少解析开销 - 子查询中的
WHERE条件字段(如a.order_id)建议有索引,否则变成嵌套循环慢查 - 如果子查询需要关联多个字段(比如
order_id + status),确保复合索引顺序与 WHERE 中顺序一致
用 CHECKSUM 或 HASH 做整行内容比对(适合宽表或结构复杂场景)
当表字段多、需整体判断是否“完全一致”,逐字段写 != 条件太冗长易错。这时可在查询中动态计算哈希值,再比对。
MySQL 8.0+ 支持 MD5(CONCAT(...)),PostgreSQL 用 MD5(row_to_json(t)::text),SQL Server 用 BINARY_CHECKSUM(*)(注意该函数对 NULL 敏感,且不保证全局唯一)。
示例(MySQL,跳过自增ID和时间戳字段):
SELECT order_id, customer_id, amount, status FROM ( SELECT *, MD5(CONCAT(customer_id, '|', amount, '|', status)) AS hash_val FROM orders ) t1 LEFT JOIN ( SELECT *, MD5(CONCAT(customer_id, '|', amount, '|', status)) AS hash_val FROM orders_backup ) t2 ON t1.order_id = t2.order_id AND t1.hash_val != t2.hash_val;
- 拼接字段前加分隔符(如
'|'),避免"ab"+"c"和"a"+"bc"哈希相同 -
BINARY_CHECKSUM(*)在 SQL Server 中对 NULL 返回 0,所有 NULL 行哈希都一样,不可靠;推荐用HASHBYTES('SHA2_256', ...)显式拼接 - 哈希只是辅助手段,发现差异后仍需回查具体字段,不能直接当作修复依据
实际做差异比对时,最容易被忽略的是 NULL 的语义处理和 JOIN 字段的数据类型一致性——这两点不提前确认,查出来的“差异”大概率是假阳性。










