2 Outer Joins on Same Table? - sql

Here is a question which has been boggling me for few days now, and I searched and searched but couldn't find any convincing answer !
Simple question, why is it restricted to have 2 Outer Joins in SQL, on same table even with different columns being used, check the queries below for better understanding. Also I can overcome them using nested sub query or ANSI joins, but then why it is even restricted in the first place using (+) operator!
In this question I'm referring to the error :
ORA-01417: a table may be outer joined to at most one other table
What I want to ask is why this is allowed :
select * from
a, b, c
where a.a1 = b.b1
and a.a2 = c.c1
And why this is not allowed:
select * from
a, b, c
where a.a1(+) = b.b1
and a.a2(+) = c.c1
Please leave ANSI and Nested SubQueries alone

The restriction is described in Oracle documentation: Outer Joins
Oracle recommends that you use the FROM clause OUTER JOIN syntax rather than the Oracle join operator. Outer join queries that use the Oracle join operator (+) are subject to the following rules and restrictions, which do not apply to the FROM clause OUTER JOIN syntax:
...
In a query that performs outer joins of more than two pairs of tables, a single table can be the null-generated table for only one other table. For this reason, you cannot apply the (+) operator to columns of B in the join condition for A and B and the join condition for B and C. Refer to SELECT for the syntax for an outer join.
which basically means (described in ANSI/ISO syntax) that you can't have with the old (+) syntax what is perfectly valid in ANSI/ISO:
--- Query 1 ---
a
RIGHT JOIN b
ON a.x = b.x
RIGHT JOIN c
ON a.y = c.y
or:
--- Query 1b ---
c
LEFT JOIN
b LEFT JOIN a
ON a.x = b.x
ON a.y = c.y
That's only one of the many restrictions of the old Oracle syntax.
As for the reasons for this restriction, it may be implementation details or/and the ambiguity of such joins. While the two joins above are 100% equivalent, the following is not equivalent to the above two:
--- Query 2 ---
a
RIGHT JOIN c
ON a.y = c.y
RIGHT JOIN b
ON a.x = b.x
See the test in SQL-Fiddle. So the question arises. How should the proprietary join be interpreted, as query 1 or 2?
FROM a, b, c
WHERE a.y (+) = c.y
AND a.x (+) = b.x
There is no restriction if a table appears on the left side of (2 or more) outer joins. These are perfectly valid, even with the old syntax:
FROM a
LEFT JOIN b ON a.x = b.x
  LEFT JOIN c ON a.y = c.y
...
LEFT JOIN z ON a.q = z.q
FROM a, b, ..., z
WHERE a.x = b.x (+)
  AND a.y = c.y (+)
...
AND a.q = z.q (+)

I strongly suggest to use explicit OUTER JOIN syntax. Starting from Oracle 12c this restriction is relaxed 1.4.3 Enhanced Oracle Native LEFT OUTER JOIN Syntax:
In previous releases of Oracle Database, in a query that performed outer joins of more than two pairs of tables, a single table could be the null-generated table for only one other table. Beginning with Oracle Database 12c, a single table can be the null-generated table for multiple tables.
Code:
CREATE TABLE a AS
SELECT 1 AS a1, 2 AS a2 FROM dual;
CREATE TABLE b AS
SELECT 1 AS b1 FROM dual;
CREATE TABLE c AS
SELECT 3 AS c1 FROM dual;
-- Oracle 12c: code below will work
SELECT *
FROM a, b, c
WHERE a.a1(+) = b.b1
AND a.a2(+) = c.c1;
Output:
A1 A2 B1 C1
- - 1 3
db<>fiddle demo - Oracle 11g will return error
db<>fiddle demo Oracle 12c/18c will return resultset

Related

Writing SQL query from a given data model

