最稳妥的方式是 left join + is null,即以主表为左表、关联表为右表,join 后筛选右表字段为 null 的行;需避免 where right_table.id = null(应为 is null),并确保关联字段类型一致、建立索引。

用 LEFT JOIN + IS NULL 找出左表中没被关联的记录
这是最常用也最稳妥的方式,尤其适合 MySQL、PostgreSQL、SQL Server 等主流数据库。核心逻辑是:把主表(比如 tblcountry)作为左表,关联表(比如 tblprojectcountry)作为右表,LEFT JOIN 后筛选右表字段为 NULL 的行。
常见错误是写成 WHERE right_table.id = NULL —— 这永远不成立,必须用 IS NULL。
实操建议:
- 确保
ON条件中的关联字段类型一致(比如都是INT,别一边是VARCHAR一边是INT) - 在
tblprojectcountry.countryid上建索引能显著提速,特别是当关联表很大时 - 避免在
WHERE子句里对右表字段做函数操作(如WHERE COALESCE(c.countryid, 0) = 0),会绕过索引
示例:
SELECT c.countryid, c.countrycode FROM tblcountry c LEFT JOIN tblprojectcountry pc ON c.countryid = pc.countryid WHERE pc.countryid IS NULL;
用 NOT EXISTS 替代 NOT IN 防止 NULL 引发意外空结果
当你想查 tblcountry 中“没有任何项目关联”的国家时,NOT IN 看似简洁,但只要 tblprojectcountry.countryid 里有 NULL,整条查询就返回空集——这是 SQL 三值逻辑导致的隐蔽陷阱。
NOT EXISTS 没这个问题,语义清晰,且多数引擎能走半连接优化。
实操建议:
- 子查询里必须相关联:
WHERE pc.countryid = c.countryid不能漏掉 - 子查询中不需要
SELECT *,用SELECT 1更轻量 - 如果关联字段允许 NULL,
NOT EXISTS是唯一安全选择
示例:
SELECT c.countryid, c.countrycode FROM tblcountry c WHERE NOT EXISTS ( SELECT 1 FROM tblprojectcountry pc WHERE pc.countryid = c.countryid );
EXCEPT 在支持它的数据库里更直观,但注意兼容性
EXCEPT(或 PostgreSQL/SQL Server 的 EXCEPT,SQLite 的 EXCEPT,MySQL 的 NOT EXISTS 或 LEFT JOIN 替代)直接表达“A 减去 B”的意图,可读性高。但它要求左右查询列数、类型、顺序完全一致,且自动去重。
性能上,它通常等价于 NOT EXISTS,但某些场景下优化器可能无法下推过滤条件。
实操建议:
- 只在 PostgreSQL、SQL Server、SQLite 等明确支持
EXCEPT的环境用;MySQL 用户别硬套,会报错 - 如果原表有重复国家,而你又需要保留原始重复项,
EXCEPT就不合适(它强制去重) - 别在
EXCEPT左右两边用SELECT *,显式列出字段更可控
示例(PostgreSQL):
SELECT countryid, countrycode FROM tblcountry EXCEPT SELECT DISTINCT pc.countryid, c.countrycode FROM tblprojectcountry pc JOIN tblcountry c ON pc.countryid = c.countryid;
为什么别轻易用 NOT IN?
不是语法错,而是语义风险太大。只要子查询结果里任意一个 countryid 是 NULL,整个 NOT IN 表达式就变成 UNKNOWN,导致整行被过滤掉——结果看起来“少了很多”,却找不到原因。
这种问题在线上查数据缺失时极难定位,因为表数据量大时,你根本想不到是 NULL 搞的鬼。
实操建议:
- 除非你能 100% 确保子查询字段非空(比如加了
WHERE countryid IS NOT NULL),否则跳过NOT IN - 用
EXPLAIN看执行计划时,NOT IN常被重写成NOT EXISTS,但前提是你没引入 NULL - 团队协作时,
NOT EXISTS比NOT IN更容易被新人理解且不易出错
真正容易被忽略的点是:NULL 不是值,是缺失标记。所有涉及 NULL 的布尔判断都得按三值逻辑重新想一遍。










