将谓词添加到外连接时出现问题

时间:2010-12-09 23:18:51

标签: sql oracle null predicate outer-join

我一直在撕扯我的头发:

-- 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'过去的谓词在连接后选择行。

我想让第一个查询工作,出于性能原因 - 有谁知道问题是什么?

2 个答案:

答案 0 :(得分:3)

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

您可以通过一些示例数据了解它。

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)

答案 1 :(得分:0)

嗯。这是一个很好的脑力激荡,但我想我可能会有它。

在第一个查询中,您将表格与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)来调整您的第一个查询。然而,这将作为您的第二个查询给出不同的结果,并且我认为还有一定数量的重复行。而且我也认为它不会更快。