SQL"选择不在子查询中的位置"不返回任何结果

Jer*_*ein 120 sql t-sql sql-server

免责声明:我已经找到了问题(我认为),但我想将此问题添加到Stack Overflow,因为我无法(轻松)在任何地方找到它.此外,有人可能会比我更好的答案.

我有一个数据库,其中一个表"Common"被其他几个表引用.我想看看Common表中的哪些记录是孤立的(即没有来自任何其他表的引用).

我运行了这个查询:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)
Run Code Online (Sandbox Code Playgroud)

我知道有孤立的记录,但没有返回任何记录.为什么不?

(这是SQL Server,如果重要的话.)

Qua*_*noi 218

更新:

我博客中的这些文章更详细地描述了这些方法之间的差异:


有三种方法可以执行此类查询:

table1.common_id不可为空时,所有这些查询在语义上都是相同的.

当它可以为空时,NOT IN是不同的,因为IN(并且因此NOT IN)NULL当值与包含a的列表中的任何内容不匹配时返回NULL.

这可能会令人困惑,但如果我们回想一下这个的替代语法,可能会变得更加明显:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)
Run Code Online (Sandbox Code Playgroud)

此条件的结果是列表中所有比较的布尔乘积.当然,单个NULL值产生的NULL结果NULL也会产生整个结果.

我们永远不能肯定地说common_id这个列表中的任何内容都不等于,因为至少有一个值是NULL.

假设我们有这些数据:

common

--
1
3

table1

--
NULL
1
2
Run Code Online (Sandbox Code Playgroud)

LEFT JOIN / IS NULL并且NOT EXISTS将返回3,NOT IN将不返回任何内容(因为它将始终评估为FALSE或者NULL).

MySQL非可空列的情况下,LEFT JOIN / IS NULL并且比NOT IN一小部分(百分之几)更高效NOT EXISTS.如果列可以为空,NOT EXISTS则效率最高(再次,不多).

Oracle,所有三个查询产生相同的计划(an ANTI JOIN).

In SQL Server,NOT IN/ NOT EXISTS更高效,因为LEFT JOIN / IS NULL无法ANTI JOIN通过其优化器对其进行优化.

In PostgreSQL,LEFT JOIN / IS NULL并且NOT EXISTSNOT IN它们更有效,它们被优化为a Anti Join,同时NOT IN使用hashed subplan(subplan如果子查询太大而无法哈希,则甚至是普通的)

  • 很棒的答案!谢谢! (8认同)
  • 这太棒了,非常有帮助 (3认同)
  • +1,因为四年半过去了,这个答案帮助我解决了一个困扰我的问题! (2认同)
  • @Carson63000 啪!在看到这个答案之前我以为我疯了 (2认同)

Amy*_*y B 35

如果你想让世界成为一个双值布尔位置,你必须自己防止null(第三个值)的情况.

不要编写允许列表方面为空的IN子句.过滤掉它们!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Run Code Online (Sandbox Code Playgroud)

  • in-clause-list中的空值是缺少查询结果的常见原因. (6认同)

Dor*_*aba 13

简短的回答:

\n

子查询返回的集合中有一个 NULL。您可以通过在完成子查询之前删除该 NULL 值或使用NOT EXISTS谓词而不是NOT IN解决该问题,因为它隐式地执行了该操作。

\n

长答案(来自 T-SQL 基础知识,第三版,作者:Itzik Ben-Gan)

\n

这是一个示例:假设 Sales.Orders 表中有一个 orderid 为 NULL 的订单,因此子查询返回一些整数和一个 NULL 值。

\n
SELECT custid, companyname\nFROM Sales.Customers\nWHERE custid NOT IN(SELECT O.custid\n             FROM Sales.Orders AS O);\n
Run Code Online (Sandbox Code Playgroud)\n

关于为什么上面的查询返回空集的解释:

\n

显然,罪魁祸首是NULL您添加到 Orders 表中的客户 ID。是NULL子查询返回的元素之一。\n让\xe2\x80\x99s 从行为符合您预期的部分开始。对于下订单的客户(例如客户 85),IN 谓词返回 TRUE,因为子查询返回这样的客户。NOT 运算符否定谓词IN;因此,NOT TRUE变成FALSE,并且客户被丢弃。这里的预期行为是,如果已知客户 ID 出现在 Orders 表中,则您可以肯定地知道您不想将其退回。

