如果双方都是左连接实体,则where子句中的条件顺序是否相关?

时间:2019-01-30 13:16:11

标签: sql oracle

我们在数据库上有两个查询,唯一的区别是OR子句中条件的顺序。

具有相同结构的查询可以很好地处理另一个数据库上的相似数据集。我们正在与Oracle 12.2合作。

Bestand表具有两个连接的实体,其中任何一个始终被填充。在我们的示例中,文章已连接,KbArticle始终为null。 在我们的一个查询中,我们获得了id在其他情况下不起作用的所有结果。 所有Bestand实体都满足最后两个条件(cod_lbr = 12, flg_sperre = 0)

SELECT bestand0_.*
FROM tbl_bestand bestand0_
LEFT OUTER JOIN tbl_kb_artikel customeror1_
ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel
LEFT OUTER JOIN tbl_artikel article2_
ON bestand0_.id_artikel          =article2_.id_artikel
WHERE (customeror1_.id_kb_artikel=3017874 OR article2_.id_artikel          =3017874)
AND bestand0_.cod_lbr            =12
AND NVL(bestand0_.flg_sperre,0)  = 0;

产生结果 Link to Explain Plan

SELECT bestand0_.*
FROM tbl_bestand bestand0_
LEFT OUTER JOIN tbl_kb_artikel customeror1_
ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel
LEFT OUTER JOIN tbl_artikel article2_
ON bestand0_.id_artikel          =article2_.id_artikel
WHERE (article2_.id_artikel          =3017874 OR customeror1_.id_kb_artikel=3017874)
AND bestand0_.cod_lbr            =12
AND NVL(bestand0_.flg_sperre,0)  = 0;

无结果 Link to Explain Plan

对我而言,绝对没有任何意义,因为我们应该获得满足where子句中所有语句的所有结果是,如果保留最后两个条件,查询将返回预期结果,因此如果我们修改我们的查询到:

SELECT bestand0_.*
FROM tbl_bestand bestand0_
LEFT OUTER JOIN tbl_kb_artikel customeror1_
ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel
LEFT OUTER JOIN tbl_artikel article2_
ON bestand0_.id_artikel          =article2_.id_artikel
WHERE (article2_.id_artikel          =3017874 OR customeror1_.id_kb_artikel=3017874);

我们再次获得结果 Link to Explain Plan

我希望两个查询都能产生相同的结果,我不明白为什么条件的顺序应该以任何方式影响查询的结果。 这里有一些索引弄乱了吗? 它必须是特定于数据库的,因为我们的查询可以在另一个具有相同数据集的相同版本的数据库上正常工作。

**更新**

不幸的是,将AND OTHER_ID IS NULL用作@kfinity的查询与上面的查询存在相同的问题,这对我来说绝对没有意义。在我们禁用了@Kuvick提到的 Adaptive Statistics Optimizer 后,使用以下说明计划,它们都再次返回了结果:

Explain plan Query 1 Explain plan Query 2

因此关闭优化程序可以解决我们的问题。

3 个答案:

答案 0 :(得分:3)

我们确定问题出在Oracle设置OPTIMIZER_ADAPTIVE_PLANS和OPTIMIZER_ADAPTIVE_STATISTICS中。设置数据库后,默认情况下,第一个处于活动状态,第二个处于禁用状态。

在我们的情况下,两个都被激活。在停用OPTIMIZER_ADAPTIVE_STATISTICS(即返回默认设置)之后,两个查询都返回了相同的结果(与预期的一样)。 可以使用以下查询检查这些设置:

SELECT * FROM v$parameter WHERE 1 = 1 AND LOWER(name) LIKE LOWER('optimizer_ad%') ORDER BY 1 DESC;

答案 1 :(得分:2)

看着您的解释计划,似乎优化器大致像这样重写您的查询:

第一个查询:

select *
from tbl_bestand b
where ID_KB_ARTIKEL=3017874
  and COD_LBR=12 
  and ID_ARTIKEL IS NOT NULL
  and NVL(b.FLG_SPERRE,0)=0 
union all
select b.*
from tbl_bestand b
left outer join tbl_kb_artikel c
  on b.id_kb_artikel = c.id_kb_artikel
where COD_LBR=12 
  and ID_ARTIKEL=3017874
  and NVL(b.FLG_SPERRE,0)=0 
  and LNNVL(c.ID_KB_ARTIKEL=3017874) -- ie, ID_KB_ARTIKEL <> 3017874 or ID_KB_ARTIKEL is null
;

第二个查询:

select *
from tbl_bestand b
where ID_ARTIKEL=3017874 
  and COD_LBR=12 
  and ID_KB_ARTIKEL IS NOT NULL -- this is the problem, you didn't want this
  and NVL(b.FLG_SPERRE,0)=0 
union all
select b.*
from tbl_bestand b
left outer join tbl_kb_artikel a
  on b.id_kb_artikel = a.id_kb_artikel
where COD_LBR=12 
  and ID_KB_ARTIKEL=3017874
  and NVL(b.FLG_SPERRE,0)=0 
  and LNNVL(c.ID_ARTIKEL=3017874) -- ie, ID_ARTIKEL <> 3017874 or ID_ARTIKEL is null
;

因此,在每种情况下它都摆脱了外部联接之一(因为您没有在外部联接表中的任何其他列上进行过滤),但是它会根据您首先放置的条件进行切换。

但是@scaisEdge是正确的-如果看到IS NOT NULL条件,它将始终将外部联接之一转换为内部联接。但是我认为您也是正确的,似乎优化程序行为不一致。我不确定您的其他数据库为什么没有以相同的方式对其进行优化,但是当我尝试使用示例数据进行查询时,解释计划仅得到2 hash join outer s。

无论如何,当将左连接列放在WHERE子句中时,我通常会明确要求使用NULL以避免这种情况。此版本可以正常工作吗?

SELECT bestand0_.*
FROM tbl_bestand bestand0_
LEFT OUTER JOIN tbl_kb_artikel customeror1_
ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel
LEFT OUTER JOIN tbl_artikel article2_
ON bestand0_.id_artikel =article2_.id_artikel
WHERE ((article2_.id_artikel=3017874 and bestand0_.id_kb_artikel is null)
    OR (customeror1_.id_kb_artikel=3017874 and bestand0_.id_artikel is null))
AND bestand0_.cod_lbr            =12
AND NVL(bestand0_.flg_sperre,0)  = 0;

答案 2 :(得分:1)

在这种情况下,您不应使用左联接表列作为内部联接 因此相关条件必须放在ON子句

    SELECT bestand0_.*
    FROM tbl_bestand bestand0_
    LEFT OUTER JOIN tbl_kb_artikel customeror1_ 
        ( ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel 
                 OR customeror1_.id_kb_artikel=3017874 )
    LEFT OUTER JOIN tbl_artikel article2_
        ( ON bestand0_.id_artikel =article2_.id_artikel 
                  OR  article2_.id_artikel =3017874 )
    WHERE  bestand0_.cod_lbr            =12
    AND NVL(bestand0_.flg_sperre,0)  = 0;