在不支持它们的 SQL 实现(例如 Microsoft SQL Server 2008R2)中,如何重写包含标准IS DISTINCT FROM
和运算符的表达式?IS NOT DISTINCT FROM
9 回答
谓词作为 SQL:1999的IS DISTINCT FROM
特征 T151 被引入,它的可读否定IS NOT DISTINCT FROM
被添加为 SQL:2003 的特征 T152。这些谓词的目的是保证比较两个值的结果是True或False,绝不是Unknown。
这些谓词适用于任何可比较的类型(包括行、数组和多集),因此精确模拟它们相当复杂。但是,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
评估为Unknown。例如,NULL IS DISTINCT FROM NULL
应该评估为False。同样,1 IS NOT DISTINCT FROM NULL
应该评估为False。在这两种情况下,您的表达式都会产生Unknown。
我喜欢的另一个解决方案是利用 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 中 Col1、Col2 或 Col3 的值与 TableA 不同的行,可以使用以下命令:
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 更详细地解释了这种解决方法:https ://sql.kiwi/2011/06/undocumented-query-plans-equality-comparisons.html
如果您的 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)
只是为了扩展约翰凯勒的答案。我更喜欢使用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);
重写 IS DISTINCT FROM 和 IS NOT DISTINCT FROM 的一个警告是不要干扰索引的使用,至少在使用 SQL Server 时是这样。换句话说,当使用以下内容时:
WHERE COALESCE(@input, x) = COALESCE(column, x)
SQL Server 将无法使用任何包含column的索引。因此,在 WHERE 子句中,最好使用以下形式
WHERE @input = column OR (@input IS NULL AND column IS NULL)
利用column的任何索引。(括号仅用于清楚起见)
拼写出来使用CASE
作为参考,最规范(和可读)的实现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 的, using INTERSECT
)更简洁。
DECODE
如果可用,请使用
我知道这个问题是关于 SQL Server 的,但为了完整起见,Db2 和 Oracle 支持一个DECODE()
功能,在这种情况下可以模拟以下内容:
-- a IS DISTINCT FROM b
DECODE(a, b, 1, 0) = 0
-- a IS NOT DISTINCT FROM b
DECODE(a, b, 1, 0) = 1
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)
这些表达式可以很好地替代 IS DISTINCT FROM 逻辑,并且比前面的示例执行得更好,因为它们最终被 SQL Server 编译成单个谓词表达式,这将导致大约。过滤器表达式的运算符成本的一半。它们本质上与 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
这是一个老问题,有一个新的答案。它更容易理解和维护。
-- 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 数据库(见最后的链接)。此处详细说明了此方法和替代方法