SQL左反半连接等效查询
Posted
技术标签:
【中文标题】SQL左反半连接等效查询【英文标题】:SQL left anti semi join equivalent query 【发布时间】:2021-03-28 22:12:48 【问题描述】:我正在分析 SQL Server 中的concept of anti semi-join,
描述 U-SQL 语法的关键字 ANTISEMIJOIN 的 Microsoft documentation 引用了以下 SQL 查询:
SELECT * FROM A
WHERE A.Key NOT IN (SELECT B.Key FROM B)
Others 建议以下查询:
SELECT * FROM A
WHERE NOT EXISTS (SELECT 1 FROM B WHERE A.Key = B.Key)
他们似乎都返回相同的记录,微软提出的查询似乎有更好的性能,但是我发现了一个案例,其中两个查询给出了不同的结果,我不知道他们中的哪个是正确的.我用一个例子来展示它:
DECLARE @A TABLE ([OnA] [int], [DataA] [text])
DECLARE @B TABLE ([OnB] [int], [DataB] [text])
INSERT INTO @A VALUES (1, 'A1'), (2, 'A2'), (NULL, 'A3')
INSERT INTO @B VALUES (1, 'B1'), (3, 'B2'), (NULL, 'B3')
--LEFT SEMI-JOIN
SELECT * FROM @A
WHERE OnA IN (SELECT OnB FROM @B)
--Returns with ANSI_NULLS ON: 1 | A1 (Correct!)
--Returns with ANSI_NULLS OFF: 1 | A1, NULL | A3 (Correct!)
SELECT * FROM @A
WHERE EXISTS (SELECT 1 FROM @B WHERE OnA = OnB)
--Returns with ANSI_NULLS ON: 1 | A1 (Correct!)
--Returns with ANSI_NULLS OFF: 1 | A1 (Not correct!)("ANSI_NULLS OFF" insensitive!)
--LEFT ANTISEMI-JOIN
SELECT * FROM @A
WHERE OnA NOT IN (SELECT OnB FROM @B)
--Returns with ANSI_NULLS ON: empty (Not Correct and very dangerous!)
--Returns with ANSI_NULLS OFF: 2 | A2 (Correct!)
SELECT * FROM @A
WHERE NOT EXISTS (SELECT 1 FROM @B WHERE OnA = OnB)
--Returns with ANSI_NULLS ON: 2 | A2, NULL | A3 (Correct!)
--Returns with ANSI_NULLS OFF: 2 | A2, NULL | A3 (Not correct!)("ANSI_NULLS OFF" insensitive!)
很明显,由于键列的对应关系中存在 NULL,Microsoft 引用的查询 left antisemi-join 不会返回(在正常情况下:当 ANSI_NULLS ON 时)互补left semi-join 查询的结果,这已经是一个错误。另外,总是返回零记录,很严重!
另一方面,带有 EXIST 和 NOT EXIST 运算符的其他公式似乎也与“ANSI_NULLS OFF”设置不一致。此错误还会影响“左/右半连接”操作的结果!
所以,我假设“semi join”操作在 T-SQL/SQL Server 中是 ANSI_NULLS 证明,但它只能以这种方式完成:
--LEFT SEMI-JOIN
SELECT * FROM @A
WHERE OnA IN (SELECT OnB FROM @B)
虽然“反半联接”操作不安全且不是 ANSI_NULLS 证明,但我们有责任在静态上下文中使用正确的变体:
--LEFT ANTISEMI-JOIN (when ANSI_NULLS is ON)
SELECT * FROM @A
WHERE NOT EXISTS (SELECT 1 FROM @B WHERE OnA = OnB)
--LEFT ANTISEMI-JOIN (when ANSI_NULLS is OFF and it's possible that there is at least one record with NULL in key value)
SELECT * FROM @A
WHERE OnA NOT IN (SELECT OnB FROM @B)
你们都同意我的观点吗?
这些是我的问题:
使用 SET ANSI_NULLS 而不是显式查询来处理“NullVsNull”可能性是否正确?
无论设置 ANSI_NULLS 如何,什么是忠实反映 ANTI SEMI JOIN 行为的最佳单一查询?
Microsoft 和 Others 有可能都犯了错误吗?
为什么 EXIST 运算符似乎对“ANSI_NULLS OFF”设置不敏感?
为什么NOT IN操作符在第五种情况下总是返回EMPTY?
【问题讨论】:
在 MS SQL 中,这被普遍接受为 按设计,当SET ANSI_NULLS ON
时,您不能也不应该尝试以这种方式使用 Null 比较。除非您使用 IS NULL 或合并函数,否则此模式下的 Nulls 将有效地从比较中排除空值记录。如果您希望空值作为值参与直接值比较,请使用 SET ANSI_NULLS OFF
。 docs.microsoft.com/en-us/sql/t-sql/statements/….
使用 SET ANSI_NULLS OFF 时,NullVsNull 案例被认为是匹配的,我认为它不应该是,相反,如果我忘记设置 ANSI_NULLS OFF,我总是会得到一个空列表,我认为更危险。在我看来,带有 NOT EXISTS 子句的查询仍然是最精确的 null 证明,无论 ANSI_NULLS 是什么。那么为什么微软没有提到这个查询呢?也许 U-SQL 引擎使用 NOT IN 子句反映查询?我想试试..
我并不是说 ANSI_NULLS OFF 是一个很好的解决方案,但如果涉及空值,则它可以工作,使用 NOT EXISTS 和/或 IS NOT NULL,或者 COALESCE
可能为空的值(或ISNULL
)到任意值以进行比较,您只问为什么会有差异,这在文档中没有特别承认,因为它推断出对于MS SQL Server的JOIN和比较逻辑,我们中的许多人专门围绕这种类型的评估行为构建查询。 (当我需要与预期的空值进行比较时,我通常IsNull(expr, -1)
)
这不是错误或错误,这是设计使然。以下文章解释:sqlbadpractices.com/using-not-in-operator-with-null-values
这能回答你的问题吗? NOT IN vs NOT EXISTS
【参考方案1】:
首先,这里有一个 Fiddle 可以帮助您解决所有这些问题:
http://sqlfiddle.com/#!18/aa477/20
现在我们可以谈谈发生了什么。这里的答案从正确理解NULL
的含义开始。
人们常常把NULL
理解为“这个值是空的”的意思,但这种理解是不完整的。最好将NULL
理解为“我不知道这个值是什么”。
根据前一种(空)理解,比较两个NULL
值(empty == empty
)可以合理地看出产生true
。但是随着对NULL
的新的正确(我不知道)理解,我们可以看到假设一个我不知道 将等于另一个我不知道 是没有意义的。这种比较的正确答案显然仍然是我不知道。因此NULL == NULL
仍然是...NULL
。此外,NULL
在强制为布尔表达式时是falsy。
这是 ANSI 标准定义的内容,您应该始终坚持的内容,也是理解 NULL
如何与任何数学/关系代数连接概念正式交互的正确方法。
如果我真的想正式表达这种连接类型,我会使用NOT EXISTS()
变体。你也可以这样写:
SELECT @A.*
FROM @A
LEFT JOIN @B ON OnA = OnB
WHERE @B.OnB IS NULL
我们有时将其称为排除连接,但NOT EXISTS()
通常也比LEFT JOIN
执行得更快。
同样,对于所有这些选项,理解什么是正确的仍然取决于对NULL
比较的正确理解。由于A3
行中的NULL
不知道与B3
行中的NULL
具有相同的值,因此应该包含该记录。
但是,(这是重要),所有这些都在理论摘要中。在实际的具体世界中,您的应用程序的正确结果取决于值的含义。有时,特定系统、应用程序、开发人员或数据模型将决定 NULL
确实只是为此目的表示“空”。这是明智的,还是形式上正确的,都无关紧要。相关的是了解您正在查看的数据的含义,并生成能够回答您的要求所提出问题的查询结果。
这个查询是否显示Sql Server的错误或bug仍然存在问题:
SELECT * FROM @A
WHERE EXISTS (SELECT 1 FROM @B WHERE OnA = OnB)
启用ANSI_NULLS
(默认设置)后,一切正常。我们不知道NULL
的值是否相同,因此不应包含该记录。但是,当ANSI_NULLS
关闭时,我们的结果相同,即使我们希望NULL = NULL
比较包含该行。你可能对这里的一个错误是正确的,尽管比我聪明的人可能会解释为什么它不是。但如果是这样,我真的不介意。在我看来,如果你关闭 ANSI_NULLS,你已经是在玩火,应该遇到这种事情。
【讨论】:
这已经很清楚了,但我感谢您的规范。你如何回答第 6 题? @jangix 添加了一堆东西。【参考方案2】:你已经明白为什么会这样了。我在下面的链接中找到了一篇好文章。请检查一下。
微软可能没有犯错。根据以下文章,如果您将 ansi_nulls
设置为关闭,Microsoft SQL Server 处理 null 的方式与 ANSI 标准不同。
在查询前使用下面的行
set ansi_nulls off
尽管列表中有 null,not in()
将返回您想要的结果。
进一步阅读:
https://www.sqlservercentral.com/articles/four-rules-for-nulls
在提供的 Microsoft 文档中,他们还提到:
考虑使用 SEMIJOIN (U-SQL) 来处理需要使用 SQL 中带有 IN 的子查询。考虑使用 ANTISEMIJOIN (U-SQL) 在 SQL 中使用 NOT IN 的子查询的情况
【讨论】:
链接仅回答本网站质量差的问题,可能会被删除。您需要引用链接的相关部分,以便在链接断开的情况下得到答案。 @DaleK 我已删除屏幕截图。由于我只是漫游了几个月,我可以知道什么时候使用图像来回答吗? 微软的错误可能没有提到 SET ANSI_NULLS OFF 这个条件。我会说这不是一个小错误...... @KaziMohammadAliNur 关于屏幕显示唯一合适的情况是如果您正在构建用户界面并且需要显示它的外观。 (或者例如显示 s-s-rS 报告的呈现)。其他任何内容都应该是适当格式的文本。 @jangix - 你应该忘记SET ANSI_NULLS OFF
的存在。实施这是 > 20 年前犯的一个错误。只关注标准 SQL,是的,NOT IN
与 NOT EXISTS
的行为在存在空值时是不同的。至于哪一个(如果有的话)反映了我不知道的 USQL 行为以上是关于SQL左反半连接等效查询的主要内容,如果未能解决你的问题,请参考以下文章
如何将任何给定的 SQL/HQL 选择查询动态转换为等效计数查询?