【问题标题】:SQL: field = other_field returns false even if they are identical (NULL values)SQL: field = other_field 返回 false,即使它们相同(NULL 值)
【发布时间】:2013-12-12 19:18:02
【问题描述】:

我有一个困难,因为在比较子查询中的两个字段时,虽然字段相同,即它们都有 NULL 值,但比较返回 FALSE 结果

因此 NULL = NULL 返回 FALSE

现在我知道 NULL 应该与 IS 运算符进行比较,但是当我比较两个字段时,我应该如何知道它们包含空值?如果值是否为 NULL,我需要比较相同数据的两个字段。

考虑这个 SQL:

SELECT 
*
FROM
fts.fts_customers_data_50360001
WHERE 
    fts.fts_customers_data_50360001.record_type = 15
    AND
    fts.fts_customers_data_50360001.mid = 103650360001
    AND NOT EXISTS
    (
        SELECT 
            fts.temp_fees_50360001.record_type
        FROM 
            fts.temp_fees_50360001
        WHERE 
            fts.temp_fees_50360001.record_type      = fts.fts_customers_data_50360001.record_type    
            AND 
            fts.temp_fees_50360001.merch_id         = fts.fts_customers_data_50360001.mid
            AND 
            fts.temp_fees_50360001.fee_curr         = fts.fts_customers_data_50360001.currency
            AND 
            fts.temp_fees_50360001.card_scheme      = fts.fts_customers_data_50360001.card_scheme
            AND 
            fts.temp_fees_50360001.tran_type        = fts.fts_customers_data_50360001.fee_type
            AND 
            fts.temp_fees_50360001.area             = fts.fts_customers_data_50360001.region
            AND 
            fts.temp_fees_50360001.srvc_type        = fts.fts_customers_data_50360001.card_type
    );

在上面的查询中,

fts.temp_fees_50360001.card_scheme = fts.fts_customers_data_50360001.card_scheme

两者都有 NULL 值,但比较返回 false .. 太糟糕了

任何想法都会受到赞赏

【问题讨论】:

  • “NULL = NULL 返回 FALSE”不正确。 NULL = NULL 返回 UNKNOWN - SQL 三值逻辑中的 third 真值。要查看您的断言不正确,请尝试NOT (NULL = NULL),您会发现它仍然不匹配。
  • 如果您需要比较 7 个数据字段,您的数据库似乎未标准化。

标签: sql postgresql null comparison


【解决方案1】:

正如其他人指出的那样,NULL 无法与NULL 进行比较。

在 Postgres 中,您可以使用运算符 IS DISTINCT FROM 来缩短表达式,它是 <> 的空安全替代。在您的情况下,您需要使用 IS NOT DISTINCT FROM 来比较相等性(看起来有点错误,但不幸的是 SQL 标准中没有定义相应的 IS EQUAL TO)。

From the manual:

当任一输入为空时,普通比较运算符产生空值(表示“未知”),而不是真或假。例如,7 = NULL 产生 null,7 NULL 也是如此。当这种行为不合适时,使用 IS [ NOT ] DISTINCT FROM 结构:

所以,而不是

(fts.temp_fees_50360001.record_type = fts.fts_customers_data_50360001.record_type
 OR (fts.temp_fees_50360001.record_type IS NULL 
     AND fts.fts_customers_data_50360001.record_type IS NULL)
)

你可以使用:

(fts.temp_fees_50360001.record_type IS NOT DISTINCT FROM  fts.fts_customers_data_50360001.record_type)

自动处理 NULL 值。如果你想比较是否相等,这个条件看起来有点奇怪,但它仍然很短。

【讨论】:

  • +1 PostgreSQL 的好方法,可能是最好的方法
  • @RomanPekar: IS DISTINCT FROM 实际上是 ANSI SQL(所以不是真正的“Postgres 特定的”)——尽管我不知道任何其他实现它的 DBMS。
  • 谢谢,我一直都知道 PostgreSQL 是最接近实现 ANSI SQL 的所有功能的一个 :) 我几乎 100% 的时间都在使用 SQL Server,很难记住所有这些差异..
