两个SQL查询结果不符的技术问题咨询
这问题我之前踩过坑!看起来逻辑一致的两个查询结果不一样,核心原因是SQL对NULL值的特殊处理逻辑,尤其是NOT IN子查询碰到NULL时的行为和左连接完全不同,咱们来拆解清楚:
你的两个查询及结果
Query #1(左连接筛选)
SELECT SUM(size) FROM RepoSize s LEFT JOIN VirtualRepo v ON s.repo_id = v.repo_id WHERE v.repo_id IS NULL;
执行结果:
+----------------+ | SUM(size) | +----------------+ | 61550890457198 | +----------------+ 1 row in set (0.32 sec)
Query #2(NOT IN筛选)
SELECT SUM(size) FROM RepoSize WHERE repo_id NOT IN (SELECT repo_id FROM VirtualRepo);
执行结果:
+----------------+ | SUM(size) | +----------------+ | 61551148262106 | +----------------+ 1 row in set (0.45 sec)
为什么结果会有差异?
关键看VirtualRepo表的repo_id字段是否存在NULL值,以及RepoSize中是否有repo_id为NULL的行,这两种场景会导致两个查询的逻辑出现偏差:
1. 左连接(Query #1)的逻辑
LEFT JOIN会保留RepoSize的所有行,然后尝试和VirtualRepo匹配repo_id:
- 如果
RepoSize的某行repo_id在VirtualRepo中能找到匹配,那么v.repo_id会是对应的值,最终被WHERE v.repo_id IS NULL排除。 - 如果
RepoSize的某行repo_id在VirtualRepo中找不到匹配(包括RepoSize的repo_id是NULL的情况——因为SQL中NULL = NULL的结果是不成立的,所以无法匹配),那么v.repo_id会被设为NULL,这部分行会被保留并计入SUM。
2. NOT IN(Query #2)的逻辑
NOT IN的判断逻辑是:对于RepoSize的每一行repo_id,检查它是否完全不存在于VirtualRepo的repo_id集合中。但这里有个致命陷阱:
- 如果
VirtualRepo的repo_id中存在任何NULL值,那么对于RepoSize中的任意repo_id(不管是NULL还是非NULL),repo_id NOT IN (...)的结果都会是UNKNOWN(既不是TRUE也不是FALSE)。因为repo_id != NULL的结果是未知的,而逻辑AND中只要有一个UNKNOWN,整个表达式结果就是UNKNOWN。 - SQL的
WHERE子句只会保留判断结果为TRUE的行,所以这些UNKNOWN的行都会被排除,导致SUM结果和预期不符。
结合你的结果,建议先执行以下查询验证NULL值的影响:
-- 检查VirtualRepo是否存在NULL的repo_id SELECT COUNT(*) FROM VirtualRepo WHERE repo_id IS NULL; -- 检查RepoSize中NULL的repo_id对应的行数和总和 SELECT COUNT(*), SUM(size) FROM RepoSize WHERE repo_id IS NULL;
如果想要和Query1逻辑一致的替代写法,推荐用NOT EXISTS,它对NULL的处理更符合直觉:
SELECT SUM(size) FROM RepoSize s WHERE NOT EXISTS (SELECT 1 FROM VirtualRepo v WHERE s.repo_id = v.repo_id);
内容的提问来源于stack exchange,提问作者cuihaikuo
相关产品推荐
相关产品推荐

