【问题标题】:Problem adding predicates to outer join将谓词添加到外部连接时出现问题
【发布时间】:2011-05-23 04:38:42
【问题描述】:

为此我一直在扯头发:

-- misses nulls
SELECT *
FROM BASE_TABLE TAB1
     FULL JOIN BASE_TABLE TAB2
       USING (ANOTHER_ID)
WHERE (TAB1.ID = 6 OR TAB1.ID IS NULL)
  AND (TAB2.ID = 8 OR TAB2.ID IS NULL);

-- catches nulls
SELECT *
FROM (SELECT * FROM BASE_TABLE WHERE ID = 6) TAB1
     FULL JOIN (SELECT * FROM BASE_TABLE WHERE ID = 8) TAB2
       USING (ANOTHER_ID);

第一个查询会丢失一个或另一个表中的行不存在的行。为什么第一个查询做外连接失败?

我一直认为我明白了 - 首先评估 WHERE 子句,因此稍后不应用“OR IS NULL” - 但这对我来说没有意义,因为我已经成功应用了“IS NULL”过去的谓词选择连接后的行。

出于性能原因,我想让第一个查询工作 - 有人知道问题出在哪里吗?

【问题讨论】:

  • 我对@9​​87654322@ 感到困惑——那是什么语法?我在任何文档中都找不到它,普通 SQL 使用 ON TAB1.ANOTHER_ID = TAB2.ANOTHER_ID..?
  • @littlegreen 自 9i 以来的 Oracle 功能。它本质上是您所写内容的简写。
  • @littlegreen, djacobson:当您使用 USING (ANOTHER_ID) 时,它只会生成一个名为 ANOTHER_ID 的列(不能被别名引用),而 ON 语法会导致两列。

标签: sql oracle null predicate outer-join


【解决方案1】:

第一个查询执行连接然后过滤,第二个查询执行过滤然后连接。 对于外连接,区别很重要。

你会通过一些示例数据来理解它。

create table tab1 (id number, another_id number);
create table tab2 (id number, another_id number);

insert into tab1 values (6,5);
insert into tab2 values (8,5);
insert into tab1 values (1,6);
insert into tab2 values (2,6);

SELECT *
FROM TAB1
     FULL JOIN TAB2 USING (ANOTHER_ID);

     ANOTHER_ID              ID              ID
--------------- --------------- ---------------
           5.00            6.00            8.00
           6.00            1.00            2.00

结果集(不带 WHERE)显示 another_id 6 的连接条件已经成功。不需要外连接。

当您添加 WHERE 过滤器时,它会过滤掉 6 的匹配项,因为 id 既不是 6,8 也不是 null。也就是说,您将其用作过滤谓词而不是连接谓词。

我相信您的意图是让 TAB1 加入 TAB2,ANOTHER_ID 上应该匹配,并且 TAB1 的 ID 应该是 6,TAB2 的 ID 应该是 8。这就是第二个 SQL 中的内容。

连接谓词也可以表示为

SELECT *
FROM TAB1
    FULL JOIN TAB2 ON 
           (TAB1.ANOTHER_ID = TAB2.ANOTHER_ID AND TAB1.ID=6 AND TAB2.ID=8)

【讨论】:

  • 可能是这样,尽管我确信我已经使用 WHERE 子句检查 IS NULL 以排除匹配项,这意味着 IS 空检查是在连接之后进行的。我会玩你的例子。顺便说一句,我试过你的替代措辞,但是当你在 ON 而不是 WHERE 子句中包含谓词时,优化器往往会尝试加入每条记录而不先过滤,这是一个性能问题。
  • 是的,使用外连接时,WHERE 子句中的谓词必须应用在连接子句中的谓词之后。这才是重点。您的要求是在加入之前或期间应用 ID=6/8 条件,因此它们不应位于 WHERE 中。
【解决方案2】:

嗯。这是一个脑筋急转弯,但我想我可能会拥有它。

在您的第一个查询中,您在键 ANOTHER_ID 上将表完全连接到自身。

当两个连接表相同时,无论你是进行全连接、内连接、左连接还是右连接,结果都是一样的。因为您的密钥 ANOTHER_ID 在两个表中始终存在或不存在。不存在一个表的 ANOTHER_ID 值在另一个表中找不到的情况,因此不存在 TAB1.ANOTHER_ID 或 TAB2.ANOTHER_ID 最终为 NULL 的情况。所以你实际上只是在 ANOTHER_ID 上进行自内联。

现在我不知道你的 ID 列的内容,但我想它总是充满了一些价值。因此,在您的自内连接之后,生成的 ID 列将始终填充一些内容。也许不是 6 或 8,但也不是 NULL。在没有 NULL 值的情况下,您的 WHERE 查询将转换为 WHERE TAB1.ID = 6 AND TAB2.ID = 8,它只留下正确的组合,没有其他任何内容。

相比之下,在您的第二个查询中,您定义了 ID=6 和 ID=8 的子集,并将这些子集彼此完全连接。子集 1 包含子集 2 中不存在的某些 ANOTHER_ID,反之亦然。因此,现在有了 FULL JOIN 的基础,因为某些行不会连接到其他行,在 TAB1.ID 或 TAB2.ID 中留下您可以检测到的 NULL 值。

我认为可以通过将 WHERE 子句更改为:WHERE TAB1.ID IN (6,8) AND TAB2.ID IN (6,8) 来调整您的第一个查询。但是,这将给出与您的第二个查询不同的结果,而且我认为还有一定数量的重复行。而且我也不认为它会更快。

【讨论】:

  • 我不相信这与它是自联接与 2 个单独表的联接这一事实有关。但我会试试你的例子,也许我会看到光明。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
相关资源
最近更新 更多