left outer join - what's the difference? - oracle

I sometimes get confused with LEFT JOINS and OR.
Here in the below two code samples, there is only one difference. There is a bracket in the WHERE clause in one sample and no bracket in other sample.
Obviously, I am getting different results. I want to understand what's happening.
Thanks.
SELECT FROM TABLE_A as A
LEFT OUTER JOIN TABLE_B as B
ON
A.col1 = B.col1 AND
A.col2 = B.col2 AND
A.col3 = B.col3
WHERE
B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL AND
B.col4= 'R' AND
B.col5 != 'DR'
SELECT FROM TABLE_A as A
LEFT OUTER JOIN TABLE_B as B
ON
A.col1 = B.col1 AND
A.col2 = B.col2 AND
A.col3 = B.col3
WHERE
(B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL) AND
B.col4= 'R' AND
B.col5 != 'DR'

(B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL)
In second case, first the expression in the bracket is evaluated and then the following "AND" operators are applied. So the results if above bracket expression is ANDed with B.col4= 'R' and then it is ANDed with B.col5 != 'DR'
B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL AND
B.col4= 'R' AND
B.col5 != 'DR'
In first case, it is evaluated like following:
(B.col1 IS NULL) OR (B.col2 IS NULL) OR (B.col3 IS NULL AND B.col4= 'R' AND B.col5 != 'DR')

The AND operator has higher precedence than OR, so your first WHERE clause can be written as this:
WHERE
B.col1 IS NULL OR B.col2 IS NULL OR (B.col3 IS NULL AND
B.col4= 'R' AND
B.col5 != 'DR')
But this may not be what you intended, if the second version of your WHERE clause is any evidence. If you want to check for either of the three columns being NULL ANDed with the other two conditions then you should use parentheses.

