左联接中的歧义(仅适用于Oracle?)

时间:2020-03-05 18:52:10  来源:igfitidea点击:

我的老板在我创建的查询中发现了一个错误,尽管查询结果证明他是正确的,但我不理解该错误的原因。这是修复之前的查询(简化版):

select PTNO,PTNM,CATCD
from PARTS 
left join CATEGORIES on (CATEGORIES.CATCD=PARTS.CATCD);

这是修复后:

select PTNO,PTNM,PARTS.CATCD
from PARTS 
left join CATEGORIES on (CATEGORIES.CATCD=PARTS.CATCD);

错误是,显示的CATCD列为空值,即查询结果包括表CATEGORIES的结果而不是PARTS的结果。
这是我不明白的:如果原始查询中存在歧义,为什么Oracle不会抛出错误?据我了解,在左连接的情况下,查询(PARTS)中的"主"表具有歧义性。
我是错了,还是只是没有正确考虑这个问题?

更新:

这是一个修改后的示例,其中不会引发歧义错误:

CREATE TABLE PARTS (PTNO NUMBER, CATCD NUMBER, SECCD NUMBER);

CREATE TABLE CATEGORIES(CATCD NUMBER);

CREATE TABLE SECTIONS(SECCD NUMBER, CATCD NUMBER);

select PTNO,CATCD 
from PARTS 
left join CATEGORIES on (CATEGORIES.CATCD=PARTS.CATCD) 
left join SECTIONS on (SECTIONS.SECCD=PARTS.SECCD) ;

有人知道吗?

解决方案

回答

通常建议使用特定的名称,并且无论如何都要完全限定所有列的名称,因为这样可以节省优化程序的工作量。当然在SQL Server中。

从Oracle文档中可以看出,似乎只有在选择列表中两次选择列名,或者在选择列表中一次选择列名,然后再在其他地方(如order by子句)选择列名时,才会抛出该异常。

也许我们发现了"未记录的功能" :)

回答

有趣的是,SQL Server会抛出一个错误(应该如此)

select id
from sysobjects s
left join syscolumns c on s.id = c.id

服务器:消息209,级别16,状态1,第1行
列名" id"不明确。

select id
from sysobjects 
left join syscolumns  on sysobjects.id = syscolumns.id

服务器:消息209,级别16,状态1,第1行
列名" id"不明确。

回答

Here's the query (simplified version)

我认为通过简化查询,我们可以删除导致错误的真正原因:-)

我们正在使用哪个oracle版本? Oracle 10g(10.2.0.1.0)提供:

create table parts (ptno number , ptnm number , catcd number);  
create table CATEGORIES (catcd number);

select PTNO,PTNM,CATCD from PARTS  
left join CATEGORIES on (CATEGORIES.CATCD=PARTS.CATCD);

我得到ORA-00918:列的定义不明确

回答

我正在使用Oracle 9.2.0.8.0。并且确实给出了错误" ORA-00918:列定义不明确"。

回答

像HollyStyles一样,我在Oracle文档中找不到任何可以解释我们所看到的内容的东西。

PostgreSQL,DB2,MySQL和MSSQL都因为模棱两可而拒绝运行第一个查询。

回答

根据我的经验,如果像这样创建查询,那么当存在这样的字段重叠时,数据结果将从连接的右侧而不是左侧拉CATCD。

因此,由于此联接将具有来自PARTS的所有记录,而仅具有来自CATEGORIES的一些记录,因此,只要右侧没有数据,则CATCD字段中的值为NULL。

通过从PARTS(即左侧)显式定义列,我们将获得一个非null值,前提是该字段在PARTS中具有数据。

请记住,使用LEFT JOIN只能保证左侧表中字段中的数据,右侧很可能有空列。

回答

@帕特:对于查询,我在这里遇到同样的错误。我的查询比我最初发布的查询稍微复杂一点。我现在正在研究一个可重现的简单示例。

回答

恐怕我不能告诉你为什么你没有例外,但是我可以推测为什么它选择了CATEGORIES版本的列而不是PARTS版本的列。

As far as I understood, in the case of left joins, the "main" table in the query (PARTS) has precedence in ambiguity

目前尚不清楚,从概念上看,查询中的"主"是指左联接中的左表,还是"驱动"表。我们编写的查询在实际执行中不一定是"主"表。

我的猜测是,Oracle在执行查询时只是使用它命中的第一个表中的列。而且,由于SQL中的大多数单独操作不需要先击中一个表,因此DBMS将在解析时决定哪个是最有效的扫描对象。尝试获取查询的执行计划。我怀疑它可能揭示它先击中类别,然后击中PARTS。

回答

这可能是Oracle优化器中的错误。我可以在3个表的查询上重现相同的行为。直观上似乎应该产生一个错误。如果我通过以下两种方式之一重写它,则会产生错误:

(1)使用旧式外部联接

select ptno, catcd
from parts, categories, sections
where categories.catcd (+) = parts.catcd
  and sections.seccd (+) = parts.seccd

(2)明确隔离两个联接

select ptno, catcd
from (
  select ptno, seccd, catcd
  from parts
  left join categories on (categories.CATCD=parts.CATCD) 
)
left join sections on (sections.SECCD=parts.SECCD)

我使用DBMS_XPLAN来获取有关查询执行的详细信息,这确实显示了一些有趣的东西。计划基本上是外部联接PARTS和CATEGORIES,投影结果集,然后将其外部联接到SECTIONS。有趣的是,在第一个外部联接的投影中,它仅包括PTNO和SECCD-不包括前两个表中的任一个的CATCD。因此,最终结果是从第三张表获得CATCD。

但是我不知道这是原因还是结果。

回答

我们应该问自己一个更大的问题是:为什么零件表中没有类别表中的类别代码?

回答

当使用ANSI样式的联接时,这是某些Oracle版本的已知错误。正确的行为是得到ORA-00918错误。

无论如何,始终最好指定表名。这样,当我们碰巧添加一个名称也用于另一个表的新列时,查询不会中断。