1

我正在尝试清理 SQL 服务器中的一些数据并在两个表之间添加一个外键。

我要删除的其中一个表中有大量孤立行。我不知道为什么下面的查询会在 MS SQL 服务器中返回 0 行。

--此查询不返回任何行

select * from tbl_A where ID not in ( select distinct ID from tbl_B )

当我在子查询中包含 IS NOT NULL 时,我得到了我期望的结果。

-- 返回的行包含 tbl_A 中的所有记录,但不包含 tbl_B 中的所有记录

select * from tbl_A where ID not in ( select distinct ID from tbl_B where ID is not null )

ID 列可以为空,并且包含空值。如果我只运行子查询,我会得到完全相同的结果,除了第一个查询按预期返回一个额外的 NULL 行。

4

5 回答 5

2

这是NOT IN子查询的预期行为。当子查询返回单个nullNOT IN时将不匹配任何行。

如果您不想专门进行null检查,那么您将需要使用NOT EXISTS

select * 
from tbl_A A
where not exists (select distinct ID 
                  from tbl_B b
                  where a.id = b.id)

至于为什么NOT IN会引起问题,这里有一些讨论它的帖子:

NOT IN vs. NOT EXISTS vs. LEFT JOIN / IS NULL

不存在与不存在

NOT EXISTS vs. NOT IN vs. LEFT JOIN WHERE IS NULL 之间有什么区别?

于 2013-01-14T17:11:26.387 回答
1

从逻辑的角度来看,使用等于 (=) 匹配 NULL 将返回 NULL 或 UNKNOWN,而不是 true/false。例如,参见http://msdn.microsoft.com/en-us/library/aa196339(v=sql.80).aspx进行讨论。

如果您想在表 A 中包含在表 B 中没有 NULL 的情况下查找 NULL 值(如果 B 是“父”并且 A 是您想要的“外键”关系中的“子”),那么您需要第二个声明,类似于以下内容。此外,我建议使用表前缀或别名来限定 ID 字段,因为两个表中的字段名称相同。最后,我不建议将 NULL 值作为键。但无论如何:

select * from tbl_A as A where (A.ID not in ( select distinct B.ID from tbl_B as B ))
  or (A.ID is NULL and not exists(select * from tbl_B as B where B.ID is null))
于 2013-01-14T17:13:18.333 回答
0

您可能关闭了 ANSI NULL。这会比较 null 值,因此 null=null 将返回 true。

在第一个查询前加上

SET ANSI_NULLS ON
GO
于 2013-01-14T17:12:41.697 回答
0

问题是空值的不可比性。如果您要问“不在”并且子查询中有空值,则它不能说任何东西绝对不存在,因为它将这些空值视为“未知”,因此在三值逻辑中答案始终是“未知” SQL 使用的。

当然,这一切都假设您已启用 ANSI_NULLS(这是默认设置)如果您将其关闭,那么 NULLS 突然变得可比较,它会给您结果,并且可能是您期望的结果。

于 2013-01-14T17:20:22.467 回答
0

如果 id 永远不会是负数,您可能会考虑类似:

select *
from tbl_A
where coalesce(ID, -1) not in ( select distinct coalesce(ID, -1) from tbl_B )

(或者如果id是一个字符串,使用 something line coalesce(id, '<null>'))。

这可能并非在所有情况下都有效,但它在编码级别具有简单性的优点。

于 2013-01-14T17:23:08.177 回答