I apologize in advance if this question is ambiguous. My SQL skills are very weak and I'm not sure if this question is too general to have a correct answer.
I'm working on a project, converting reports from Hyperion Interactive Reporting (IR) to OBIEE. I'm given a visual of the data model in IR, and I'm trying to write the equivalent SQL query.
The data model looks like this:
A --- = --- B --- = --- C
\-- +=+ --/ \-- +=+ --/
The = represents an inner join; +=+ represents a full outer join. Table B inner joins and full outer joins to tables A and C. So I have four joins that I'm trying to piece together:
A join B on A.x = B.x
A full outer join B on A.y = B.y
B join C on B.x = C.x
B full outer join C on B.y = C.y
Without specifying details of my data, is it possible to write a query that matches the behavior of the data model above? And if so, what is the correct/preferred way to do so?
Use union/union all as per your requirement
A join B on A.x = B.x
B join C on B.x = C.x
union
A full outer join B on A.y = B.y
B full outer join C on B.y = C.y

Is an update with a join standard sql compliant

Is the following standard SQL compliant? If not, then why not?
UPDATE a
SET a.Y = 2
FROM TABLE_A a
INNER JOIN TABLE_B b ON
a.X = b.X
WHERE b.Z = blahblah
The ANSI compliant way to write the query is:
UPDATE TABLE_A
SET Y = 2
WHERE b.Z = blahblah AND
EXISTS (SELECT 1 FROM TABLE_B b WHERE TABLE_A.X = b.X);
To the best of my knowledge, neither ANSI nor ISO provide rationales for why they do not do something. I could speculate that the FROM clause causes issues when there are multiple matches on a given row. Personally, I would not want to be in the room during the arguments about which order the updates take place.

T-SQL: Wild Card as table name alias in select statement possible?

This is more of a curiosity than an actual applied question. Say you have a statement with multiple joins such as:
SELECT
a.name,
b.salary,
c.x
FROM
[table1] a
INNER JOIN [table2] b
ON a.key = b.key
INNER JOIN [table3] c
ON b.key = c.key
Now, say you were to make several more joins to other tables whose schema was unfamiliar, however you know:
the keys on which to make the join
that several of those tables has a column with the the name 'x'.
Is it possible to select 'x' from all tables that contain it, without explicitly referring to the table alias. So it would ave a similar results as this (if it were possible)
SELECT
a.name,
b.salary,
*.x
...
No this isn't possible.
You can use a.* to get all columns from a but it is not valid to use a wildcard as the table name.
#Martin Smith is correct that you can't use *.x and refer to columns from multiple tables. There is however a way to write a query that shows all columns x from tables where they exist without breaking if one or more of the tables do not have such column. It's a rather complicated way that (mis)uses scope resolution.
Lets say that some of the tables (b and d in the example) have a column named x, while some others (c here) do not have such column. Then you can replace INNER joins with CROSS APPLY and LEFT joins with OUTER APPLY and a query with:
SELECT
a.name,
a.salary,
b.x AS bx,
'WITHOUT column x' AS cx,
d.x AS dx
FROM
a
INNER JOIN b
ON a.aid = b.aid
LEFT JOIN c
ON a.aid = c.aid
LEFT JOIN d
ON a.aid = d.aid ;
would be written as:
SELECT
a.name,
a.salary,
bx,
cx,
dx
FROM
( SELECT a.*,
'WITHOUT column x' AS x
FROM a
) a
CROSS APPLY
( SELECT x AS bx
FROM b
WHERE a.aid = b.aid
) b
OUTER APPLY
( SELECT x AS cx
FROM c
WHERE a.aid = c.aid
) c
OUTER APPLY
( SELECT x AS dx
FROM d
WHERE a.aid = d.aid
) d ;
Tested at SQL-Server 2008: SQL-Fiddle

How to use oracle outer join with a filter where clause

If i write a sql:
select *
from a,b
where a.id=b.id(+)
and b.val="test"
and i want all records from a where corresponding record in b does not exist or it exists with val="test", is this the correct query?
You're much better off using the ANSI syntax
SELECT *
FROM a
LEFT OUTER JOIN b ON( a.id = b.id and
b.val = 'test' )
You can do the same thing using Oracle's syntax as well but it gets a bit hinkey
SELECT *
FROM a,
b
WHERE a.id = b.id(+)
AND b.val(+) = 'test'
Note that in both cases, I'm ignoring the c table since you don't specify a join condition. And I'm assuming that you don't really want to join A to B and then generate a Cartesian product with C.
Move the condition into the JOIN clause and use the ANSI standard join pattern.
SELECT NameYourFields,...
FROM A
LEFT OUTER JOIN B
ON A.ID = B.ID
AND B.VAL = 'test'
INNER JOIN C
ON ...
A LEFT OUTER JOIN is one of the JOIN operations that allow you to specify a join clause. It preserves the unmatched rows from the first (left) table, joining them with a NULL row in the shape of the second (right) table.
So you can do as follows :
SELECT
FROM a LEFT OUTER JOIN b
ON a.id = b.id
--Note that you have used double quote "test" which is not used for varchar in SQL you should use single quote 'test'
AND b.val = 'test';
SELECT * FROM abc a, xyz b
WHERE a.id = b.id
AND b.val = 'test'