\n

但是(深吸一口气),如果 Customers 中的客户 ID 没有出现在 Orders 中的非 NULL 客户 ID 集合中,并且还有一个NULL客户 ID,那么您可以\xe2\x80\x99t 确定地告诉客户在那里\xe2\x80\x94,同样,你也可以\xe2\x80\x99t 确定地告诉\xe2\x80\x99 不在那儿。使困惑?我希望我能用一个例子来澄清这个解释。

\n

IN谓词返回UNKNOWN未出现在 Orders 的已知客户 ID 集中的客户(例如 22)。\xe2\x80\x99s 因为当你将它与已知的客户 ID 进行比较时,你会得到 FALSE,而当你将它与 NULL 进行比较时,你会得到UNKNOWNFALSEUNKNOWN产生UNKNOWN。考虑这个表达式22 NOT IN (1, 2, <other non-22 values>, NULL)。该表达式可以改写为NOT 22 IN (1, 2, \xe2\x80\xa6, NULL)。您可以将此表达式扩展为NOT (22 = 1 OR 22 = 2 OR \xe2\x80\xa6 OR 22 = NULL). 将括号中的每个表达式计算为真值,您将得到NOT (FALSE OR FALSE OR \xe2\x80\xa6 OR UNKNOWN),它会转换为NOT UNKNOWN,其计算结果为UNKNOWN

\n

这里的逻辑含义是UNKNOWN,在应用NOT运算符之前,\xe2\x80\x99 无法确定客户 ID 是否出现在集合中,因为 可以NULL代表该客户 ID。这里棘手的部分是否定UNKNOWNwithNOT运算符仍然会产生 \n UNKNOWN。这意味着,在未知客户 ID 是否出现在集合中的情况下,也未知其是否出现在集合中。请记住,查询过滤器会丢弃\n获得的行UNKNOWN在谓词结果中的行。

\n

简而言之,当您NOT IN对至少返回一个 的子查询使用谓词时NULL,查询始终返回一个空集。那么,您可以遵循哪些做法来避免此类麻烦呢?首先,\n当列不应允许 时NULLs,请务必将其定义为 NOT NULL。其次,在您编写的所有查询中,您应该考虑 NULL 和三值逻辑。明确地考虑查询是否可能处理 NULL,如果是,SQL\xe2\x80\x99 对 NULL 的处理是否对您来说是正确的。当是\xe2\x80\x99t时,就需要介入了。例如,我们的查询返回一个空集,因为\n与NULL. 如果要检查客户 ID 是否仅出现在已知值集中,则应显式或隐式排除 NULL\xe2\x80\x94。要明确排除它们,\n请添加谓词O.custidIS NOT NULL 添加到子查询中,如下所示:

\n
SELECT custid, companyname\nFROM Sales.Customers\nWHERE custid NOT IN(SELECT O.custid\n                    FROM Sales.Orders AS O\n                    WHERE O.custid IS NOT NULL);\n
Run Code Online (Sandbox Code Playgroud)\n

NOT EXISTS您还可以使用谓词而不是隐式排除 NULLNOT IN如下所示:

\n
SELECT custid, companyname\nFROM Sales.Customers AS C\nWHERE NOT EXISTS\n   (SELECT *\n    FROM Sales.Orders AS O\n    WHERE O.custid = C.custid);\n
Run Code Online (Sandbox Code Playgroud)\n

回想一下,与 不同IN, ,EXISTS使用二值谓词逻辑。EXISTS总是返回TRUEor\nFALSE而从不返回UNKNOWN。当子查询遇到NULLin时O.custid,表达式\n计算为UNKNOWN并且该行被过滤掉。就EXISTS谓词而言,\nNULL情况会自然消除,就好像它们不存在\xe2\x80\x99t 一样。因此EXISTS最终仅处理\n已知的客户 ID。因此,它\xe2\x80\x99使用起来NOT EXISTSNOT IN.

\n

以上信息摘自第 4 章 - 子查询,T-SQL 基础知识,第三版

\n


Jer*_*ein 5

表1或表2的common_id具有一些空值。使用以下查询:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
Run Code Online (Sandbox Code Playgroud)


pat*_*ech 5

select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
Run Code Online (Sandbox Code Playgroud)