I would like to change the syntax of an Oracl SQL Query,
The original query contains a join:
WHERE (A.ID = B.ID OR A.ID is null)
I would like to change this to LEFT OUTER JOIN or INNER JOIN syntax,
I tried the following but the query doesn't return the same results:
LEFT OUTER JOIN TABLEA A ON A.ID = B.ID
As an outsider, I can't say for sure, but I strongly suspect that the person who wrote
WHERE (A.ID = B.ID OR A.ID is null)
was trying to do an outer join and just got it completely wrong. That condition is not logically equivalent to an outer join.
Consider:
with a ( id, val ) as (
SELECT 1, 'a one' FROM DUAL UNION ALL
SELECT 2, 'a two' FROM DUAL UNION ALL
SELECT 3, 'a three' FROM DUAL UNION ALL
SELECT null, 'a null' FROM DUAL ),
b ( id, val ) as (
SELECT 2, 'b two' FROM DUAL UNION ALL
SELECT 3, 'b three' FROM DUAL UNION ALL
SELECT 4, 'b four' FROM DUAL UNION ALL
SELECT null, 'a null' FROM DUAL )
select a.id aid, a.val aval, b.id bid, b.val bval
from a, b
where ( a.id = b.id or a.id is null )
+-----+---------+-----+---------+
| AID | AVAL | BID | BVAL |
+-----+---------+-----+---------+
| 2 | a two | 2 | b two |
| 3 | a three | 3 | b three |
| | a null | 2 | b two |
| | a null | 3 | b three |
| | a null | 4 | b four |
| | a null | | a null |
+-----+---------+-----+---------+
... you can see the null value for a.id got joined to every row in table b. If there were no row in table a with a null id, there would have been no row having b.id = 4 (only the 1st two records above would have been returned).
An outer join of the same data should look like this:
select a.id aid, a.val aval, b.id bid, b.val bval from b left join a
on a.id = b.id order by a.id, b.id
+-----+---------+-----+---------+
| AID | AVAL | BID | BVAL |
+-----+---------+-----+---------+
| 2 | a two | 2 | b two |
| 3 | a three | 3 | b three |
| | | 4 | b four |
| | | | a null |
+-----+---------+-----+---------+
My advice: understand what the query is supposed to do. If the requirements of the query are to outer join, fix the query to be an outer join and ignore what it used to do -- it was wrong.
Related
So, to me comes Table2 (PK Text) filled with elements FIRST, SECOND, THIRD, FORTH.
I need to insert this element to TABLE1 where these elements are missing.
TABLE1
+----+--------+
| ID | Text |
+----+--------+
| A | FIRST |
| A | SECOND |
| A | THIRD |
| B | FIRST |
| B | THIRD |
| C | FIRST |
+----+--------+
So ID A misses FORTH
| A | FORTH |
Should be inserted
B misses SECOND and FORTH and so on
Answer should be something like that
+----+--------+
| ID | Text |
+----+--------+
| A | FIRST |
| A | SECOND |
| A | THIRD |
| A | FORTH |
| B | FIRST |
| B | SECOND |
| B | THIRD |
| B | FORTH |
| C | FIRST |
| C | SECOND |
| C | THIRD |
| C | FORTH |
+----+--------+
You can cross join the texts from table2 with the distinct ids availabel in table1 and filter on the missing tuples with a not exists condition, like so:
insert into table1(id, text)
select t1.id, t2.text
from table2 t2
cross join (select distinct id from table1) t1
where not exists (
select 1
from table1 t11
where t11.id = t1.id and t11.text = t2.text
)
Cross join the distinct IDs with the distinct Texts and left join the table to get the unmatched rows of the table:
INSERT INTO TABLE1 (ID, Text)
SELECT i.ID, d.Text
FROM (SELECT DISTINCT ID FROM TABLE1) i
CROSS JOIN (SELECT DISTINCT Text FROM TABLE1) d
LEFT JOIN TABLE1 t ON t.ID = i.ID AND t.Text = d.Text
WHERE t.ID IS NULL
See the demo.
If there is a case that any of the values 'FIRST', 'SECOND', 'THIRD' or 'FOURTH' is missing from the table then use this:
INSERT INTO TABLE1 (ID, Text)
SELECT i.ID, d.Text
FROM (SELECT DISTINCT ID FROM TABLE1) i
CROSS JOIN (
SELECT 'FIRST' Text UNION ALL SELECT 'SECOND'
UNION ALL SELECT 'THIRD' UNION ALL SELECT 'FOURTH'
) d
LEFT JOIN TABLE1 t ON t.ID = i.ID AND t.Text = d.Text
WHERE t.ID IS NULL
See the demo.
I would like to know if the following is possible without joining the same table twice:
Table A:
+----+------+
| ID | ColA |
+----+------+
| 1 | A1 |
| 2 | A2 |
| 3 | A3 |
| 4 | A4 |
+----+------+
Table B:
+----+------+
| ID | ColB |
+----+------+
| 1 | B1 |
| 2 | B2 |
| 3 | B3 |
| 4 | B4 |
| 5 | B5 |
| 6 | B6 |
+----+------+
Table C:
+----+
| ID |
+----+
| 1 |
| 2 |
+----+
Desired result: (A LEFT JOIN B WITHOUT C)
+----+------+------+
| ID | ColA | ColB |
+----+------+------+
| 3 | A3 | B3 |
| 4 | A4 | B4 |
+----+------+------+
So basically I need to add Column B to Table A, hence left join, and exclude all IDs which occur in Table C.
Current solution:
SELECT a.id, a.ColA, b.ColB
FROM tableA a
LEFT JOIN tableB b ON a.id = b.id
WHERE a.id NOT IN(
SELECT a2.id FROM tableA a2
LEFT JOIN tableC c on a2.id = c.id)
What's irritating me is, that the exclusion of table C requires an additional left join of table A with table C. Isn't there a more straight-forward approach, without having to join table A again as part of the subquery, if all I want to do is to exclude IDs which occur in table C from the resultset?.
Thanks
Use a not exists:
SELECT a.id, a.ColA, b.ColB
FROM tableA a
LEFT JOIN tableB b ON a.id = b.id
where not exists(select 1 from tablec c where a.id = c.id)
The issue with using a not in with a select in Oracle is that:
a) it has to return the whole subquery dataset
b) if there are nulls, it breaks
TOM link regarding these 2 issues
won't this work?
SELECT a.id, a.ColA, b.ColB
FROM tableA a
JOIN tableB b ON a.id = b.id
WHERE a.id NOT IN (SELECT c.Id FROM tableC c)
this can also be done in a join
SELECT a.id, a.ColA, b.ColB
FROM tableA a
JOIN tableB b ON a.id = b.id
LEFT JOIN tableC C ON a.id = c.id
WHERE c.Id is null
[MS SQL 2008]
I have tables (all columns are string names):
A: two columns relating some datafield to an owning entity
B: three columns defining a hierarchy of entities
I need to create a singe table of the whole hierarchy (including all rows not existing in both tables), but the key column in table A (shown as Acol2) can be in either column 1 or 2 of table B...
A: B:
Acol1 | Acol2 Bcol1 | Bcol2 | Bcol3
-------+------ --------+-------+------
A | B B | X | Y
C | D Q | X | Y
E | F H | D | Z
G | H W | V | U
The output should be
Hierarchy:
Acol1 | Bcol1 | Bcol2 | Bcol3
-------+-------+-------+------
A | B | X | Y
Null | Q | X | Y
C | Null | D | Z
G | H | D | Z
E | Null | Null | Null
Null | W | V | U
Logic (also added to original):
If A has no record in B, show A with all Null
If A has record in Bcol1, show A with full row B
If A has record in Bcol2, show A with Null, Bcol2, Bcol3
If B has no record in A, show B with Null for Acol1
I have tried all sorts of UNIONs of two separate JOINs, but can't seem to get rid of extraneous rows...
B LEFT JOIN A ON Acol2=Bcol1 UNION B LEFT JOIN A ON Acol2=Bcol2;
gives duplicate rows, as the second part of the union has to set Bcol1 to NULL
(perhaps one solution is a way to remove this duplicate NULL row?)
B INNER JOIN A ON Acol2=Bcol1 UNION B INNER JOIN A ON Acol2=Bcol2;
Obviously removes all the rows from A and B that have no shared keys
(solution as to easy way to regain just those rows?)
Any idea appreciated!
To play:
[SQL removed - see fiddle in reply comments]
SELECT
Table1.ACol1,
CASE WHEN Table1.ACol1 = Table2.BCol1 THEN Table2.BCol1 ELSE NULL END AS BCol1
Table2.BCol2,
Table2.BCol3
FROM
Table1
FULL OUTER JOIN
Table2
ON Table1.ACol2 IN (Table2.BCol1, Table2.BCol2)
When you say no duplicates, this is only possible if ACol2 only ever appears in one field of one row in Table2. If it appears in multiple places, you'll get duplication.
- If that's possible, how would you want to chose which record from Table2?
Also, in general, however, this is a SQL-Anti-Pattern.
This is because the join would prefer an index on Table2. But, since you never know which field you're joining on, no single index will ever satsify the join condition.
EDIT:
What would make this significantly faster is to create a normalised TableB...
B_ID | B_Col | B_Val
------+-------+-------
1 | 1 | B
1 | 2 | X
1 | 3 | Y
2 | 1 | Q
2 | 2 | X
2 | 3 | Y
3 | 1 | H
3 | 2 | D
3 | 3 | Z
4 | 1 | W
4 | 2 | V
4 | 3 | U
Then index that table with (B_ID) and on (B_Val)...
Then include the B_ID field in the non_normalised table...
ID | Bcol1 | Bcol2 | Bcol3
------+-------+-------+-------
1 | B | X | Y
2 | Q | X | Y
3 | H | D | Z
4 | W | V | U
Then use the following query...
SELECT
Table1.ACol1,
CASE WHEN Table1.ACol1 = Table2.BCol1 THEN Table2.BCol1 ELSE NULL END AS BCol1
Table2.BCol2,
Table2.BCol3
FROM
(
Table1
LEFT JOIN
Table2Normalised
ON Table2Normalised.B_Val = Table1.ACol2
AND Table2Normalised.B_Col IN (1,2)
)
FULL OUTER JOIN
Table2
ON Table2Normalised.B_ID = Table2.ID
EDIT:
Without changing the schema, and instead having one index on BCol1 and a second index on Bcol2...
SELECT ACol1, BCol1, BCol2, BCol3 FROM Table1 a INNER JOIN Table2 b ON a.ACol2 = b.BCol1
UNION ALL
SELECT ACol1, NULL, BCol2, BCol3 FROM Table1 a INNER JOIN Table2 b ON a.ACol2 = b.BCol2
UNION ALL
SELECT ACol1, NULL, NULL, NULL FROM Table1 a WHERE NOT EXISTS (SELECT * FROM Table2 WHERE BCol1 = a.ACol2)
AND NOT EXISTS (SELECT * FROM Table2 WHERE BCol2 = a.ACol2)
UNION ALL
SELECT NULL, BCol1, BCol2, BCol3 FROM Table2 b WHERE NOT EXISTS (SELECT * FROM Table1 WHERE ACol2 = b.BCol1)
AND NOT EXISTS (SELECT * FROM Table1 WHERE ACol2 = b.BCol2)
But that's pretty messy...
I was wondering how I could go by writing a the outer join query to get the required outputs (described below)
where the tables I am outer joining are part of the other join conditions in the statement
given the following datastructure where
- Table A is the main table containing some arbitrary objects
- Table B is referenced by A where A.TYPE_ID = B.ID
- Table C defininfs relations between the objects in Table A where C.SOURCE_ID references A.ID and C.TARGET_ID references A.ID
This is how the schema is defined and I can't do anything about it (it's a legacy system)
TABLE_A
---------------------------
| ID | TYPE_ID | Name |
|-------------------------|
| 1 | 1 | Name 1 |
| 2 | 2 | Name 2 |
| 3 | 1 | Name 3 |
| 4 | 1 | Name 4 |
| 5 | 3 | Name 5 |
|-------------------------|
TABLE_B
----------------------
| ID | TYPE_NAME |
|--------------------|
| 1 | Type 1 |
| 2 | Type 2 |
| 3 | Type 3 |
| 4 | Type 4 |
|--------------------|
TABLE_C
-------------------------------
| PK | SOURCE_ID | TARGET_ID |
|-----------------------------|
| 11 | 2 | 1 |
| 12 | 2 | 3 |
| 13 | 5 | 1 |
| 13 | 5 | 4 |
-------------------------------
What I would like to get is all the objects in Table A of "Type 1" with the name of the object they are associated to (null otherwise) which are of Type 2,
i.e an outer join to get all the objects of Type 1 regardless if they have an association, but if they do then I need the name of the object.
Note that objects of Type 1 will always been in the TARGET in the relstionship.
The output for the above example would be
-------------------------------
| Target Name | Source Name |
|-----------------------------|
| Name 1 | Name 2 |
| Name 3 | Name 2 |
| Name 4 | (NULL) |
|-----------------------------|
My original join query (couldn't get the outer join to work) this is the normal join not showing objects with no associations.
select atrgt.NAME, asrc.NAME
from TABLE_A atrgt
JOIN TABLE_B trgttype on atrgt.TYPE_ID = trgttype.ID
and trgttype.TYPE_NAME = 'Type 1'
JOIN TABLE_C assoc on atrgt.ID = assoc.TARGET_ID
JOIN TABLE_A asrc on asrc.ID = assoc.SOURCE_ID
JOIN TABLE_B srctype on asrc.TYPE_ID = srctype.ID
and srctype.TYPE_NAME = 'Type 2'
Basically in these situations I think the best approach is to subdivide the query into two normal joins, then do the outer join between those results sets. If you think of SQL as procedural code, you may think it looks inefficient, but the query optimizer will not necessarily run the two subjoins independently.
You didn't say what RDBMS you are using. In Oracle I would probably write it like this:
with
src_type_2 as (
select c.target_id, a.name
from table_c c
join table_a on a.id = c.source_id
join table_b on b.id = a.type_id
where b.type_name = 'Type 2'
),
all_type_1 as (
select a.id, a.name
from table_a a
join table_b on b.id = a.type_id
where b.type_name = 'Type 1'
)
select tgt.name, src.name
from all_type_1 tgt
left join src_type_2 src on src.target_id = tgt.id
Try
select atrgt.NAME, baseview.NAME
from TABLE_A atrgt
JOIN TABLE_B trgttype on atrgt.TYPE_ID = trgttype.ID
and trgttype.TYPE_NAME = 'Type 1'
JOIN TABLE_C assoc on atrgt.ID = assoc.TARGET_ID
LEFT JOIN (
TABLE_A asrc on asrc.ID = assoc.SOURCE_ID
JOIN TABLE_B srctype on asrc.TYPE_ID = srctype.ID
and srctype.TYPE_NAME = 'Type 2'
) as baseview
I think this should work:
SELECT
TGT.NAME, SRC_TYPE.TYPE_NAME
FROM TABLE_A TGT
JOIN TABLE_B TGT_TYPE ON TGT.TYPE_ID = TGT_TYPE.ID
LEFT JOIN TABLE_C REL ON TGT.ID = REL.TARGET_ID
LEFT JOIN TABLE_A SRC ON REL.SOURCE_ID = SRC.ID
LEFT JOIN TABLE_B SRC_TYPE ON SRC_TYPE.ID = SRC.TYPE_ID
WHERE TGT_TYPE.TYPE_NAME = 'Type 1' AND COALESCE(SRC_TYPE.TYPE_NAME, 'Type 2') = 'Type 2'
If you're using Oracle, you could replace the COALESCE with NVL(SRC_TYPE.TYPE_NAME, 'Type 2').
I have 2 unrelated tables A and B which both have foreign key constraints on C. I need to run an sql query that determines if either A or B contain a given id of C. My first approach was to use the union all but A and B are not related thus it will not work.
Any ideas?
Select 1
From DUAL
Where Exists ( Select null From Table_A Where a.fk = :id ) OR
Exists ( Select null From Table_B Where b.fk = :id );
You could indeed use union, why not? But why use UNION ALL, and not just UNION? Just pick the one common column:
SELECT 1
FROM
(select A.fk from A inner join C on A.FK = C.pk
UNION
select B.fk from B inner join C on B.FK = C.pk) AS bothTables
WHERE fk = 'desiredValue';
This would work just nicely.
Tested it on the following tables in MySQL, with myValue = 1, just to verify.
mysql> select * from A;
+------+--------+------+
| pk | value | fk |
+------+--------+------+
| 1 | ape | 2 |
| 2 | fjfjfj | 3 |
+------+--------+------+
2 rows in set (0.00 sec)
mysql> select * from B;
+------+--------+------+
| pk | value | fk |
+------+--------+------+
| 1 | katt | 1 |
| 2 | fjfjfj | 3 |
+------+--------+------+
2 rows in set (0.00 sec)
mysql> select * from C;
+------+-------+
| pk | value |
+------+-------+
| 1 | hei |
| 2 | nei |
| 3 | jeg |
+------+-------+
3 rows in set (0.00 sec)
Not 100% sure what you're asking but if you mean to return the elements of A and B that match the id in C then this will do it.
Select c.*, a.*, b.*
From c.id
Left Outer Join a On a.id = c.id
Left Outer Join b On b.id = c.id
Where c.id = #somevalue and (a.id Is Not Null or b.id Is Not Null)
Where #somevalue is the value you're looking for.