当有 NULL 值的结果时,NOT IN 子查询失败

Posted

技术标签:

【中文标题】当有 NULL 值的结果时,NOT IN 子查询失败【英文标题】:NOT IN subquery fails when there are NULL-valued results 【发布时间】:2013-10-31 22:59:52 【问题描述】:

对不起,伙计们,我不知道如何表达这个,但我在 where 子句中有以下内容:

person_id not in (
    SELECT distinct person_id
    FROM protocol_application_log_devl pal
    WHERE pal.set_id = @set_id
)

当子查询没有返回任何结果时,我的整个选择都无法返回任何内容。为了解决这个问题,我将子查询中的person_id 替换为isnull(person_id, '00000000-0000-0000-0000-000000000000')

这似乎有效,但有没有更好的方法来解决这个问题?

【问题讨论】:

【参考方案1】:

最好还是使用NOT EXISTS

WHERE NOT EXISTS(
    SELECT 1 FROM protocol_application_log_devl pal
    WHERE pal.person_id = person_id
     AND  pal.set_id = @set_id
)

Should I use NOT IN, OUTER APPLY, LEFT OUTER JOIN, EXCEPT, or NOT EXISTS?

我经常看到的一种模式,但希望我没有看到,但它不在。什么时候 我看到这种模式,我畏缩。但不是出于性能原因——之后 总而言之,在这种情况下它创建了一个足够体面的计划:

主要问题是如果目标结果可能令人惊讶 列是 NULLable (SQL Server 将此作为左反半 加入,但不能可靠地告诉您右侧的 NULL 是否相等 到 - 或不等于 - 左侧的参考)。还, 如果列可以为 NULL,则优化的行为可能会有所不同,即使 它实际上不包含任何 NULL 值

此查询模式使用相关的不存在,而不是 NOT IN。 始终。其他方法可能会在性能方面与它相媲美,当所有 其他变量相同,但所有其他方法都引入 性能问题或其他挑战。

【讨论】:

哇,我猜想NOT EXISTS 的成本会高得多,主要是因为我认为每个人都需要调用子查询(在我的情况下是数万) .但是文章说查询受益于c.CustomerID 作为索引,但在我的情况下它不是索引。 person_id 的相同值可以在该表中多次出现。 @jreed121:索引不需要是唯一的即可从中受益。但即使没有索引 NOT EXISTS 也是最好的选择(如文章中所示)。【参考方案2】:

虽然我支持 Tim 的回答在实践中是正确的(此处不适合 ),但IN / NOT IN 文档中提到了一个有趣的案例:

警告:任何由子查询或表达式返回的空值,如果使用 IN 或 NOT IN 与 test_expression 进行比较,则返回 UNKNOWN。 将空值与 IN 或 NOT IN 一起使用会产生意想不到的结果1

这就是isnull“修复”问题的原因——它会屏蔽任何此类 NULL 值并避免意外行为。考虑到这一点,以下方法也可以使用(但请注意不要使用 NOT IN 开头的建议):

person_id not in (
  SELECT distinct person_id
  FROM protocol_application_log_devl pal
  WHERE pal.set_id = @set_id
    AND person_id NOT NULL    -- guard here
)

但是,NULL person_id 是可疑的,可能表示其他问题..

1这是证明布丁:

select case when 1 not in (2)       then 1 else 0 end as r1,
       case when 1 not in (2, NULL) then 1 else 0 end as r2
-- r1: 1, r2: 0

【讨论】:

但是一个 NULL person_id 仅仅意味着这个人在 protocol_application_log_devl 表中没有那个特定的 set_id。在这种情况下,我想将该人包括在我父母 SELECT 的结果中。在这一点上,我意识到我最大的成本驱动因素是这个日志表没有索引,但我不知道如何应用一个索引,因为一个人可能有相同的 set_id多次应用。 @jreed121 花时间根据需要/有益的方式重新投资到架构中。查看查询计划以确定是否有任何严重缺乏索引或查询计划不周的部分。我通常会尝试根据 JOIN 编写所有查询 - 如果不使用 distinct 就不能这样做,那么可能有问题。【参考方案3】:

我刚刚使用isnull 函数将null 值替换为空值,如下例所示。它解决了我的问题

where isnull(UserId,'') not in (select UserID from users where ...)

【讨论】:

【参考方案4】:

这应该可行:

nvl(person_id, '') not in (
    SELECT distinct person_id
    FROM protocol_application_log_devl pal
    WHERE pal.set_id = @set_id
)

【讨论】:

以上是关于当有 NULL 值的结果时,NOT IN 子查询失败的主要内容,如果未能解决你的问题,请参考以下文章

按具有相同值的值排序时定义的 SQL 行为

hibernate 向数据库里设置了默认值的字段添加数据为null时失效的问题

PLSQLNULL值的处理

hive concat ws 怎么处理null值的

在R中转换icd9代码,当有倍数时只保留最上面的结果

并发10当有多个线程设置对应的值的时候,读取的值是否是那个线程设置的值?