Need help with a sql query that has an inner and outer join

I really need help getting this query right. I can't share actual table and column names, but will try my best to layout the problem simply.
Assume the following tables. The tables and keys CANNOT be changed. Period. I don't care if you think it's a bad design, this question isn't a design question, it's on SQL syntax.
Table A - Primary key named id1
Table B - Contains two foreign keys, TableA.id1 and Foo.id2(ignore Foo, it doesn't matter for this)
Table C - Contains two foreign keys, TableA.id1 and Foo.id2, additional interesting
columns.
Constraints:
The SQL gets a set of id1s passed in as an argument.
It must return a list of Table C rows.
It must only return Table C rows where a Table B row exists with a matching TableA.id1 and Foo.id2 - There ARE rows in Table C that don't match Table B
A row MUST be returned for every id1 passed in, even if no Table C row exists.
At first I tried a Left Outer Join from Table A to Table B then an Inner Join to Table C. That violates the 4th rule above, as the Inner Join drops out those rows.
Next I tried two Left Outer joins. This is closer, but has the side effect of including rows that match the Table A join to Table B, but don't have a corresponding Table C entry, which isn't what I want.
So, here's what I came up with.
SELECT
a.id1,
c.*
FROM
TableB b
INNER JOIN
TableC c USING (id1,id2)
RIGHT OUTER JOIN
TableA a USING (id1)
WHERE
a.id1 in (x,y,z)
I'm a bit wary of a Right Outer Join, as the documentation I've read says it can be replaced with a Left Outer, but it doesn't appear so for this case. It also seems a bit rare, which is making other devs nervous, so I'm being cautious.
So, three questions in one.
Is this correct?
Did I use the Right Outer Join correctly?
Is there a cleaner way to achieve the same thing?
EDIT: DB is MySQL
You can rewrite it as a LEFT OUTER JOIN by using parentheses. In pseudo-SQL change this:
SELECT ...
FROM b
INNER JOIN c ON ...
RIGHT OUTER JOIN a ON ...
to this:
SELECT ...
FROM a
LEFT OUTER JOIN (
b INNER JOIN c ON ...
) ON ...
You can use an EXISTS clause, which sometimes works better
SELECT
a.id1,
c.*
FROM TableA a
LEFT JOIN TableC c
ON c.id1 = a.id1 AND EXISTS (
select *
from TableB b
where b.id1=c.id1 and b.id2=c.id2)
WHERE
a.id1 in (x,y,z)
As you have written it, it works because ANSI JOINs are always processed top to bottom. Since you need to test B against C before joining to A, it is about the only way to write it without introducing a subquery [(B x C) RIGHT JOIN A]. However, a bad query plan could perform all records in B and C (B x C) before right joining to A.
The EXISTS method efficiently uses the filter on A, then LEFT JOINs to C and for each C found, validates that it also exists in B (or discards).
Q's
Yes your query is correct
Yes
EXISTS should work better
Yeah, you need to start with TableA and then add tables B and C using joins. The only reason you even need TableA is to make sure you have a row for each parameter.
Select a.id1,c.*
From
TableA a
Left Join TableB b on a.id1=b.id1
Left Join TableC c on b.id1=c.id1 and b.id2=c.id2
Where a.id1 in (x,y,z)
You need to do OUTER joins all the way across, or rows that are missing in B will also cause data from A to be filtered out of the result set. By joining C to B (instead of directly to A) you are using B to filter. You could do it with a complicated EXISTS clause, but this is cleaner.