【问题标题】:SQL "Join" on null values空值上的 SQL“加入”
【发布时间】:2009-11-18 19:22:17
【问题描述】:

由于我无法控制的原因,我需要连接两个表并且我需要空值来匹配。我能想到的最佳选择是吐出一个 UUID 并将其用作我的比较值,但它看起来很难看

SELECT * FROM T1 JOIN T2 ON nvl(T1.SOMECOL,'f44087d5935dccbda23f71f3e9beb491') = 
   nvl(T2.SOMECOL,'f44087d5935dccbda23f71f3e9beb491')

我怎样才能做得更好?如果重要,这在 Oracle 上,上下文是一个应用程序,其中必须将一批用户上传的数据与一批现有数据进行比较,以查看是否有任何行匹配。回想起来,我们应该阻止任一数据集中的任何连接列包含空值,但我们没有,现在我们不得不忍受它。

编辑:需要明确的是,我关心空值。如果列不为空,我希望它们匹配它们的实际值。

【问题讨论】:

  • 表是否连接在空值上?如果是这样的话,你会得到一个相当大的结果集。例如,如果 T1 中有 10 个空值,T2 中有 10 个空值,那么您将获得 100 行。当然,您可以加入另一个填充字段,然后选择 ... 其中 T1.SomeCol 为空而 T2.SomeCol 为空
  • 用户函数在大型查询中使用时会对性能产生巨大影响,也许您可​​以通过使用持久计算字段来改善这一点。我不确定,只是想。
  • 您的解决方案有什么问题?除了 UUID,您可以使用列中不存在的任何字符串,例如字符串“null”,以减少内存占用。
  • 当你在 cmets 中找到真正的答案...

标签: sql oracle null


【解决方案1】:

也许这会起作用,但我从未真正尝试过:

SELECT * 
FROM T1 JOIN T2 
ON T1.SOMECOL = T2.SOMECOL OR (T1.SOMECOL IS NULL AND T2.SOMECOL IS NULL)

【讨论】:

  • 我也没有尝试过,但我的大脑一直在坚持你需要一个 CROSS JOIN 而不是 JOIN?不过,我不相信它,它整天都在阅读丑陋的 Java 代码,而且已经精疲力尽了。
  • @R. Bemrose - 如果您有多个空值,则应该只存在交叉连接类型问题。如果只有一个空值,它应该可以正常工作。但是话又说回来,如果您有多个 null,则实际上没有解决方案,因为无法确定哪个 null 与哪个其他 null 匹配。
  • 比Dan提出的nvl方案慢很多,尤其是行数增加的时候。
  • 我也遇到了同样的问题。这个解决方案就像一个魅力。谢谢@EricPetroelje
  • 这是我在 Stack 上找到的最佳解决方案,感谢您的帮助。
【解决方案2】:

在我使用过的 SQL Server 中:

WHERE (a.col = b.col OR COALESCE(a.col, b.col) IS NULL)

显然效率不高,因为 OR,但除非有保留值,否则您可以将 NULL 映射到两侧而不会产生歧义或折叠,这是您可以做的最好的(如果有,为什么在您的设计...)

【讨论】:

  • 这很好,但 isnull 仅在比较两个值时更快。
【解决方案3】:

在 oracle 中,您可以使用 decode 连接空值:

    SELECT * FROM T1 JOIN T2 ON DECODE(T1.SOMECOL, T2.SOMECOL, 1, 0) = 1

decode 将空值视为相等,因此这在没有“神奇”数字的情况下有效。两列必须具有相同的数据类型。

它不会写出最易读的代码,但可能还是比t1.id = t2.id or (t1.id is null and t2.id is null)

