问题
I have a query that I've been using in phpMyAdmin and it's been working perfectly however I migrated my database over to different server and I am now using SQL*Plus to perform my queries. The query is now generating
ERROR at line 10:
ORA-25155: column used in NATURAL join cannot have qualifier
Here is my query:
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;
Since it is not liking my qualifiers is there a different way to get the query without natural join? I've tried using the same thing with Join and Inner Join but both are not working.
回答1:
Part 1.
When you natural join, the columns that have been "naturally joined" lose their table aliases, so for example:
SELECT CardId, Block
FROM Contains
NATURAL JOIN
(SELECT CardId FROM ...
) tmp
WHERE Contains.CardId = tmp.CardId
Here, both sides of the natural join share a column CardId
, so you cannot refer to the table alias for this column, e.g.:
SELECT CardId, Block
FROM Contains
NATURAL JOIN
(SELECT CardId FROM ...
) tmp
WHERE CardId = CardId
But obviously this makes no sense, since the natural join means that CardId
= CardId
by definition, so the above should be simply:
SELECT CardId, Block
FROM Contains
NATURAL JOIN
(SELECT CardId FROM ...
) tmp
Part 2.
This natural join in the inner query:
SELECT CardId
FROM Costs
NATURAL JOIN
(SELECT Id FROM ...
) rc
WHERE Costs.CardId = rc.Id
AND ManaCardId IN (...)
Here, the two column lists (CardId
) and (Id
) have no columns in common, which means the natural join has nothing to join - which would normally result in a cartesian join. However, the where clause effectively does an inner join anyway because of Costs.CardId = rc.Id
. So, just to make the code clearer, I would prefer to just use an inner join:
SELECT CardId
FROM Costs
JOIN
(SELECT Id FROM ...
) rc
WHERE Costs.CardId = rc.Id
AND ManaCardId IN (...)
Part 3.
Natural joins are generally frowned upon because they are dependent on which columns are selected - so if a developer adds a column to a select list but doesn't notice it's using a naturaly join, it may have unexpected side effects. It is generally good practice to join tables explicitly, e.g.:
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;
You can also simplify the innermost join:
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;
Now, looking at this query I notice that you're UNIONing two queries on the Contains
table - the second query being a subset of those rows. By definition, all the rows returned by the 2nd query are included in the 1st query, and UNION eliminates duplicates, so the above query is logically equivalent to:
SELECT Block FROM (
SELECT CardId, Block
FROM Contains
GROUP BY Block
) bn
GROUP BY Block
HAVING COUNT(*) < 2;
I note that the query with the GROUP BY doesn't have any aggregates, so this will not work in Oracle. I think this query is equivalent to:
SELECT Block FROM (
SELECT DISTINCT Block
FROM Contains
) bn
GROUP BY Block
HAVING COUNT(*) < 2;
Which counts the number of duplicate blocks from a query that returns a distinct set of Blocks! - which means this query is equivalent to:
SELECT DISTINCT Block FROM Contains;
I suspect there are some logical differences between the way that PHP runs this query vs. how it will work in Oracle - so the above simplification is probably wrong.
回答2:
As you performed NATURAL Join you should remove explicit column qualifier. Please try to revmocve
WHERE Costs.CardId = rc.Id AND ManaCardId IN
Costs and rc
WHERE Contains.CardId = tmp.CardId)bn
Contains and tmp
In any case you can rewrite this SQL without Natural Joins.
来源:https://stackoverflow.com/questions/20041858/using-natural-join-with-where-operation