【问题标题】:How to rewrite IS DISTINCT FROM and IS NOT DISTINCT FROM in SQL Server 20008R2?如何重写 IS DISTINCT FROM 和 IS NOT DISTINCT FROM?
【发布时间】:2012-05-12 02:33:52
【问题描述】:

如何在不支持它们的 SQL 实现(例如 Microsoft SQL Server 2008R2)中重写包含标准 IS DISTINCT FROMIS NOT DISTINCT FROM 运算符的表达式?

【问题讨论】:

    标签: sql sql-server tsql sql-server-2008-r2 ansi-sql


    【解决方案1】:

    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 b OR a IS NULL OR b IS NULL) AND NOT (a IS NULL AND b IS NULL)):我们不能只是 ab 或 a 为空 xor b 是空
    • 为什么我们不能将a IS NOT DISTINCT FROM b 重写为a = b OR a IS NULL and b IS NULL?这样看起来更简洁。
    • @Rudey 因为当只有一个操作数为 null 时,a = b 的计算结果为 null,这会导致整个表达式的计算结果为 null。
    • IS DISTINCT FROM在sql server 2019中实现了吗? sql server 2017 中没有实现。
    • @costa 不,SQL Server 2019 仍然不支持IS DISTINCT FROM - 也没有任何 方法来简洁地执行 NULL 安全比较(使用集合操作除外)在谓词中,这对于标量比较是不切实际的)。
    【解决方案2】:

    我喜欢的另一个解决方案是利用 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

    【讨论】:

    • 这应该是公认的答案,因为它以一种不会重复引用ab 的方式重写谓词。对于非确定性表达式ab,或具有副作用(如日志记录)的表达式,这将非常有用。您的第二个示例还模拟了 (A.Col1, A.Col2, A.Col3) IS DISTINCT FROM (B.Col1, B.Col2, B.Col3),它仅由 PostgreSQL 本地支持(据我所知)。有时是一个非常有用的谓词。
    • exists(...intersect...) 点赞。当 a 和 b 是长表达式时很有用。
    • 我修改了一些查询,这些查询的谓词与此处的其他答案相似。我得出的结论是,使用INTERSECT 可以加快查询速度。感谢分享!
    【解决方案3】:

    如果您的 SQL 实现未实现 SQL 标准 IS DISTINCT FROMIS 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)
    

    【讨论】:

    • 这是一个错误的答案。请参阅 Chris 回答的最后一段。
    • 是的,在 UNKNOWN 和 FALSE 之间的区别很重要的上下文中使用此答案是不正确的。不过,我认为这并不常见。
    • @JasonKresowaty:这并不罕见。在像(a IS DISTINCT FROM b) AND something 这样的任何谓词中,UNKNOWNFALSE 之间的区别是必不可少的。如果ab 都是NULL,那么无论somethingTRUE 还是FALSE,您的仿真都会生成NULL
    • 您也可以使用coalesce(a = b, a is null and b is null) 来测试它们是否相同,因此(a IS NOT DISTINCT FROM b)
    • 由于某种原因,这个答案中的第一种方法(使用逻辑)比我(SQL Server)的第二种方法(使用函数)慢一个数量级。
    【解决方案4】:

    只是为了扩展John Keller's 答案。我更喜欢使用EXISTSEXCEPT 模式:

    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

    【讨论】:

      【解决方案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)
      

      利用的任何索引。 (括号只是为了清楚起见)

      【讨论】:

      • +1 用于提及函数如何扼杀索引的使用。这就是我最初来到这里的原因。
      【解决方案6】:
      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)
      

      【讨论】:

        【解决方案7】:

        使用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)更简洁。

        More details here.

        使用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
        

        【讨论】:

          【解决方案8】:

          这些表达式可以很好地替代 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

          【讨论】:

            【解决方案9】:

            这是一个老问题,现在有一个新答案。更容易理解和维护。

            -- 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

            【讨论】:

              猜你喜欢
              • 2014-02-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-01-31
              • 2019-03-11
              • 1970-01-01
              相关资源
              最近更新 更多