【发布时间】:2012-05-12 02:33:52
【问题描述】:
如何在不支持它们的 SQL 实现(例如 Microsoft SQL Server 2008R2)中重写包含标准 IS DISTINCT FROM 和 IS NOT DISTINCT FROM 运算符的表达式?
【问题讨论】:
标签: sql sql-server tsql sql-server-2008-r2 ansi-sql
如何在不支持它们的 SQL 实现(例如 Microsoft SQL Server 2008R2)中重写包含标准 IS DISTINCT FROM 和 IS NOT DISTINCT FROM 运算符的表达式?
【问题讨论】:
标签: sql sql-server tsql sql-server-2008-r2 ansi-sql
IS DISTINCT FROM 谓词作为 SQL:1999 的功能 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 的计算结果为未知。例如,NULL IS DISTINCT FROM NULL 的计算结果应为 False。同样,1 IS NOT DISTINCT FROM NULL 的计算结果应为 False。在这两种情况下,您的表达式都会产生 Unknown。
【讨论】:
a IS NOT DISTINCT FROM b 重写为a = b OR a IS NULL and b IS NULL?这样看起来更简洁。
a = b 的计算结果为 null,这会导致整个表达式的计算结果为 null。
IS DISTINCT FROM在sql server 2019中实现了吗? sql server 2017 中没有实现。
IS DISTINCT FROM - 也没有任何 方法来简洁地执行 NULL 安全比较(使用集合操作除外)在谓词中,这对于标量比较是不切实际的)。
我喜欢的另一个解决方案是利用 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
【讨论】:
a 和b 的方式重写谓词。对于非确定性表达式a 和b,或具有副作用(如日志记录)的表达式,这将非常有用。您的第二个示例还模拟了 (A.Col1, A.Col2, A.Col3) IS DISTINCT FROM (B.Col1, B.Col2, B.Col3),它仅由 PostgreSQL 本地支持(据我所知)。有时是一个非常有用的谓词。
exists(...intersect...) 点赞。当 a 和 b 是长表达式时很有用。
INTERSECT 可以加快查询速度。感谢分享!
如果您的 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)
【讨论】:
(a IS DISTINCT FROM b) AND something 这样的任何谓词中,UNKNOWN 和FALSE 之间的区别是必不可少的。如果a 和b 都是NULL,那么无论something 是TRUE 还是FALSE,您的仿真都会生成NULL。
coalesce(a = b, a is null and b is null) 来测试它们是否相同,因此(a IS NOT DISTINCT FROM b)
只是为了扩展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);
【讨论】:
重写 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)
利用列的任何索引。 (括号只是为了清楚起见)
【讨论】:
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)
【讨论】:
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's,使用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
【讨论】:
这些表达式可以很好地替代 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 数据库(请参阅最后的链接)。这个和替代品都详细解释了here
【讨论】: