How to replace outer join - sql

I have three tables. Table A Table B and Table C they have id1,id2,id3 as primary key respectively.
id2 is foreign key in both A and C.
id1 is foreign key in C.
For following query we get five rows
select *
from A
where id3=123;
For following query we get three rows
select *
from C
where id3=123;
To get two remaining rows I performed outer join like
select *
from A,B,C
where C.id3=123
AND A.id1=C.id1(+)
I am getting required output
Is there any simpler way like in line query or use of "not exist" using which I can replace outer join ?

You syntax would generate a syntax error.
I speculate that you want to run something like this:
select a.*
from a left join
c
on a.id3 = c.id3 and a.id1 = c.id1
where a.id3 = 123;
However, I don't know what B is doing there; it is in the query but no where else in the question.

An outer join is the proper mechanism for what you want, but a more standard syntax would be:
select *
from A
LEFT JOIN C
ON A.id1=C.id1
WHERE id3=123

Related

Does wrapping my Coalesce in a subquery make my query more efficient or does it do nothing?

Lets say I have a query where one field can appear in either Table A or Table B but not both. So to retrieve it I use Coalesce.
Something like
Select
...
Coalesce(A.Number,B.Number) Number
...
From Table A
Left Join Table B on A.C= B.C
Now lets say I want to join another table to that Number field
should I just do
Join Table Z on Z.Z = Coalesce(A.Number,B.Number)
Or is it better to wrap my original table in a query and join on the definite result. So something like
Select * from (
Select
...
Coalesce(A.Number,B.Number) Number
...
From Table A
Left Join Table B on A.C= B.C
) T
left join Table Z on Z.Number= T.Number
Does this make a difference?
if i were joining another table to the result of the first query instead of a sub query i would place the first part in a CTE whenever possible, i believe the performance would be the same as a subquery but CTEs are more readable in my opinion.
with cte1 as
(
Select
...
Coalesce(A.Number,B.Number) Number
...
From Table A
Left Join Table B
on A.C= B.C
)
select *
from cte1 a
Join Table Z
on Z.Z = a.number

Is it possible to JOIN with a CTE in PostgreSQL?

I'm trying to write a query like this
WITH a AS (SELECT key FROM table)
SELECT *
FROM a
JOIN b;
which generates a syntax error in PostgreSQL 10.4.
Why does this error?
It looks like I will be creating a view instead. Is there a better solution?
You are missing the JOIN condition:
WITH a AS (SELECT key FROM table)
SELECT *
FROM a
JOIN b ON a.key = b.key;
The problem is not the CTE, it is a simple syntax error:
SELECT *
FROM a
JOIN b
-- something missing here
Here, JOIN defaults to an INNER JOIN, which requires some condition for which rows should be joined - generally either like ON a.key = b.key or USING key. The same would be true of a LEFT OUTER JOIN or RIGHT OUTER JOIN.
If you wanted all the possible combinations (rare, but occasionally useful), you would use CROSS JOIN:
SELECT *
FROM a
CROSS JOIN b;
Or the similar comma operator:
SELECT *
FROM a, b;

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.

Join a table only if result set > 0

I have a table A joined with a table B which give me a result set.
I want to join a table C to the previous ones in order to restrict the result set. But in case there is no result with this join, I would like to have the same result set than before (without taking care of C).
Can you think of way to do that in SQL ?
SELECT *
FROM TableA
INNER JOIN TableB
ON TableA.ID = TableB.TableAID
LEFT JOIN TableC
ON TableC.ID = TableB.TableCID
This will return all rows from Tables A & B but only the rows from TableC where the ON criteria match.
Otherwise conditional joins don't really apply in standard SQL. If you are using SQL Server you can perform some stored procedure logic to check the results from TableC and if there are none then only get data from Table A & B. But this approach with be provider specific
Not possible with regular SQL since it involves logic.
Your best bet is to make a small script, e.g. (in pseudo code)
select * into #tmp from x inner join y inner join z where blabla;
if (exists (select * from #tmp))
BEGIN
select * from #tmp
END
else
BEGIN
select * from x inner join y where blabla;
END
Edit:
But if I were you, I would just always join with C using a LEFT JOIN, so you can see if the result was in one or the other result set...
e.g.
select x.*, y.*, case when z.id is null then 0 else 1 end from x inner join y left join z on blabla where blabla;
But that of course assumes you are able to alter the code path that reads the result.
I see a problem in the LEFT/OUTER JOIN methods. If you do it you could get some results that are in A and B but not in C. If I understand well the porpouse is join AB with C, I mean the result when crossing with C must include the three restrictions. So the #Cine solution is the apropiate to this case.

How to exclude rows that don't join with another table?

I have two tables, one has primary key other has it as a foreign key.
I want to pull data from the primary table, only if the secondary table does not have an entry containing it's key. Sort of an opposite of a simple inner join, which returns only rows that join together by that key.
SELECT <select_list>
FROM Table_A A
LEFT JOIN Table_B B
ON A.Key = B.Key
WHERE B.Key IS NULL
Full image of join
From aticle : http://www.codeproject.com/KB/database/Visual_SQL_Joins.aspx
SELECT
*
FROM
primarytable P
WHERE
NOT EXISTS (SELECT * FROM secondarytable S
WHERE
P.PKCol = S.FKCol)
Generally, (NOT) EXISTS is a better choice then (NOT) IN or (LEFT) JOIN
use a "not exists" left join:
SELECT p.*
FROM primary_table p LEFT JOIN second s ON p.ID = s.ID
WHERE s.ID IS NULL
Another solution is:
SELECT * FROM TABLE1 WHERE id NOT IN (SELECT id FROM TABLE2)
SELECT P.*
FROM primary_table P
LEFT JOIN secondary_table S on P.id = S.p_id
WHERE S.p_id IS NULL
If you want to select the columns from First Table "which are also present in Second table, then in this case you can also use EXCEPT. In this case, column names can be different as well but data type should be same.
Example:
select ID, FName
from FirstTable
EXCEPT
select ID, SName
from SecondTable
This was helpful to use in COGNOS because creating a SQL "Not in" statement in Cognos was allowed, but it took too long to run. I had manually coded table A to join to table B in in Cognos as A.key "not in" B.key, but the query was taking too long/not returning results after 5 minutes.
For anyone else that is looking for a "NOT IN" solution in Cognos, here is what I did. Create a Query that joins table A and B with a LEFT JOIN in Cognos by selecting link type: table A.Key has "0 to N" values in table B, then added a Filter (these correspond to Where Clauses) for: table B.Key is NULL.
Ran fast and like a charm.