【讨论】:

    【解决方案4】:

    您不能做得更好,但是您拥有的 JOIN 不会以任何方式执行实际的“JOIN”(T1.SOMECOL 和 T2.SOMECOL 之间没有任何相关性,除了它们都有 NULL该列的值)。基本上这意味着您将无法在 NULL 上使用 JOIN 来查看行是否匹配。

    NULL 永远不会等于另一个 NULL。未知价值的东西怎么可能等于其他未知价值的东西?

    【讨论】:

    • 从概念上我知道它不能。该应用程序的这一特殊功能设计得很愚蠢。基本上我们要求该列的值在数据集中是唯一的。这包括允许有一行(但只有一个)具有空值。如果新数据进来并且该列也有一个空值,我们希望它匹配。
    • 不是概念上的。实际上。如果现有数据的键列为空,而新数据的键列为空,则它们可能+或可能不+匹配。 IOW,给定一个具有 NULL 键值的现有行和 100 个新行,每个新行具有一个 NULL 键值,哪一个与现有行匹配?在您的数据试图使用它的意义上,没有“唯一的 NULL 值”之类的东西。或者,更准确地说,all NULL 是唯一的;事实上,没有一个 NULL 匹配另一个。
    【解决方案5】:

    对于此类任务,Oracle 内部使用了一个未记录的函数 sys_op_map_nonnull(),您的查询将变为:

    SELECT *
    FROM T1 JOIN T2 ON sys_op_map_nonnull(T1.SOMECOL) = sys_op_map_nonnull(T2.SOMECOL)
    

    无证,所以如果你走这条路要小心。

    【讨论】:

      【解决方案6】:

      简单,利用COALESCE,它将返回其第一个非空参数:

      SELECT * FROM T1 JOIN T2 ON 
        COALESCE(T1.Field, 'magic string') = 
           COALESCE(T2.Field, 'magic string')
      

      您唯一需要担心的是“魔术字符串”不能包含在任一表中连接字段的合法值中。

      【讨论】:

      • 这种情况下COALESCENVL有什么区别?
      • 我很抱歉——我正在回答一个 MySQL 问题,并且很可能在搜索之前是否已回答后将其发布到错误的位置。您的问题显然已有一年多的历史并已得到解答。我也应该多加注意,因为 MySQL 中的查询配置文件显示COALESCE 阻止在Field 上使用任何索引——您标记为正确的答案没有这种副作用。但这并不是什么大问题,因为这个特定示例中的连接最终会扫描两个表
      【解决方案7】:

      如果值为空,您真的希望能够加入表吗?你不能只排除连接谓词中可能的空值吗?我发现很难理解两个表中的行可以通过空值相关联。如果您在 table1.col_a 中有 100 个空值,在 table2.col_b 中有 100 个空值,那么您将仅针对具有空值的行返回 10000 行。听起来不正确。

      但是,您确实说过您需要它。我可以建议将空列合并成一个较小的字符串,因为字符比较相对昂贵。更好的是,如果列中的数据将是文本,则将空值合并为一个整数。这样您就可以快速进行“比较”,并且不太可能与现有数据发生冲突。

      【讨论】:

      • 我迟到了,但我找到了一个正当的理由:加入user_segmentsuser_objects。如果表已分区,您必须还将partition_name 连接到subobject_name。但是,在非分区表上,这些值为 null。
      【解决方案8】:

      您可以尝试使用以下查询。

      SELECT *
      FROM TABLEA TA
      JOIN TABLEB TB ON NVL(TA.COL1,0)=NVL(TB.COL2,0);
      

      【讨论】:

        【解决方案9】:

        把它扔出去——有没有办法可以将这些空值合并为一个已知值,比如一个空字符串?不太了解您的表格的布局方式意味着我无法确定您是否会以这种方式失去意义——即,有一个空字符串表示“用户拒绝输入电话号码”,而 NULL 表示“我们忘记了问一下”,或者类似的东西?

        我敢肯定,这很可能是不可能的,但如果是的话,您将有已知的值进行比较,并且您可以通过这种方式获得合法的加入。

        【讨论】:

          【解决方案10】:

          这和检查两列中是否存在空值不一样吗?

          SELECT * FROM T1, T2 WHERE T1.SOMECOL IS NULL and T2.SOMECOL IS NULL
          

          SELECT * FROM T1 CROSS JOIN T2 WHERE T1.SOMECOL IS NULL and T2.SOMECOL IS NULL
          

          【讨论】:

          • sql 将 NULL 与“IS”关键字进行比较,而不是 '=='。事实上,它没有在任何地方使用 '==',只是简单的 '='。
          • 你是对的。已编辑。但问题仍然存在。 OP 要求的不是有效地检查两列中的空值吗?如果您还需要加入其他值,则可以执行 (T1.SOMECOL IS NULL AND T2.SOMECOL IS NULL) OR (T1.SOMECOL = T2.SOMECOL)。
          • 我发现这个答案在编辑后没有理由得到 -1,所以 +1。
          【解决方案11】:

          为什么不这样:

          SELECT * FROM T1 JOIN T2 ON nvl(T1.SOMECOL,'null') = nvl(T2.SOMECOL,'null')

          我不知道你为什么使用 UUID。您可以使用列中不存在的任何字符串,例如字符串“null”,以减少内存占用。例如,使用nvl 的解决方案比 Eric Petroelje 提出的使用or ... is null 的解决方案要快得多。

          【讨论】:

            【解决方案12】:

            我相信你仍然可以使用 nvl() 来加入:

            SELECT *
            FROM T1
            JOIN T2 ON NVL(T2.COL1,-1)=NVL(T1.COL1,-1);
            

            但您需要在 col1 列上添加基于函数的索引

            CREATE INDEX IND_1 ON T1 (NVL(COL1,-1));
            CREATE INDEX IND_2 ON T2 (NVL(COL1,-1));
            

            索引应该会显着提高 NVL(..) 上的连接速度。

            【讨论】:

              【解决方案13】:

              @Sarath Avanavu

              这不是最好的方法。如果 TA.COL1 保持值 0 并且 TB.COL2 为 NULL,它将加入这些记录,这是不正确的。

              SELECT *
              FROM TABLEA TA
              JOIN TABLEB TB ON NVL(TA.COL1,0)=NVL(TB.COL2,0);
              

              【讨论】:

                【解决方案14】:

                也可以用CASE替换子查询中的空值,然后JOIN结果:

                SELECT T1.COL1 FROM
                (
                   (SELECT (CASE WHEN COL1 IS NULL THEN 'X' ELSE COL1 END) AS COL1 FROM TABLE1) T1
                   JOIN
                   (SELECT (CASE WHEN COL1 IS NULL THEN 'X' ELSE COL1 END) AS COL1 FROM TABLE2) T2
                )
                ON T1.COL1=T2.COL1
                

                【讨论】:

                • 这在概念上与 OP 给出的没有什么不同,实际上更糟,因为 X 更有可能在数据库中。此外,内联视图是不必要的,并且在您创建笛卡尔积时 JOIN 是错误的。
                猜你喜欢
                • 2011-09-06
                • 2015-06-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-04-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多