LEFT JOIN + IS NULL 能查差集,因其保留左表所有行,右表无匹配时对应字段为NULL,IS NULL筛选出左有右无的记录;须作用于右表非空字段(如主键),否则可能误判。

LEFT JOIN + IS NULL 为什么能查差集
因为 LEFT JOIN 会保留左表所有行,右表无匹配时对应字段全为 NULL;加 IS NULL 筛选后,就只留下左表有、右表没有的记录——这正是集合差(A − B)的语义。
注意:必须用 LEFT JOIN,不能用 INNER JOIN 或 RIGHT JOIN,否则逻辑失效;IS NULL 必须作用于右表的**某个非空约束字段**(比如主键或 NOT NULL 列),否则可能漏判。
- 推荐写法:
ON a.id = b.id WHERE b.id IS NULL(假设b.id是主键或必非空) - 错误写法:
WHERE b.name IS NULL(若b.name允许NULL,会把本该匹配却恰好name为空的行也误选进来) - 性能提示:确保
ON条件中右表字段有索引,否则JOIN过程可能全表扫描
和 NOT EXISTS 对比:什么情况下该选 LEFT JOIN + IS NULL
两者语义等价,但执行计划和可读性不同。MySQL 5.7 及更早版本中,LEFT JOIN + IS NULL 常比 NOT EXISTS 更快;而 PostgreSQL 和新版本 MySQL 优化器通常能将二者转成相同计划。
- 适用
LEFT JOIN + IS NULL的场景:EXPLAIN显示其实际用了索引嵌套循环,且右表过滤条件简单(如仅靠主键关联) - 慎用场景:右表关联条件含函数或表达式(如
ON a.code = UPPER(b.code)),会导致索引失效,此时NOT EXISTS更可控 - 可读性差异:
LEFT JOIN + IS NULL更直观反映“找左边有、右边无”的意图,适合团队协作中快速理解
常见翻车点:NULL 值陷阱与连接字段类型不一致
差集结果异常,八成出在这两处。尤其当字段允许 NULL 或隐式类型转换发生时,JOIN 条件本身就会漏掉本该匹配的行,导致 IS NULL 筛出来的不是真差集。
-
a.status是VARCHAR,b.status是INT:MySQL 可能静默转成数字比较,'active'→0,导致匹配失败,虚增差集 - 左表某行
a.category为NULL,右表无NULL值:因NULL = NULL永假,该行必然进入结果,但它其实不属于“业务意义上的差集” - 修复方法:显式 cast 类型(如
ON a.id = CAST(b.id AS SIGNED)),或提前用WHERE a.category IS NOT NULL排除歧义行
一个可靠示例:查用户表中未下单的用户ID
假设 users 表有 id(主键),orders 表有 user_id(外键,NOT NULL):
SELECT u.id FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.user_id IS NULL;
这里用 o.user_id IS NULL 安全,因为 user_id 是外键且非空;如果换成 o.order_no IS NULL 就危险——order_no 可能允许 NULL,哪怕该订单存在也会被误判。
真正难的不是写出这个语句,而是确认右表那个被 IS NULL 的字段,在业务和 DDL 层面确实不可能为 NULL。这点常被跳过,直到线上数据对不上才回头查。

