【解决方案2】:

首先,为您的表使用别名,您的查询将更具可读性:

select *
from fts.fts_customers_data_50360001 as d
where
    d.record_type = 15 and
    d.mid = 103650360001 and
    not exists 
    (
        select *
        from fts.temp_fees_50360001 as f
        where
            f.record_type = d.record_type and 
            f.merch_id = d.mid and
            f.fee_curr = d.currency and
            f.card_scheme = d.card_scheme and
            f.tran_type = d.fee_type and
            f.area = d.region and
            f.srvc_type = d.card_type
    )

至于你的问题,有几种方法可以做到这一点,例如,你可以使用如下语法:

...
(
    f.card_scheme is null and d.card_scheme is null or
    f.card_scheme = d.card_scheme
)
...

或者使用 coalesce 与一些无法存储在您的列中的值:

...
coalesce(f.card_scheme, -1) = coalesce(d.card_scheme, -1)
...

最近我也喜欢使用exists 和intersect 进行此类比较:

...
exists (select f.card_scheme, f.tran_type intersect select d.card_scheme, d.tran_type)
...

只是附注 - 在编写这样的查询时必须小心,并检查查询计划以确保使用了您的索引。

【讨论】:

    【解决方案3】:

    在 SQL 中,null 永远不会等于 null。获得与 null 比较的真实结果的唯一方法是通过特殊测试:

    IS NULL
    IS NOT NULL
    

    在您的情况下,您必须专门针对被视为相等的“两个空值”情况:

    AND (fts.temp_fees_50360001.card_scheme = fts.fts_customers_data_50360001.card_scheme
      OR (fts.temp_fees_50360001.card_scheme IS NULL
        AND fts.fts_customers_data_50360001.card_scheme IS NULL)
    )
    

    没有办法解决它(尽管有一些变化)。

    【讨论】:

      【解决方案4】:

      以下内部 SELECT 有效(但我不保证性能):

          SELECT 
              fts.temp_fees_50360001.record_type
          FROM 
              fts.temp_fees_50360001
          WHERE 
              (fts.temp_fees_50360001.record_type      = fts.fts_customers_data_50360001.record_type
              OR (fts.temp_fees_50360001.record_type IS NULL AND fts.fts_customers_data_50360001.record_type IS NULL))
              AND 
              (fts.temp_fees_50360001.merch_id         = fts.fts_customers_data_50360001.mid
              OR (fts.temp_fees_50360001.merch_id IS NULL AND fts.fts_customers_data_50360001.mid IS NULL))
              AND 
              (fts.temp_fees_50360001.fee_curr         = fts.fts_customers_data_50360001.currency
              OR (fts.temp_fees_50360001.fee_curr IS NULL AND fts.fts_customers_data_50360001.currency IS NULL))
              AND 
              (fts.temp_fees_50360001.card_scheme      = fts.fts_customers_data_50360001.card_scheme
              OR (fts.temp_fees_50360001.card_scheme IS NULL AND fts.fts_customers_data_50360001.card_scheme IS NULL))
              AND 
              (fts.temp_fees_50360001.tran_type        = fts.fts_customers_data_50360001.fee_type
              OR (fts.temp_fees_50360001.tran_type IS NULL AND fts.fts_customers_data_50360001.fee_type IS NULL))
              AND 
              (fts.temp_fees_50360001.area             = fts.fts_customers_data_50360001.region
              OR (fts.temp_fees_50360001.area IS NULL AND fts.fts_customers_data_50360001.region IS NULL))
              AND 
              (fts.temp_fees_50360001.srvc_type        = fts.fts_customers_data_50360001.card_type 
              OR (fts.temp_fees_50360001.srvc_type IS NULL AND fts.fts_customers_data_50360001.card_type))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-08
        • 1970-01-01
        • 1970-01-01
        • 2019-12-04
        • 2013-02-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多