left join 后用 where right_col is null 能找出左表中在右表无匹配的行,因为left join保留左表所有行,未匹配的右表字段为null,where在连接结果上过滤出这些null行;必须用is null而非= null,且条件须放where而非on。

LEFT JOIN 后 WHERE col IS NULL 为什么能找出“没匹配到”的行
因为 LEFT JOIN 会保留左表所有行,右表没匹配上的字段全为 NULL;而 WHERE 是在连接结果上过滤——所以加 WHERE right_table.id IS NULL,实际就是筛出左表中“在右表里完全找不到对应记录”的那些行。
常见错误是写成 WHERE right_table.id = NULL:这是错的,= NULL 永远不成立(要用 IS NULL);也有人把条件误放 ON 子句里,导致逻辑失效。
- 必须用
IS NULL,不能用= NULL或!= NULL -
IS NULL条件必须放在WHERE,不是ON;否则会变成“左连接 + 右表某字段为 NULL 的匹配”,不是“无匹配” - 如果右表有多个可能匹配的行,但你只想找“完全没匹配上”的左表行,这个写法依然可靠
MySQL / PostgreSQL / SQL Server 中写法是否一致
核心语法完全一致:都支持 LEFT JOIN ... WHERE right_col IS NULL。差异只在细节:
- PostgreSQL 对
NULL处理最严格,但此处无影响 - SQL Server 允许在
ON里写right_col IS NULL,但那不是你要的效果——它会参与连接逻辑,可能漏掉本该捕获的行 - MySQL 8.0+ 和低版本在此场景行为一致,无需额外适配
- 所有主流引擎都对这种写法有索引友好性:如果右表连接字段有索引,性能通常不错
容易被忽略的 NULL 值干扰:右表字段本身允许 NULL 怎么办
如果右表的连接字段(比如 user_id)本身就允许 NULL,那么 WHERE user_id IS NULL 就分不清是“没匹配上”还是“匹配上了但值就是 NULL”。这时候不能直接依赖该字段判空。
- 优先改用右表的主键或非空唯一键做判断,例如
WHERE orders.id IS NULL(假设orders.id是主键,不可能为 NULL) - 如果右表没有合适非空字段,可加
AND right_table.some_required_col IS NOT NULL辅助排除“右表自己存了 NULL”的情况 - 更稳妥的做法是先确认右表连接字段的实际 NULL 分布:
SELECT COUNT(*) FROM right_table WHERE join_col IS NULL
替代方案:NOT EXISTS 为什么有时更清晰
当目标只是“查左表中在右表不存在的记录”,NOT EXISTS 语义更直白,且在某些场景下优化器更容易走索引:
SELECT * FROM users u WHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id );
但它和 LEFT JOIN ... IS NULL 并不完全等价:如果右表有重复匹配,NOT EXISTS 不会放大左表行数,而 LEFT JOIN 可能因笛卡尔积导致结果膨胀(虽然加 IS NULL 后通常不会)。
- 如果右表连接条件复杂(含多个字段或函数),
NOT EXISTS往往比LEFT JOIN更易读、更少出错 - 某些旧版 MySQL 对
NOT EXISTS优化不佳,但 5.7+ 和所有现代版本基本无差别 - 别用
NOT IN (subquery)替代——只要子查询返回任意NULL,整个条件就变UNKNOWN,结果为空
实际用哪一种,取决于你手头表结构是否干净、连接条件是否简单、以及团队习惯。真正要盯住的,是右表连接字段有没有“自己就存 NULL”的情况——这最容易让结果悄悄出错。










