SQL conditional join to multiple tables in one shot - sql

I would like to make conditional join of three tables. Left join table B and if the key is missing then join to table C.
+------+--+------+-------------+--+------+-------------+
| A.id | | B.id | B.OtherFish | | C.id | C.OtherFish |
+------+--+------+-------------+--+------+-------------+
| 1 | | 1 | B1 | | 1 | C1 |
| 2 | | | | | 2 | C2 |
| 3 | | 3 | B3 | | 3 | C3 |
| 4 | | | | | 4 | C4 |
| 5 | | 5 | B5 | | | |
| 6 | | 6 | B6 | | 6 | C6 |
+------+--+------+-------------+--+------+-------------+
There are no matching keys for 2 and 4 in B, and no keys for 5 in C.
Expected results:
+------+-----------+
| A.id | OtherFish |
+------+-----------+
| 1 | B1 |
| 2 | C2 |
| 3 | B3 |
| 4 | C4 |
| 5 | B5 |
| 6 | B6 |
+------+-----------+
The query I use is:
select
A.id
,coalesce(B.id,C.id)
,coalesce(B.OtherFish,C.OtherFish)
from A
left join B
on A.id=B.id
left join C
on A.id=C.id
The drawback of the approach is that I have to use coalesce through all the columns I need from different tables. It is annoying if there are hundred columns in the table. It would be desirable to make a coalesce on the whole aliases like coalesce(B,C).
Is it possible to make it in one shot like:
left join B
on A.id=B.id
left join C
on A.id=(case when B.id is null then C.id end)
so that in C.id I would have all good data without making coalesce through all the columns?

Although I don't see anything wrong with COALESCE , you can try using UNION , something like this:
SELECT a.id,t.OtherFish
FROM A
LEFT JOIN(SELECT b.id,b.otherFish FROM b
UNION ALL
SELECT c.id,c.otherFish FROM C
where c.id NOT EXISTS(SELECT 1 FROM b bb WHERE bb.id = c.id)) t
ON(a.id = t.id)

Related

SQL: Update with all possible combinations

