在关系型数据库的日常查询中,我们经常会遇到这样的需求:找出在A表中存在、但在B表中没有对应记录的数据行。这类问题本质上就是求两个集合的差集。利用LEFT JOIN配合IS NULL是最经典且兼容性最好的实现方式,下面我们详细拆解它的原理与写法。

一、LEFT JOIN与IS NULL的基本原理
LEFT JOIN(左连接)会以左表也就是A表为基准,将B表中满足连接条件的记录拼接到结果里。如果B表中找不到匹配的行,那么B表的所有选中列都会以NULL填充。基于这个特性,只要我们在连接之后,用WHERE子句过滤出B表连接键为NULL的记录,就能精准得到那些只存在于A表、不存在于B表的数据。
这种做法之所以被广泛使用,是因为它语义清晰,不需要写复杂的子查询,而且大多数数据库的优化器都能针对LEFT JOIN生成高效的执行计划。相比之下,使用NOT IN如果遇到NULL值还容易产生意料之外的结果,而LEFT JOIN加IS NULL则没有这个隐患。
二、基础代码示例
假设我们有两张表:orders表存放订单信息,shipments表存放发货信息,订单通过order_id关联。现在我们要查出所有未发货的订单。
-- 查询只存在于orders表但不存在于shipments表的订单 SELECT o.order_id, o.create_time, o.amount FROM orders o LEFT JOIN shipments s ON o.order_id = s.order_id WHERE s.order_id IS NULL;
在上面这段代码中,orders作为左表,shipments作为右表。连接条件是两个表的order_id相等。对于那些没有发货记录的订单,右表shipments的order_id自然是NULL,于是被WHERE条件筛选出来。
需要特别注意的是,WHERE过滤的必须是右表的非主键或连接键字段为NULL,通常使用连接用的字段即可。如果误把过滤条件写进ON子句,就会改变连接行为,导致结果不正确。
三、与NOT EXISTS写法对比
除了LEFT JOIN加IS NULL,开发者也常使用NOT EXISTS来实现相同逻辑。两者在结果上一致,但在执行机制和适用场景上有细微差别。
-- 使用NOT EXISTS实现同样需求 SELECT o.order_id, o.create_time, o.amount FROM orders o WHERE NOT EXISTS ( SELECT 1 FROM shipments s WHERE s.order_id = o.order_id );
NOT EXISTS在语义上更直接表达“不存在”的含义,某些数据库在右表字段有索引时,会对NOT EXISTS做半连接优化,效率极高。而LEFT JOIN加IS NULL在表结构复杂、需要同时取出左表多列时,书写起来更直观。从可读性角度,LEFT JOIN方式对新手更友好,因为整个查询是一个扁平的语句。
在大数据量下,如果shipments.order_id上有索引,两种写法性能通常接近;但若B表极小,LEFT JOIN可能先扫小表再哈希匹配,而NOT EXISTS可能走嵌套循环。实际项目中建议用EXPLAIN查看执行计划再做选择。
四、常见误区与避坑
一个常见错误是在LEFT JOIN之后,把右表的条件写进WHERE却忘了处理NULL,或者在ON里写了本应属于WHERE的过滤,导致左表记录被提前丢掉。例如下面这种错误示范:
-- 错误示例:在ON里加右表额外条件,可能过滤掉左表基准行 SELECT o.order_id FROM orders o LEFT JOIN shipments s ON o.order_id = s.order_id AND s.status = 'done' WHERE s.order_id IS NULL;
上述代码虽然也能跑,但s.status = 'done'放在ON里意味着只有已发货的才参与连接,未发货的B表侧为NULL,于是连“已发货但想查未发货”的逻辑都乱了。正确做法是,如果要把B表某状态作为“存在”的依据,应把该条件放入ON,但明确业务含义;若只是查完全无记录,则ON只写关联键。
另一个坑是字段类型不一致,比如A表order_id是字符串,B表是数字,某些数据库会做隐式转换导致索引失效。保证关联字段类型一致,是写出高效差集查询的前提。
五、总结与扩展
通过LEFT JOIN配合IS NULL,我们可以用标准SQL语法稳定地查出只存在于A表而不存在于B表的数据。它的核心在于理解左连接保留左表全量、右表无匹配补NULL的机制。在真实业务里,这种方法适用于数据核对、脏数据排查、异步流程漏单检测等场景。
当你掌握这种写法后,也可以进一步了解FULL OUTER JOIN模拟差集、以及使用EXCEPT关键字(部分数据库支持)的写法。但在跨数据库兼容的项目中,LEFT JOIN加IS NULL依旧是最省心的首选方案。