I think you've gotten very confused about left joins as well as the AND/OR operator precedence.
In your first query, the order is applied left to right, with ANDS being applied first.
That means that your first query is the equivalent of:
SELECT *
FROM table_a a
LEFT OUTER JOIN table_b b ON (a.col1 = b.col1
AND a.col2 = b.col2
AND a.col3 = b.col3)
WHERE b.col1 IS NULL
OR b.col2 IS NULL
OR (b.col3 IS NULL
AND b.col4 = 'R'
AND b.col5 != 'DR');
However, your second query is checking for nulls in any of the three join conditions (which would either be all null, or all not null, since you haven't catered for null values in your join conditions) as well as checking for the presence of values in the remaining two columns. Which doesn't make sense - if col1, col2 and col3 are null, it means that table_b row isn't part of the join, so col4 and col5 would be null as well.
Instead, what I think you meant to do was to make the and b.col4 = 'R' and b.col5 != 'DR' part of the left outer join conditions - ie. only join the rows from table_b where col4 = 'R' and col5 != 'DR' - like so:
WITH table_a AS (SELECT 1 col1, 1 col2, 1 col3 FROM dual UNION ALL
SELECT 2 col1, 2 col2, 2 col3 FROM dual UNION ALL
SELECT 3 col1, 3 col2, 3 col3 FROM dual),
table_b AS (SELECT 1 col1, 1 col2, 1 col3, 'R' col4, 'DP' col5 FROM dual UNION ALL
SELECT 2 col1, 2 col2, 2 col3, 'R' col4, 'DR' col5 FROM dual)
SELECT *
FROM table_a a
LEFT OUTER JOIN table_b b ON (a.col1 = b.col1
AND a.col2 = b.col2
AND a.col3 = b.col3
AND b.col4 = 'R'
AND b.col5 != 'DR');
COL1 COL2 COL3 COL1 COL2 COL3 COL4 COL5
---------- ---------- ---------- ---------- ---------- ---------- ---- ----
1 1 1 1 1 1 R DP
2 2 2
3 3 3

Your second query is not a LEFT OUTER JOIN:
SELECT *
FROM TABLE_A A
LEFT OUTER JOIN TABLE_B B
ON ( A.col1 = B.col1
AND A.col2 = B.col2
AND A.col3 = B.col3 )
WHERE ( B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL )
AND B.col4= 'R'
AND B.col5 != 'DR'
The WHERE clause will only accept rows where B.col4 and B.col5 are NOT NULL and this will only ever be the case when the tables are joined and this means the join condition is effectively an INNER JOIN.
However, the conditions ( B.col1 IS NULL OR B.col2 IS NULL OR B.col3 IS NULL ) and ( A.col1 = B.col1 AND A.col2 = B.col2 AND A.col3 = B.col3 ) can never be fulfilled simultaneously since a NULL value cannot equal any other value (including another NULL). So the query should never return any rows.
The first query is:
SELECT *
FROM TABLE_A A
LEFT OUTER JOIN TABLE_B B
ON ( A.col1 = B.col1
AND A.col2 = B.col2
AND A.col3 = B.col3 )
WHERE B.col1 IS NULL
OR B.col2 IS NULL
OR ( B.col3 IS NULL AND B.col4= 'R' AND B.col5 != 'DR' )
This will return rows when B.col1 IS NULL OR B.col2 IS NULL but will never match on ( B.col3 IS NULL AND B.col4= 'R' AND B.col5 != 'DR' ) since the join condition will enforce the either B.col3 is not NULL (when there is a match in the LEFT OUTER JOIN) or B.col4 and B.col5 are both NULL (when there is no match in the LEFT OUTER JOIN).
What you might actually be trying to achieve (although it is unclear from your question) is:
SELECT *
FROM TABLE_A A
LEFT OUTER JOIN TABLE_B B
ON ( A.col1 = B.col1
AND A.col2 = B.col2
AND A.col3 = B.col3
AND B.col4 = 'R'
AND B.col5 != 'DR' )

Related

Oracle CASE statement optimization

Is there a way to optimize this statement in terms of performance?
SELECT
CASE
WHEN A.COL1 IN ('A','F','G','K','L') THEN 'VALUE1'
WHEN A.COL1 IS NULL AND B.COL1 IN ('A','F','G','K','L') THEN 'VALUE1'
ELSE NULL
AS VALUES_COLUMN
FROM
TABLE A LEFT JOIN TABLE B ON A.COD = B.COD
I was thinking about using an OR expression to avoid code redundance and reduce time comparison, like that:
SELECT
CASE
WHEN A.COL1 IN ('A','F','G','K','L') OR B.COL1 IN ('A','F','G','K','L') THEN 'VALUE1'
ELSE NULL
AS VALUES_COLUMN
FROM
TABLE A LEFT JOIN TABLE B ON A.COD = B.COD
Thanks
I don't know if this is "optimized" (could mean several different things), but it's shorter:
SELECT
CASE
WHEN NVL(A.COL1, B.COL1) IN ('A','F','G','K','L') THEN 'VALUE1'
ELSE NULL
AS VALUES_COLUMN
FROM
TABLE A LEFT JOIN TABLE B ON A.COD = B.COD

INNER JOIN on multiple columns with multiple matches

How to find what rows got exact one match and what rows got more than one match in a INNER JOIN ?
SELECT A.Col1, B.Col2 FROM A INNER JOIN B
ON A.Col3 = B.Col3 AND A.Col4 = B.Col4;
As we know INNER JOIN returns rows with minimum one match, so to again reiterate my qustion, how to find which rows matched once and which rows got more than one match.
Regards,
Sachin
You could use a window function to count how many records are coming from B:
SELECT A.Col1, B.Col2, Count(*) OVER (PARTITION BY b.col3, b.col4) as bcount
FROM A
INNER JOIN B
ON A.Col3 = B.Col3 AND A.Col4 = B.Col4;
With the help of JNevill's inputs, here is a working example of what I was looking for. I want to thank JNevill once again.
create table A (col1 number, col3 number, col4 number, col5 number, col6 number);
create table B (col2 number, col3 number, col4 number, col5 number, col6 number);
insert into A values (1,2,3, 4, 5);
insert into A values (2,3,4,5,6);
insert into B values (3,4,5,6,7);
insert into B values (4,2,3,4,5);
insert into B values (5,2,3,8,9);
insert into B values (6,3,4,5,6);
insert into B values (7,3,4,5,6);
SELECT Col1 FROM(
SELECT A.Col1,B.Col2, A.Col3, A.Col4, A.Col5 ,A.Col6, Count(*) OVER (PARTITION BY B.col3, B.col4, B.col5, B.col6) as bcount
FROM A
INNER JOIN B
ON A.Col3 = B.Col3 AND A.Col4 = B.Col4 AND A.Col5 = B.Col5 AND A.Col6 = B.Col6) WHERE BCOUNT = 1;
So, I was looking for a column from table A which has exact one match for all the joining columns in table B.
Regards.

joining based on columns priority

I want to join 2 tables based on columns priority
ex. Suppose Table1 has six columns(Col1,Col2,Col3,Col4,Col5,Col6)
If i want to join Table 1 with table2 (Col1,Col2,Col3,Col4,Col5,Col7), it should
otherwise
Select Table2.col7
where
first check col1 , col2 and col3 if match found no need go check more
second check col1 , col2 if match found no need go check more
third check col1 if match found no need go check more
last ignore all col1 , col2 and col3
AND Table1.Col4=Table2.Col4
AND Table1.Col5=Table2.Col5
I may not be clear with my words, if any concern please shout
You cannot tell SQL to try to join on a certain condition first and in case it finds no match to go on searching. What you can do is join all allowed combinations (matches on col4 and col5 in your case) and then rank your matches (such that a match on col1 and col2 and col3 is considered best etc.). Then only keep the best matches:
select col7
from
(
select
t1.*,
t2.*,
row_number() over
(
partition by t1.col4, t1.col5
order by case
when t2.col1 = t1.col1 and t2.col2 = t1.col2 and t2.col3 = t1.col3 then 1
when t2.col1 = t1.col1 and t2.col2 = t1.col2 then 2
when t2.col1 = t1.col1 then 3
else 4
) as rn
from table1 t1
join table2 t2 on t2.col4 = t1.col4 and t2.col5 = t1.col5
)
where rn = 1;
Select t2.col7
from Table1 t1 inner join Table2 t2
on
case
when t1.col1 = t2.col1 then 1
when t1.col2 = t2.col2 then 1
when t1.col3 = t2.col3 then 1
when t1.Col4=t2.Col4
and t1.Col5=t2.Col5 then 1
else 0 end = 1
;

How to do a Full Outer Join using old style Oracle syntax

I have two tables with the same columns, Item Code and Qty, for each Table:
TABLE A TABLE B
-------------- -------------
X 2 X 1
Y 1 S 2
Z 5 Z 5
The result that I am aiming to get is something like this:
Table C
---------------
X 2 1
Y 1 0
S 0 2
I only need the items where qty differs in both tables (including the nulls which should be shown as zeroes.
Note: I am using Oracle8 so I can't use the ANSI FULL OUTER JOIN.
Edit, Since the question is specific to Oracle 8 which does not use ANSI syntax, the following should work:
select col1,
nvl(a_col2, 0) as a_col2,
nvl(b_col2, 0) as b_col2
from
(
select a.col1, a.col2 as a_col2, b.col2 as b_col2
from TableA a, TableB b
where a.col1 = b.col1(+)
union
select b.col1, a.col2 as a_col2, b.col2 as b_col2
from TableA a, TableB b
where a.col1(+) = b.col1
)
where a_col2 <> b_col2
or (a_col2 is null or b_col2 is null)
See SQL Fiddle with Demo. This will return:
| COL1 | A_COL2 | B_COL2 |
--------------------------
| S | 0 | 2 |
| X | 2 | 1 |
| Y | 1 | 0 |
If you are using a version of Oracle that supports ANSI syntax then you can use the following FULL OUTER JOIN:
select
coalesce(a.col1, b.col1) col1,
coalesce(a.col2, 0) a_col2,
coalesce(b.col2, 0) b_col2
from tablea a
full outer join tableb b
on a.col1 = b.col1
where a.col2 <> b.col2
or (a.col2 is null or b.col2 is null);
See SQL Fiddle with Demo
Another writing of the query which should work in 8 and (probably earlier versions).
It uses neither FULL JOIN not the horrible (+) syntax for joins so it should work even when an upgrade deprecates it.
Assuming that there are no Nulls already on the tables, you won't need COALESCE() or NVL() either:
SELECT a.col1,
a.col2 AS a_col2,
b.col2 AS b_col2
FROM TableA a, TableB b
WHERE a.col1 = b.col1
AND ( a.col2 <> b.col2
OR a.col2 IS NULL
OR b.col2 IS NULL
)
UNION ALL
SELECT col1, col2, 0
FROM TableA a
WHERE NOT EXISTS
( SELECT *
FROM TableB b
WHERE a.col1 = b.col1
)
UNION ALL
SELECT col1, 0, col2
FROM TableB b
WHERE NOT EXISTS
( SELECT *
FROM TableA a
WHERE a.col1 = b.col1
) ;
Tests at SQL-Fiddle
select code, nvl(a.qty,0) a, nvl(b.qty,0) b
from tableA a full join tableB b using(code)
where decode(a.qty, b.qty, 0) is null
fiddle
Another option:
select
full_list.item_code,
nvl(table_a.qty,0) table_a_qty,
nvl(table_b.qty,0) table_b_qty
from
(select item_code from table_a
union
select item_code from table_b) full_list,
table_a,
table_b
where
full_list.item_code = table_a.item_code(+) and
full_list.item_code = table_b.item_code(+)
The following query should work just right:
SELECT * FROM (
SELECT nvl(a.c1, b.c2), nvl(a.col1, 0) qty1, nvl(b.col2, 0) qty2 FROM a FULL OUTER JOIN b ON a.c1 = b.c2
) where qty1 != qty2;
http://sqlfiddle.com/#!4/d37ff/5/0

Multiple select for aleady joined table

Below is a part of my select query. In the same query I am selecting COLUMN_1 from a table TABLE2 with condition check. Also I am joining this table at end with one of the condition in the inner select as below. Can we have any other way to handle this situation with out using multiple `SELECT inside.
SELECT
T1.COLUMN_1
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='A' AND COLUMN_11=T2.COLUMN_11)
, T1.COLUMN_2
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='B' AND COLUMN_11=T2.COLUMN_11)
, T1.COLUMN_3
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='C' AND COLUMN_11=T2.COLUMN_11)
, T1.COLUMN_4
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='D' AND COLUMN_11=T2.COLUMN_11)
, T1.COLUMN_5
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='E' AND COLUMN_11=T2.COLUMN_11)
, T1.COLUMN_6
, (SELECT COLUMN_1 FROM TABLE2 WHERE COLUMN_22 ='F' AND COLUMN_11=T2.COLUMN_11)
FROM TABLE1 T1, TABLE2 T2
-- plus two more tables
--plus some other conditions
WHERE T1.COLUMN_11=T2.COLUMN_11
Use CASE instead:
SELECT T1.COLUMN_1
,CASE
WHEN T2.COLUMN_22 = 'A'
THEN T2.COLUMN_1
END
,T1.COLUMN_2
,CASE
WHEN T2.COLUMN_22 = 'B'
THEN T2.COLUMN_1
END
,T1.COLUMN_3
,CASE
WHEN T2.COLUMN_22 = 'C'
THEN T2.COLUMN_1
END
,T1.COLUMN_4
,CASE
WHEN T2.COLUMN_22 = 'D'
THEN T2.COLUMN_1
END
,T1.COLUMN_5
,CASE
WHEN T2.COLUMN_22 = 'E'
THEN T2.COLUMN_1
END
,T1.COLUMN_6
,CASE
WHEN T2.COLUMN_22 = 'F'
THEN T2.COLUMN_1
END
FROM TABLE1 T1
INNER JOIN TABLE2 T2 ON T1.COLUMN_11 = T2.COLUMN_11;
EDIT
I changed the query to use the ansi join syntax. But that change is irrelevant to what you are asking. You can keep your join syntax if you want. The only relevant change is in the SELECT portion of the query.
You won't have very clean solutions I think. Another possibility is with an inner join by case:
SELECT
T1.COLUMN_1,
T2_1.COLUMN_1,
T1.COLUMN_2,
T2_2.COLUMN_1
T1.COLUMN_3,
T2_3.COLUMN_1
T1.COLUMN_4,
T2_4.COLUMN_1
T1.COLUMN_5,
T2_5.COLUMN_1
T1.COLUMN_6,
T2_6.COLUMN_1
FROM TABLE1 T1
INNER JOIN TABLE2 T2_1 ON T1.COLUMN_11=T2_1.COLUMN_11 AND T2_1.COLUMN_22 = 'A',
INNER JOIN TABLE2 T2_2 ON T1.COLUMN_11=T2_2.COLUMN_11 AND T2_2.COLUMN_22 = 'B',
INNER JOIN TABLE2 T2_3 ON T1.COLUMN_11=T2_3.COLUMN_11 AND T2_3.COLUMN_22 = 'C',
INNER JOIN TABLE2 T2_4 ON T1.COLUMN_11=T2_4.COLUMN_11 AND T2_4.COLUMN_22 = 'D',
INNER JOIN TABLE2 T2_5 ON T1.COLUMN_11=T2_5.COLUMN_11 AND T2_5.COLUMN_22 = 'E',
INNER JOIN TABLE2 T2_6 ON T1.COLUMN_11=T2_6.COLUMN_11 AND T2_6.COLUMN_22 = 'F',
WHERE
etc...

Resources