I have a relation
+-----+----+
| seq | id |
+-----+----+
| 1 | A1 |
| 2 | B1 |
| 3 | C1 |
| 4 | D1 |
+-----+----+
and want to join it in PostgreSQL with
+----+-------+
| id | alter |
+----+-------+
| B1 | B2 |
| D1 | D2 |
+----+-------+
so I get all possible combinations of replacement (i.e. the Cartesian product of replacing more or less). So group 1 has no update,group 2 only B2, group 3 only D2 and group 4 both B2 and D2.
The end should look like this, but should be open to more (like an extra D3 for D1)
+-------+-----+----+
| group | seq | id |
+-------+-----+----+
| 1 | 1 | A1 |
| 1 | 2 | B1 |
| 1 | 3 | C1 |
| 1 | 4 | D1 |
| 2 | 1 | A1 |
| 2 | 2 | B2 |
| 2 | 3 | C1 |
| 2 | 4 | D1 |
| 3 | 1 | A1 |
| 3 | 2 | B1 |
| 3 | 3 | C1 |
| 3 | 4 | D2 |
| 4 | 1 | A1 |
| 4 | 2 | B2 |
| 4 | 3 | C1 |
| 4 | 4 | D2 |
+-------+-----+----+
EDIT:
Another possible replacement table could be
+----+-------+
| id | alter |
+----+-------+
| B1 | B2 |
| D1 | D2 |
| D1 | D3 |
+----+-------+
could should result in 6 groups (I hope I haven't forgot a case)
+-------+-----+----+
| group | seq | id |
+-------+-----+----+
| 1 | 1 | A1 |
| 1 | 2 | B1 |
| 1 | 3 | C1 |
| 1 | 4 | D1 |
| 2 | 1 | A1 |
| 2 | 2 | B2 |
| 2 | 3 | C1 |
| 2 | 4 | D1 |
| 3 | 1 | A1 |
| 3 | 2 | B2 |
| 3 | 3 | C1 |
| 3 | 4 | D2 |
| 4 | 1 | A1 |
| 4 | 2 | B2 |
| 4 | 3 | C1 |
| 4 | 4 | D3 |
| 5 | 1 | A1 |
| 5 | 2 | B1 |
| 5 | 3 | C1 |
| 5 | 4 | D2 |
| 6 | 1 | A1 |
| 6 | 2 | B1 |
| 6 | 3 | C1 |
| 6 | 4 | D3 |
+-------+-----+----+
If you have instead three replacements like
+----+-------+
| id | alter |
+----+-------+
| B1 | B2 |
| C1 | C2 |
| D1 | D3 |
+----+-------+
It'll result in 8 groups.
What I tried so far was not really helpful:
WITH a as (SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1') ) as a1(seq, id) )
, b as (SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter) )
---------
SELECT row_number() OVER (PARTITION BY a.id) as g, * FROM
a
CROSS JOIN b as b1
CROSS JOIN b as b2
LEFT JOIN b as b3 ON a.id=b3.id
ORDER by g,seq;
So group 1 has no update,group 2 only B2, group 3 only D2 and group 4
both B2 and D2.
Since the logic of this statement is not in a table, I decided to add this logic to table c, which is adding 3 new columns to the existing table a, depending on which selection of field had to be considered.
WITH a as (SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1') ) as a1(seq, id) )
, b as (SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter) )
, c as (
SELECT a.seq, a.id,
COALESCE(b1.alter,a.id) as id2,
COALESCE(b2.alter,a.id) as id3,
COALESCE(b3.alter,a.id) as id4
FROM a
LEFT JOIN (SELECT * FROM b WHERE b.alter='B2') b1 ON a.id = b1.id
LEFT JOIN (SELECT * FROM b WHERE b.alter='D2') b2 ON a.id = b2.id
LEFT JOIN (SELECT * FROM b WHERE b.alter IN ('B2','D2')) b3 ON a.id = b3.id)
, d as (SELECT * FROM (values (1),(2), (3), (4) ) as d1(gr) )
SELECT d.gr,
CASE d.gr
WHEN 1 THEN c.id
WHEN 2 THEN c.id2
WHEN 3 THEN c.id3
WHEN 4 THEN c.id4 END as id
FROM d
CROSS JOIN c
ORDER by d.gr, c.seq
What you need
After additional info from your comments, it seems like this is your case:
You have toll stations with a given number of booths:
CREATE TABLE station (
station text PRIMARY KEY
, booths int NOT NULL -- number of cashiers in station
);
INSERT INTO station VALUES
('A', 1)
, ('B', 2)
, ('C', 1)
, ('D', 3);
For a given route, say A --> B --> C --> D you want to generate all possible paths, taking booth numbers into account. I suggest an SQL function with a recursive CTE like:
CREATE OR REPLACE FUNCTION f_pathfinder(_route text[])
RETURNS TABLE (grp int, path text[]) LANGUAGE sql STABLE PARALLEL SAFE AS
$func$
WITH RECURSIVE rcte AS (
SELECT cardinality($1) AS hops, 1 AS hop, ARRAY[s.station || booth] AS path
FROM station s, generate_series(1, s.booths) booth
WHERE s.station = $1[1]
UNION ALL
SELECT r.hops, r.hop + 1, r.path || (s.station || booth)
FROM rcte r
JOIN station s ON s.station = _route[r.hop + 1], generate_series(1, s.booths) booth
WHERE r.hop < r.hops
)
SELECT row_number() OVER ()::int AS grp, path
FROM rcte r
WHERE r.hop = r.hops;
$func$;
Simple call:
SELECT * FROM f_pathfinder('{A,B,C,D}'::text[]);
Result:
grp | path
---: | :--------
1 | {1,1,1,1}
2 | {1,1,1,2}
3 | {1,1,1,3}
4 | {1,2,1,1}
5 | {1,2,1,2}
6 | {1,2,1,3}
Or with unnested arrays (result like you show in question):
SELECT grp, seq, booth
FROM f_pathfinder('{A,B,C,D}'::text[])
, unnest(path) WITH ORDINALITY AS x(booth, seq); -- ①
Result:
grp | seq | booth
--: | --: | :----
1 | 1 | A1
1 | 2 | B1
1 | 3 | C1
1 | 4 | D1
2 | 1 | A1
2 | 2 | B1
2 | 3 | C1
2 | 4 | D2
3 | 1 | A1
3 | 2 | B1
3 | 3 | C1
3 | 4 | D3
4 | 1 | A1
4 | 2 | B2
4 | 3 | C1
4 | 4 | D1
5 | 1 | A1
5 | 2 | B2
5 | 3 | C1
5 | 4 | D2
6 | 1 | A1
6 | 2 | B2
6 | 3 | C1
6 | 4 | D3
db<>fiddle here
The number of variants is growing quickly with the number of stops in your route. It's M1*M2* .. *Mn with Mn being the number of booths for the nth station.
① About ORDINALITY:
PostgreSQL unnest() with element number
What you asked (originally)
Seems like you want to apply all possible combinations from the set of changes listed in the replacement table rpl to the target table tbl.
With just two rows, forming the 4 (2^n) possible combinations is simple. For a general solution, I suggest a basic combinatorics function to generate all combinations. There are innumerable ways. Here is a pure SQL function:
CREATE OR REPLACE FUNCTION f_allcombos(_len int)
RETURNS SETOF bool[] LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
$func$
WITH RECURSIVE
tf(b) AS (VALUES (false), (true))
, rcte AS (
SELECT 1 AS lvl, ARRAY[b] AS arr
FROM tf
UNION ALL
SELECT r.lvl + 1, r.arr || tf.b
FROM rcte r, tf
WHERE lvl < _len
)
SELECT arr
FROM rcte
WHERE lvl = _len;
$func$;
Similar to what's discussed here:
Generate all combinations of given list of elements, sorted
Example for just 2 replacement rows:
SELECT * FROM f_allcombos(2);
{f,f}
{t,f}
{f,t}
{t,t}
Query
WITH effective_rpl AS ( -- ①
SELECT *, count(alter) OVER (ORDER BY seq) AS idx -- ②
FROM tbl LEFT JOIN rpl USING (id)
)
SELECT c.grp, e.seq
, CASE WHEN alter IS NOT NULL AND c.arr[e.idx] THEN e.alter -- ③
ELSE e.id END AS id
FROM effective_rpl e
, f_allcombos((SELECT count(alter)::int FROM effective_rpl)) -- ④
WITH ORDINALITY AS c(arr, grp); -- ⑤
Produces your desired result exactly.
db<>fiddle here
① Some of the replacements may have no match in the target table; so determine effective replacements to begin with.
② count() only counts non-null values, so this can serve as index for the 1-based array returned from f_allcombos().
③ Only replace when a replacement is available, and the boolean array has true for the given index idx.
④ The CROSS JOIN multiplies the set of rows in the target table with the number of possible replacement combinations
⑤ I use WITH ORDINALITY to generate "group numbers". See:
PostgreSQL unnest() with element number
We might wire that into the function directly, but I'd rather keep it generic.
Aside: "alter" is non-reserved in Postgres but a reserved word in standard SQL.
answer updated after edit to question
The tricky part in this problem is to generate the powerset of the replacements. However, luckily postgres supports recursive queries & powersets can be computed recursively. Thus we can build a general solution to this problem that will work regardless of the size of your replacement set.
Lets call the first table source, the 2nd table replacements, and I'll avoid the unsavory name alter for something else:
CREATE TABLE source (seq, id) as (
VALUES (1, 'A1'), (2, 'B1'), (3, 'C1'), (4, 'D1')
);
CREATE TABLE replacements (id, sub) as (
VALUES ('B1', 'B2'), ('D1', 'D2')
);
First powerset of the ids to be replaced needs to be generated. The null-set may be omitted since that won't work with joins anyhow & at the end the source table can be union'ed to the intermediate result to provide the same output.
In the recursive step, the the JOIN condition rec.id > repl.id ensures that each id is present only once for each generated subset.
In the final step:
the cross join fans out the source N times, where N is the number of non-empty combinations of replacements (with variations)
the group names are generated using a filtered runnign sum on seq.
the subsets are unnested & the ids replaced using a coalesce if the replacement id equals the source id.
WITH RECURSIVE rec AS (
SELECT ARRAY[(id, sub)] subset, id FROM replacements
UNION ALL
SELECT subset || (repl.id, sub), repl.id
FROM replacements repl
JOIN rec ON rec.id > repl.id
)
SELECT NULL subset, 0 set_name, seq, id FROM source
UNION ALL
SELECT subset
, SUM(seq) FILTER (WHERE seq = 1) OVER (ORDER BY subset, seq) set_name
, seq
, COALESCE(sub, source.id) id
FROM rec
CROSS JOIN source
LEFT JOIN LATERAL (
SELECT id, sub
FROM unnest(subset) x(id TEXT, sub TEXT)
) x ON source.id = x.id;
Tests
With replacement values ('B1', 'B2'), ('D1', 'D2'), the query returns 4 groups.
subset | set_name | seq | id
-----------------------+----------+-----+----
| 0 | 1 | A1
| 0 | 2 | B1
| 0 | 3 | C1
| 0 | 4 | D1
{"(B1,B2)"} | 1 | 1 | A1
{"(B1,B2)"} | 1 | 2 | B2
{"(B1,B2)"} | 1 | 3 | C1
{"(B1,B2)"} | 1 | 4 | D1
{"(D1,D2)"} | 2 | 1 | A1
{"(D1,D2)"} | 2 | 2 | B1
{"(D1,D2)"} | 2 | 3 | C1
{"(D1,D2)"} | 2 | 4 | D2
{"(D1,D2)","(B1,B2)"} | 3 | 1 | A1
{"(D1,D2)","(B1,B2)"} | 3 | 2 | B2
{"(D1,D2)","(B1,B2)"} | 3 | 3 | C1
{"(D1,D2)","(B1,B2)"} | 3 | 4 | D2
(16 rows)
With replacement values ('B1', 'B2'), ('D1', 'D2'), ('D1', 'D3'), The query returns 6 groups:
subset | set_name | seq | id
-----------------------+----------+-----+----
| 0 | 1 | A1
| 0 | 2 | B1
| 0 | 3 | C1
| 0 | 4 | D1
{"(B1,B2)"} | 1 | 1 | A1
{"(B1,B2)"} | 1 | 2 | B2
{"(B1,B2)"} | 1 | 3 | C1
{"(B1,B2)"} | 1 | 4 | D1
{"(D1,D2)"} | 2 | 1 | A1
{"(D1,D2)"} | 2 | 2 | B1
{"(D1,D2)"} | 2 | 3 | C1
{"(D1,D2)"} | 2 | 4 | D2
{"(D1,D2)","(B1,B2)"} | 3 | 1 | A1
{"(D1,D2)","(B1,B2)"} | 3 | 2 | B2
{"(D1,D2)","(B1,B2)"} | 3 | 3 | C1
{"(D1,D2)","(B1,B2)"} | 3 | 4 | D2
{"(D1,D3)"} | 4 | 1 | A1
{"(D1,D3)"} | 4 | 2 | B1
{"(D1,D3)"} | 4 | 3 | C1
{"(D1,D3)"} | 4 | 4 | D3
{"(D1,D3)","(B1,B2)"} | 5 | 1 | A1
{"(D1,D3)","(B1,B2)"} | 5 | 2 | B2
{"(D1,D3)","(B1,B2)"} | 5 | 3 | C1
{"(D1,D3)","(B1,B2)"} | 5 | 4 | D3
(24 rows)
With replacement values ('B1', 'B2'), ('C1', 'C2'), ('D1', 'D2'), The query returns 8 groups:
subset | set_name | seq | id
---------------------------------+----------+-----+----
| 0 | 1 | A1
| 0 | 2 | B1
| 0 | 3 | C1
| 0 | 4 | D1
{"(B1,B2)"} | 1 | 1 | A1
{"(B1,B2)"} | 1 | 2 | B2
{"(B1,B2)"} | 1 | 3 | C1
{"(B1,B2)"} | 1 | 4 | D1
{"(C1,C2)"} | 2 | 1 | A1
{"(C1,C2)"} | 2 | 2 | B1
{"(C1,C2)"} | 2 | 3 | C2
{"(C1,C2)"} | 2 | 4 | D1
{"(C1,C2)","(B1,B2)"} | 3 | 1 | A1
{"(C1,C2)","(B1,B2)"} | 3 | 2 | B2
{"(C1,C2)","(B1,B2)"} | 3 | 3 | C2
{"(C1,C2)","(B1,B2)"} | 3 | 4 | D1
{"(D1,D2)"} | 4 | 1 | A1
{"(D1,D2)"} | 4 | 2 | B1
{"(D1,D2)"} | 4 | 3 | C1
{"(D1,D2)"} | 4 | 4 | D2
{"(D1,D2)","(B1,B2)"} | 5 | 1 | A1
{"(D1,D2)","(B1,B2)"} | 5 | 2 | B2
{"(D1,D2)","(B1,B2)"} | 5 | 3 | C1
{"(D1,D2)","(B1,B2)"} | 5 | 4 | D2
{"(D1,D2)","(C1,C2)"} | 6 | 1 | A1
{"(D1,D2)","(C1,C2)"} | 6 | 2 | B1
{"(D1,D2)","(C1,C2)"} | 6 | 3 | C2
{"(D1,D2)","(C1,C2)"} | 6 | 4 | D2
{"(D1,D2)","(C1,C2)","(B1,B2)"} | 7 | 1 | A1
{"(D1,D2)","(C1,C2)","(B1,B2)"} | 7 | 2 | B2
{"(D1,D2)","(C1,C2)","(B1,B2)"} | 7 | 3 | C2
{"(D1,D2)","(C1,C2)","(B1,B2)"} | 7 | 4 | D2
(32 rows)
I can only think of a brute force approach. Enumerate the groups and multiply the second table -- so one set of rows for each group.
The following then uses bit manipulation to choose which value:
WITH a as (
SELECT * FROM (values (1,'A1'),(2,'B1'), (3,'C1'), (4,'D1') ) as a1(seq, id)
),
b as (
SELECT * FROM (values ('B1','B2'), ('D1','D2')) as b1(id,alter)
),
bgroups as (
SELECT b.*, grp - 1 as grp, ROW_NUMBER() OVER (PARTITION BY grp ORDER BY id) - 1 as seqnum
FROM b CROSS JOIN
GENERATE_SERIES(1, (SELECT POWER(2, COUNT(*))::int FROM b)) gs(grp)
)
SELECT bg.grp, a.seq,
COALESCE(MAX(CASE WHEN a.id = bg.id AND (POWER(2, bg.seqnum)::int & bg.grp) > 0 THEN bg.alter END),
MAX(a.id)
) as id
FROM a CROSS JOIN
bgroups bg
GROUP BY bg.grp, a.seq
ORDER BY bg.grp, a.seq;
Here is a db<>fiddle.

UNION ALL not performing as expected - Oracle SQL

I have two tables:
tableA
| Part | Val |
|:----:|:---:|
| AA | 3 |
| AB | 2 |
| AC | 11 |
| AD | 6 |
| AE | 3 |
tableB
| Part | Val |
|:----:|:---:|
| AC | 9 |
| AF | 5 |
| AG | 1 |
| AH | 10 |
| AI | 97 |
I would like to union these tables to achieve this result:
| Part | ValA | ValB |
|:----:|:----:|:----:|
| AA | 3 | 0 |
| AB | 2 | 0 |
| AC | 11 | 9 |
| AD | 6 | 0 |
| AE | 3 | 0 |
| AF | 0 | 5 |
| AG | 0 | 1 |
| AH | 0 | 10 |
| AI | 0 | 97 |
I have tried:
SELECT * FROM tableA
UNION ALL
SELECT * FROM tableB
But that results in only one column of vals, which I do not want.
How can I merge these tables and create two columns, one for each table, where if the part does not appear in the other table, its value can just be 0?
SQL FIDDLE for reference.
It appears that you want to join the tables, not union them
select nvl(a.Part, b.Part) as Part,
nvl( a.Val, 0 ) as ValA,
nvl( b.Val, 0 ) as ValB
from tableA a
full outer join tableB b
on( a.Part = b.Part )
order by 1
Note that using case-sensitive identifiers like you do in your fiddle is generally frowned upon. It tends to make writing queries more complicated than it needs to be and it tends to get annoying to have to include the double quotes around every column name.
Demo
You can try below -
select part,max(valA),max(valB) from
(
select part, val as valA, 0 as valB from tableA
union all
select part, 0 , val from tableB
)A group by part

Spark dataframe add Missing Values

I have a dataframe of the following format. I want to add empty rows for missing time stamps for each customer.
+-------------+----------+------+----+----+
| Customer_ID | TimeSlot | A1 | A2 | An |
+-------------+----------+------+----+----+
| c1 | 1 | 10.0 | 2 | 3 |
| c1 | 2 | 11 | 2 | 4 |
| c1 | 4 | 12 | 3 | 5 |
| c2 | 2 | 13 | 2 | 7 |
| c2 | 3 | 11 | 2 | 2 |
+-------------+----------+------+----+----+
The resulting table should be of the format
+-------------+----------+------+------+------+
| Customer_ID | TimeSlot | A1 | A2 | An |
+-------------+----------+------+------+------+
| c1 | 1 | 10.0 | 2 | 3 |
| c1 | 2 | 11 | 2 | 4 |
| c1 | 3 | null | null | null |
| c1 | 4 | 12 | 3 | 5 |
| c2 | 1 | null | null | null |
| c2 | 2 | 13 | 2 | 7 |
| c2 | 3 | 11 | 2 | 2 |
| c2 | 4 | null | null | null |
+-------------+----------+------+------+------+
I have 1 Million customers and 360(in the above example only 4 is depicted) Time slots.
I figured out a way to create a Dataframe with 2 columns (Customer_id,Timeslot) with (1 M x 360 rows) and do a Left outer join with the original dataframe.
Is there a better way to do this?
You can express this as a SQL query:
select df.customerid, t.timeslot,
t.A1, t.A2, t.An
from (select distinct customerid from df) c cross join
(select distinct timeslot from df) t left join
df
on df.customerid = c.customerid and df.timeslot = t.timeslot;
Notes:
You should probably put this into another dataframe.
You might have tables with the available customers and/or timeslots. Use those instead of the subqueries.
I think can used the answer of gordon linoff but you can add the following thinsg as you stated that there are millions of customer and you are performing join in them.
use tally table for TimeSlot?? because it might give a better performance.
for more usabllity please refer the following link
http://www.sqlservercentral.com/articles/T-SQL/62867/
and I think you should use partition or row number function to divide you column customerid and select the customers based on some partition value. For example just select the row number values and then cross join with the tally table. it can imporove your performance .

Join a table representing a "transfer" between two rows of another table

Sorry for the confusing title; however a description and illustration should hopefully clear it up.
Essentially, I have the table A representing instances of a transfer of an 'amount' between rows of table B. I wish to join A with B so that I can display the details of the transfer:
================= A ===================
+-----+-----------+----------+--------+
| AID | fromID(FK) | toID(FK) | amount |
+-----+-----------+----------+--------+
| 1 | 1 | 5 | 100 |
| 2 | 1 | 3 | 150 |
| 3 | 5 | 3 | 500 |
| 4 | 1 | 5 | 200 |
| 5 | 4 | 5 | 800 |
| 6 | 3 | 5 | 15 |
+----+------------+----------+--------+
and
==== B =====
+----+------+
| BID | name |
+----+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
| 5 | e |
+----+------+
I wish to join them and produce a "from name" column and a "to name" like:
+-----+------+----+--------+
| AID | from | to | amount |
+-----+------+----+--------+
| 1 | a | e | 100 |
| 2 | a | c | 150 |
| 3 | e | c | 500 |
| 4 | a | e | 200 |
| 5 | d | e | 800 |
| 6 | c | e | 15 |
+-----+------+----+--------+
You can join a on b twice:
SELECT aid, from_b.name, to_b.name, amount
FROM a
JOIN b from_b ON from_b.bid = a.fromid
JOIN b to_b ON to_b.bid = a.toid
Do a JOIN between the tables like below but you will have to join table B twice
select a.AID,
b.name as [from],
b1.name as [to],
a.amount
from A a
join B b on a.fromID(FK) = b.BID
join B b1 on a.toID(FK) = B.bid;
You can do this with out join.
Fiddle with sample data
select aid,
(select name from b where a.fromid = bid) as "from",
(select name from b where a.toid = bid) as "to",
amount
from a

Joining multiple tables to produce one data source

I have four tables with similar format but different values.
Table A
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 2
| 16 | 10/12/24 | 1
| 17 | 10/13/24 | 1
Table B
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 1
| 17 | 10/16/24 | 1
| 18 | 10/17/24 | 1
Table C
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 4
| 19 | 10/18/24 | 4
| 20 | 10/19/24 | 1
I need to get one data source that looks like this below, that is a full outer join of the above tables, where the ID and Date fields as the only fields with non values.
Table C
| ID | Date | Photo | Image | Cat
| 14 | 10/10/2014 | 1 | 1 | 1
| 15 | 10/11/2014 | 2 | 1 | 4
| 16 | 10/12/2014 | 1 | NULL | NULL
| 17 | 10/16/2014 | NULL | 1 | NULL
| 18 | 10/14/2014 | NULL | NULL | NULL
| 18 | 10/17/2014 | NULL | 1 | NULL
| 19 | 10/15/2014 | NULL | NULL | 4
| 20 | 10/16/2014 | 1 | NULL | NULL
| 20 | 10/19/2014 | NULL | NULL | 1
You mention FULL JOIN so I'm assuming you use a database that supports them. You can use COALESCE() to return the populated ID and Date, and also to simplify your JOIN criteria:
SELECT COALESCE(a.ID,b.ID,c.ID) AS ID
,COALESCE(a.Date,b.Date,c.Date) AS Date
,a.Photo
,b.Image
,c.Cat
FROM TableA a
FULL JOIN TableB b
ON a.ID = b.ID AND a.Date = b.Date
FULL JOIN TableC c
ON COALESCE(a.ID,b.ID) = c.ID AND COALESCE(a.Date,b.Date) = c.Date
You can use a FULL OUTER JOIN and COALESCE or NVL to ensure that the ID and Date columns are not null.
SELECT COALESCE(a.ID, b.ID,c.ID) AS ID,
COALESCE(a."Date", b."Date", c."Date") AS "Date",
a.Photo,
b.Image,
c.Cat
FROM TableA a
FULL OUTER JOIN
TableB b
ON ( a.ID = b.ID )
FULL OUTER JOIN
TableC c
ON ( c.ID = COALESCE( a.ID, b.ID ) );
SQLFIDDLE