如何在SQL实现中重写包含标准IS DISTINCT FROM
和IS NOT DISTINCT FROM
运算符的表达式,例如不支持它们的Microsoft SQL Server 2008R2?
答案 0 :(得分:41)
IS DISTINCT FROM
谓词是作为SQL:1999的特征T151引入的,其可读否定IS NOT DISTINCT FROM
被添加为SQL:2003的特征T152。这些谓词的目的是保证比较两个值的结果是 True 或 False ,从不未知。
这些谓词适用于任何类似的类型(包括行,数组和多集),使得完全模拟它们相当复杂。但是,SQL Server不支持大多数这些类型,因此我们可以通过检查空参数/操作数来实现目标:
a IS DISTINCT FROM b
可以改写为:
((a <> b OR a IS NULL OR b IS NULL) AND NOT (a IS NULL AND b IS NULL))
a IS NOT DISTINCT FROM b
可以改写为:
(NOT (a <> b OR a IS NULL OR b IS NULL) OR (a IS NULL AND b IS NULL))
您自己的答案不正确,因为它没有考虑FALSE OR NULL
评估为未知。例如,NULL IS DISTINCT FROM NULL
应评估为 False 。同样,1 IS NOT DISTINCT FROM NULL
应评估为 False 。在这两种情况下,表达式都会产生未知。
答案 1 :(得分:24)
我喜欢的另一个解决方案是利用EXISTS与INTERSECT结合的真正的双值布尔结果。此解决方案应在SQL Server 2005 +中起作用。
a IS NOT DISTINCT FROM b
可以写成:
EXISTS(SELECT a INTERSECT SELECT b)
如文档所述,INTERSECT将两个NULL值视为相等,因此如果两者都为NULL,则INTERSECT会产生一行,因此EXISTS会产生true。
a IS DISTINCT FROM b
可以写成:
NOT EXISTS(SELECT a INTERSECT SELECT b)
如果您需要在两个表中比较多个可空列,则此方法更简洁。例如,要返回TableB中具有与TableA不同的Col1,Col2或Col3值的行,可以使用以下内容:
SELECT *
FROM TableA A
INNER JOIN TableB B ON A.PK = B.PK
WHERE NOT EXISTS(
SELECT A.Col1, A.Col2, A.Col3
INTERSECT
SELECT B.Col1, B.Col2, B.Col3);
Paul White更详细地解释了这种解决方法: http://web.archive.org/web/20180422151947/http://sqlblog.com:80/blogs/paul_white/archive/2011/06/22/undocumented-query-plans-equality-comparisons.aspx
答案 2 :(得分:11)
如果您的SQL实现没有实现SQL标准IS DISTINCT FROM
和IS NOT DISTINCT FROM
运算符,您可以使用以下等效项重写包含它们的表达式:
一般来说:
a IS DISTINCT FROM b <==>
(
((a) IS NULL AND (b) IS NOT NULL)
OR
((a) IS NOT NULL AND (b) IS NULL)
OR
((a) <> (b))
)
a IS NOT DISTINCT FROM b <==>
(
((a) IS NULL AND (b) IS NULL)
OR
((a) = (b))
)
在UNKNOWN和FALSE之间存在差异的情况下使用时,此答案不正确。不过,我认为这种情况并不常见。请参阅@ChrisBandy接受的答案。
如果可以识别出实际上未在数据中出现的占位符值,则COALESCE
是另一种选择:
a IS DISTINCT FROM b <==> COALESCE(a, placeholder) <> COALESCE(b, placeholder)
a IS NOT DISTINCT FROM b <==> COALESCE(a, placeholder) = COALESCE(b, placeholder)
答案 3 :(得分:5)
重写IS DISTINCT FROM和IS NOT DISTINCT FROM的一个警告是不干扰使用索引,至少在使用SQL Server时是这样。换句话说,使用以下内容时:
WHERE COALESCE(@input, x) = COALESCE(column, x)
SQL Server将无法使用包含列的任何索引。因此,在WHERE子句中,最好使用表单
WHERE @input = column OR (@input IS NULL AND column IS NULL)
利用列的所有索引。 (Parens仅用于清晰)
答案 4 :(得分:1)
作为参考,IS [ NOT ] DISTINCT FROM
的最规范(和可读)实现将是格式良好的CASE
表达式。对于IS DISTINCT FROM
:
CASE WHEN [a] IS NULL AND [b] IS NULL THEN FALSE
WHEN [a] IS NULL AND [b] IS NOT NULL THEN TRUE
WHEN [a] IS NOT NULL AND [b] IS NULL THEN TRUE
WHEN [a] = [b] THEN FALSE
ELSE TRUE
END
显然,其他解决方案(特别是John Keller's,使用INTERSECT
)更简洁。
答案 5 :(得分:1)
只是为了扩展John Keller's回答。我更喜欢使用EXISTS
和EXCEPT
模式:
a IS DISTINCT FROM b
<=>
EXISTS (SELECT a EXCEPT SELECT b)
-- NOT EXISTS (SELECT a INTERSECT SELECT b)
和
a IS NOT DISTINCT FROM b
<=>
NOT EXISTS (SELECT a EXCEPT SELECT b)
-- EXISTS (SELECT a INTERSECT SELECT b)
出于某个特殊原因。 NOT
已对齐,而INTERSECT
则被反转。
SELECT 1 AS PK, 21 AS c, NULL AS b
INTO tab1;
SELECT 1 AS PK, 21 AS c, 2 AS b
INTO tab2;
SELECT *
FROM tab1 A
JOIN tab2 B ON A.PK = B.PK
WHERE EXISTS(SELECT A.c, A.B
EXCEPT
SELECT B.c, B.b);
<强> DBFiddle Demo 强>
答案 6 :(得分:0)
这些表达式可以很好地替代IS DISTINCT FROM逻辑,并且比前面的示例表现更好,因为它们最终被SQL服务器编译成单个谓词表达式,这将导致大约。过滤器表达式的运算符成本的一半。它们与Chris Bandy提供的解决方案基本相同,但是它们使用嵌套的ISNULL和NULLIF函数来执行底层比较。
(......如果您愿意,显然ISNULL可以用COALESCE代替)
a IS DISTINCT FROM b
可以改写为:
ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NOT NULL
a IS NOT DISTINCT FROM b
可以改写为:
ISNULL(NULLIF(a, b), NULLIF(b, a)) IS NULL
答案 7 :(得分:0)
a IS NOT DISTINCT FROM b
可以改写为:
(a IS NOT NULL AND b IS NOT NULL AND a=b) OR (a IS NULL AND b is NULL)
a IS DISTINCT FROM b
可以改写为:
NOT (a IS NOT DISTINCT FROM b)
答案 8 :(得分:0)
这是一个老问题,并且有一个新答案。易于理解和维护。
-- a IS DISTINCT FROM b
CASE WHEN (a = b) OR (a IS NULL AND b IS NULL) THEN 1 ELSE 0 END = 0
-- a IS NOT DISTINCT FROM b
CASE WHEN (a = b) OR (a IS NULL AND b IS NULL) THEN 1 ELSE 0 END = 1
应注意,IS [NOT] DISTINCT FROM
的此语法替代方法在所有主要的SQL数据库中均有效(请参见最后的链接)。 here