使用自然连接与Where操作

时间:2013-11-18 06:54:43

标签: sql oracle sqlplus natural-join

我有一个查询,我一直在phpMyAdmin中使用它并且它已经完美地工作但是我将我的数据库迁移到不同的服务器,我现在使用SQL * Plus来执行我的查询。查询现在正在生成

ERROR at line 10:
ORA-25155: column used in NATURAL join cannot have qualifier

这是我的问题:

SELECT Block FROM ( 
   SELECT CardId, Block
   FROM Contains
   GROUP BY Block
   UNION
   SELECT CardId, Block
   FROM Contains
   NATURAL JOIN
      (SELECT CardId
       FROM Costs
       NATURAL JOIN
          (SELECT Id
           FROM Card
           WHERE RarityId IN (SELECT Id FROM Rarity WHERE RarityType='Legendary')
          ) rc
       WHERE Costs.CardId = rc.Id
       AND ManaCardId IN (SELECT Id FROM ManaCard WHERE ManaColor='Red')
      ) tmp
   WHERE Contains.CardId = tmp.CardId
) bn
GROUP BY Block
HAVING COUNT(*) < 2;

由于不喜欢我的限定符,有没有不同的方法来获得没有自然连接的查询?我尝试过使用Join和Inner Join相同的东西,但两个都没有用。

2 个答案:

答案 0 :(得分:0)

当您执行NATURAL Join时,您应该删除显式列限定符。 请尝试revmocve

  

WHERE Costs.CardId = rc.Id和ManaCardId IN

费用和rc

  

WHERE Con​​tains.CardId = tmp.CardId)bn

包含和tmp

在任何情况下,您都可以在没有自然连接的情况下重写此SQL。

答案 1 :(得分:0)

第1部分。

当您自然加入时,“自然加入”的列会丢失其表别名,例如:

SELECT CardId, Block
FROM Contains
NATURAL JOIN
   (SELECT CardId FROM ...
   ) tmp
WHERE Contains.CardId = tmp.CardId

此处,自然连接的两侧共享一列CardId,因此您无法引用此列的表别名,例如:

SELECT CardId, Block
FROM Contains
NATURAL JOIN
   (SELECT CardId FROM ...
   ) tmp
WHERE CardId = CardId

但显然这没有意义,因为自然连接意味着CardId = CardId的定义,所以上面应该是简单的:

SELECT CardId, Block
FROM Contains
NATURAL JOIN
   (SELECT CardId FROM ...
   ) tmp

第2部分。

内部查询中的这种自然连接:

SELECT CardId
FROM Costs
NATURAL JOIN
   (SELECT Id FROM ...
   ) rc
WHERE Costs.CardId = rc.Id
AND ManaCardId IN (...)

这里,两个列列表(CardId)和(Id)没有共同的列,这意味着自然连接没有任何连接 - 这通常会导致笛卡尔连接。但是,由于Costs.CardId = rc.Id,where子句无论如何都能有效地进行内连接。所以,为了使代码更清晰,我宁愿只使用内连接:

SELECT CardId
FROM Costs
JOIN
   (SELECT Id FROM ...
   ) rc
WHERE Costs.CardId = rc.Id
AND ManaCardId IN (...)

第3部分。

自然联接通常不受欢迎,因为它们取决于选择了哪些列 - 因此,如果开发人员将列添加到选择列表但不注意它使用自然连接,则可能会产生意外的副作用。通常很好的做法是明确地连接表格,例如:

SELECT Block FROM ( 
   SELECT CardId, Block
   FROM Contains
   GROUP BY Block
   UNION
   SELECT CardId, Block
   FROM Contains
   JOIN
      (SELECT CardId
       FROM Costs
       JOIN
          (SELECT Id
           FROM Card
           WHERE RarityId IN (SELECT Id FROM Rarity WHERE RarityType='Legendary')
          ) rc
       ON Costs.CardId = rc.Id
       WHERE ManaCardId IN (SELECT Id FROM ManaCard WHERE ManaColor='Red')
      ) tmp
   ON Contains.CardId = tmp.CardId
) bn
GROUP BY Block
HAVING COUNT(*) < 2;

您还可以简化最里面的连接:

SELECT Block FROM ( 
   SELECT CardId, Block
   FROM Contains
   GROUP BY Block
   UNION
   SELECT CardId, Block
   FROM Contains
   JOIN
      (SELECT CardId
       FROM Costs
       JOIN Card rc
       ON Costs.CardId = rc.Id
       WHERE Costs.ManaCardId IN (SELECT Id FROM ManaCard WHERE ManaColor='Red')
       AND rc.RarityId IN (SELECT Id FROM Rarity WHERE RarityType='Legendary')
      ) tmp
   ON Contains.CardId = tmp.CardId
) bn
GROUP BY Block
HAVING COUNT(*) < 2;

现在,看看这个查询,我注意到你在Contains表上进行了两次查询 - 第二个查询是这些行的子集。根据定义,第二个查询返回的所有行都包含在第一个查询中,UNION消除了重复,因此上述查询在逻辑上等同于:

SELECT Block FROM ( 
   SELECT CardId, Block
   FROM Contains
   GROUP BY Block
) bn
GROUP BY Block
HAVING COUNT(*) < 2;

我注意到GROUP BY的查询没有任何聚合,所以这在Oracle中不起作用。我认为这个查询等同于:

SELECT Block FROM ( 
   SELECT DISTINCT Block
   FROM Contains
) bn
GROUP BY Block
HAVING COUNT(*) < 2;

它从一个返回一组不同的块的查询中计算重复块的数量! - 这意味着此查询等同于:

SELECT DISTINCT Block FROM Contains;

我怀疑PHP运行此查询的方式与它在Oracle中的工作方式之间存在一些逻辑差异 - 因此上述简化可能是错误的。