Merging multiple tables sharing a column - sql

I have a list of tables like this:
t1
ID | Name
3 | 'AAA'
4 | 'BBB'
5 | 'CCC'
6 | 'DDD'
7 | 'EEE'
t2
ID | Password
3 | 'test'
6 | 'password'
t3
ID | Birth Year | Last Name
4 | 1990 | 'John'
6 | 1988 | 'Megan'
7 | - | 'Bob'
t4
ID | Birth Year
7 | 1985
I want to merge them all into this, noticing that t3 and t4 both have birth year columns, but the value will only be in either one.
ID | Name | Password | Birth Year | Last Name
3 | 'AAA' | 'test' | - | -
4 | 'BBB' | - | 1990 | 'John'
5 | 'CCC' | - | - | -
6 | 'DDD' |'password'| 1988 | 'Megan'
7 | 'EEE' | - | 1985 | 'Bob'
Does anyone know how this can be done? t1 is the "master" table, so it will always contain all the IDs.
I've tried:
select * \
from t1 \
LEFT outer join t2 on t1.ID = t2.ID \
LEFT outer join t3 on t1.ID = t3.ID \
LEFT outer join t4 on t1.ID = t4.ID
But it doesnt work properly, it has separate columns for each individual columns in t1, t2, t3, t4

select t1.ID,
t1.Name,
t2.Password,
COALESCE(t3.[Birth Year],t4.[Birth Year]), t3.[Last Name]
from t1
LEFT outer join t2 on t1.ID = t2.ID
LEFT outer join t3 on t1.ID = t3.ID
LEFT outer join t4 on t1.ID = t4.ID

you can selectively left join t4 to t1, then show t4.BirthYear if it is not null, otherwise t3.BirthYear. like this:
SELECT t1.ID, t1.Name, t2.Password, COALESCE(t4.BirthYear, t3.BirthYear) as BirthYear, t3,LastName
FROM t1
LEFT OUTER JOIN t2 on t1.ID = t2.ID
LEFT OUTER JOIN t3 on t1.ID = t3.ID
LEFT OUTER JOIN t4 on t1.ID = t4.ID and t4.BirthYear is NOT NULL

Related

How to select from joined table with WHERE clause only when value exist?

I want to SELECT one record from table1 (WHERE t1.id = 1) and then JOIN table2 and table3 (t2.field2 and t3.field3) to table1 but ONLY if the values exists (IS NOT NULL).
So for example, if the value doesn't exist for t3.field3, the field3 column is not displayed for that table...
t1
id | field1
---------------
1 | f1val
2 | f1val
3 | f1val
t2
id(fk) | field2
-------------------
1 | f2val
2 | null
3 | null
t3
id(fk) | field3
-------------------
1 | null
2 | f3val
3 | f3val
the code I tried to do is this:
SELECT t1.id, t2.field1, t3.field3
FROM (
SELECT t1.id
FROM t1
WHERE t1.id = 1
)
LEFT JOIN t2 ON t2.id = t1.id AND t2.id is not null
LEFT JOIN t3 ON t2.id = t1.id AND t3.id is not null;
The joined table returned from the query above looks like this:
id | field2 | field3
----------------------------
1 | f1val | null
However, since field3 is null, I want it to return only the id and field2 like this:
id | field2
----------------
1 | f1val
Your help will be highly appreciated.
You could return one column, using coalesce():
SELECT t1.id, COALESCE(t2.field1, t3.field3) as field_2_3
FROM t1 LEFT JOIN
t2
ON t2.id = t1.id LEFT JOIN
t3
ON t3.id = t1.id
WHERE t1.id = 1;
However, you cannot sometimes return two columns and sometimes return 3 columns.
Notes:
The subquery on t1 is utterly unnecessary. You can just apply the filter in a single WHERE clause.
The comparisons for IS NOT NULL are unnecessary because they fail the JOIN condition anyway.
The last JOIN condition is presumably on t3.id = t1.id.

Microsoft SQL Server Conditional Joining based on 2 columns

I am looking to join 3 tables, all with the same data except one column is a different name (different date for each of the the 3 tables). The three tables look like the following. The goal is if a condition exists in table 1 AND/OR table 2 determine if a condition does or does not exist in table 3 for each individual id/condition. I'm currently left joining table 2 to table 1 but I'm aware that is not accounting for if a condition in table 2 exists that is not in table it is not being accounted for, anyways, any help would into this would be useful.
Table 1
id place Condition_2018
123 ABC flu
456 ABC heart attack
Table 2
id place Condition_2019
123 ABC flu
789 def copd
Table 3
id place Condition_2020
456 ABC heart attack
789 def copd
123 ABC flu
OUTPUT:
Table 2
id place Condition_2018 Condition_2019 Condition_2020
123 ABC flu flu flu
456 ABC heart attack null heart attack
789 def NULL copd copd
Thank you!
How about this (SQL Server syntax)...
SELECT
x.id
, x.place
, x.Condition_2018
, x.Condition_2019
, t3.Condition_2020
FROM (
SELECT
COALESCE(t1.id, t2.id) AS id
, COALESCE(t1.place, t2.place) AS place
, t1.Condition_2018
, t2.Condition_2019
FROM Table1 AS t1
FULL OUTER JOIN Table2 AS t2 ON t1.id = t2.id AND t1.place = t2.place
) AS x LEFT JOIN Table3 AS t3 ON x.id = t3.id AND x.place = t3.place
If your database supports full join, you can just do:
select
id,
place,
t1.condition_2018,
t2.condition_2019,
t3.condition_2020
from table1 t1
full join table2 t2 using(id, place)
full join table3 t3 using(id, place)
Otherwise, it is a bit more complicated: union all and aggregation is one method:
select
id,
place,
max(condition_2018) condition_2018,
max(condition_2019) condition_2019,
max(condition_2020) condition_2020
from (
select id, place, condition_2018, null condition_2019, null condition 2020 from table1
union all
select id, place, null, condition_2019, null from table2
select id, place, null, null, condition_2020 from table3
) t
group by id, place
You seem to want everything in Table3 and matches in the other two tables. That is just left joins:
select t3.id, t3.place,
t1.condition_2018, t2.condition_2019,
t3.condition_2020
from table3 t3 left join
table2 t2
on t3.id = t2.id and t3.place = t2.place left join
table1 t1
on t3.id = t1.id and t3.place = t1.place;
You need a full outer join of table1 and table2 and a left join to table3:
select
coalesce(t1.id, t2.id) id,
coalesce(t1.place, t2.place) place,
t1.Condition_2018,
t2.Condition_2019,
t3.Condition_2020
from table1 t1 full outer join table2 t2
on t2.id = t1.id
left join table3 t3
on t3.id = coalesce(t1.id, t2.id)
See the demo.
Results:
> id | place | Condition_2018 | Condition_2019 | Condition_2020
> --: | :---- | :------------- | :------------- | :-------------
> 123 | ABC | flu | flu | flu
> 456 | ABC | heart attack | null | heart attack
> 789 | def | null | copd | copd

Joining two sql tables with a one to many relationship, but want the max of the second table

I am trying to join two tables one is a unique feature the seconds is readings taken on several dates that relate to the unique features. I want all of the records in the first table plus the most recent reading. I was able to get the results I was looking for before adding the shape field. By using the code
SELECT
Table1.Name, Table1.ID, Table1.Shape,
Max(Table2.DATE) as Date
FROM
Table1
LEFT OUTER JOIN
Table2 ON Table1.ID = table2.ID
GROUP BY
Table1.Name, Table1.ID, Table1.Shape
The shape field is a geometry type and I get the error
'The type "Geometry" is not comparable. It can not be use in the Group By Clause'
So I need to go about it a different way, but not sure how.
Below is a sample of the two tables and the desired results.
Table1
Name| ID |Shape
AA1 | 1 | X
BA2 | 2 | Y
CA1 | 3 | Z
CA2 | 4 | Q
Table2
ID | Date
1 | 5/27/2013
1 | 6/27/2014
2 | 5/27/2013
2 | 6/27/2014
3 | 5/27/2013
3 | 6/27/2014
My Desired Result is
Name| ID |Shape |Date
AA1 | 1 | X | 6/27/2014
BA2 | 2 | Y | 6/27/2014
CA1 | 3 | Z | 6/27/2014
CA2 | 4 | Q | Null
You can do the aggregation on Table2 in a CTE, finding the MAX(DATE) for each ID, and then join that result to Table1:
WITH AggregatedTable2(ID, MaxDate) AS
(
SELECT
ID, MAX(DATE)
FROM
Table2
GROUP BY
ID
)
SELECT
t1.ID, t1.Name, t1.Shape, t2.MaxDate
FROM
Table1 t1
LEFT JOIN
AggregatedTable2 t2 ON t1.ID = t2.ID
Try casting geometry as a varchar.
Select Table1.Name, Table1.ID, cast(Table1.Shape as varchar(1)) AS Shape, Max(Table2.DATE) as Date
FROM Table1 LEFT OUTER JOIN
Table2 ON Table1.ID = table2.ID
Group By Table1.Name, Table1.ID, cast(Table1.Shape as varchar(1))
Try this:
SELECT t1.Name
, t1.ID
, t1.Shape
, MAX(t2.Date) As Date
FROM Table1 AS t1
LEFT JOIN Table2 AS t2
ON t2.ID = t1.ID
GROUP
BY t1.Name
, t1.ID
, t1.Shape

Postgresql array_agg, INNER JOIN and LEFT JOIN problems

I have a slight problem with one of my query. The goal of this query is to get all the table1 items of a user and their information. As you can see, the data model is quite complex (for good reasons), and this requires an big query (my goal is to gather everything with one query only).
Here is the data model :
What I want :
All T1 info
All T2 info for one T1 item (it is a 1 to n relations, so I'll use array_agg)
All T3 info for one T1 item
All T4 info for one T1 item
All T6 info for one T1 item
i18n info for the T1 itemp
Here are the table1_table2 and table4_table6 SELECT * :
table1_id | table2_id
-------------+---------------
item2id | table2item1
item4id | table2item2
item4id | table2item1
item5id | table2item3
item5id | table2item2
table4_id | table6_id
------------------+--------------------
table4item1 | table6item1
table4item1 | table6item2
table4item2 | table6item2
table4item3 | table6item3
table4item1 | table6item3
table4item2 | table6item3
Here are the Table1 SELECT with id and its foreign key.
table1_id | table3_id
------------------------
item1id | table3item1
item2id | table3item1
item6id | table3item4
item3id | table3item2
item4id | table3item2
item5id | table3item3
Same for table3 :
table3_id | table4_id
------------+--------------
table3item1 | table4item1
table3item4 | table4item1
table3item2 | table4item2
table3item3 | table4item3
Finally, here is my query :
SELECT t1.id,
na.name,
array_to_json(array_agg(row_to_json(t2))) AS table2items,
array_to_json(array_agg(row_to_json(t6))) AS table6items
FROM table1 t1
INNER JOIN table1_i18n na ON na.table1_id = t1.id
INNER JOIN table3 t3 ON t3.id = t1.table3_id
INNER JOIN table4 t4 ON t4.id = t3.table4_id
LEFT JOIN table1_table2 t1t2 ON t1t2.table1_id = t1.id
LEFT JOIN table2 t2 ON t2.id = t1t2.table2_id
LEFT JOIN table4_table6 t5_t6 ON t5_t6.table5_id = t3.table4_id
LEFT JOIN table6 t6 ON t6.id = t5_t6.table6_id
WHERE t1.user_id = 'myuserid' AND na.lang = 'en_US'
GROUP BY t1.id, na.name, t4.id
ORDER BY t1.id;
Here is the result :
id | name | table3_id | table4_id | table2items | table6items
-------------+------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
item1id | MyFirstItem | table3item1 | table4item1 | [null,null,null] | [{"id":"table6item1"},{"id":"table6item2"},{"id":"table6item3"}]
item2id | MySecondItem | table3item1 | table4item1 | [{"table2item1","data1":"damage","data2":10},{"id":"table2item1","data1":"damage","data2":10},{"id":"table2item1","data1":"damage","data2":10}] | [{"id":"table6item1"},{"id":"table6item2"},{"id":"table6item3"}]
item3id | MyThirdItem | table3item2 | table4item2 | [null,null] | [{"id":"table6item2"},{"id":"table6item3"}]
item4id | MyFourthItem | table3item2 | table4item2 | [{"id":"table2item2","data1":"range","data2":20},{"id":"table2item1","data1":"damage","data2":10},{"id":"table2item2","data1":"range","data2":20},{"id":"table2item1","data1":"damage","data2":10}] | [{"id":"table6item2"},{"id":"table6item3"},{"id":"table6item3"},{"id":"table6item2"}]
item5id | MyFifthItem | table3item3 | table4item3 | [{"id":"table2item3","data1":"range","data2":20},{"id":"table2item2","data1":"range","data2":20}] | [{"id":"table6item3"},{"id":"table6item3"}]
item6id | MySixthItem | table3item4 | table4item1 | [null,null,null] | [{"id":"table6item2"},{"id":"table6item1"},{"id":"table6item3"}]
Well, I've got a problem here. As you can see, my table2_items and table6_items arrays have the same size. I don't know the reason for this, but it seems that I'm missing something.
Worse, instead of filling this array with null value, this query creates duplicates which should not appear.
Details :
item1 and item6 have the same problem : no links to table2, and 3 items in table6. I end up with an array [null, null, null] for table2_items
item2 has 3 links to table 6, and 1 to table2. I end up with 3 times the same table2 object in the array
item4... I don't know what's happening here. Should have 2 things in each array, and I've got 4 (duplicates)
item5 : you can clearly see the duplication.
I have tried to group by table6.id, or table2.id. It doesn't work (I have got a line for each of them, so several line for each item).
Note : If I do
SELECT t1.id,
na.name,
array_to_json(array_agg(row_to_json(t2))) AS table2items,
FROM table1 t1
INNER JOIN table1_i18n na ON na.table1_id = t1.id
INNER JOIN table3 t3 ON t3.id = t1.table3_id
INNER JOIN table4 t4 ON t4.id = t3.table4_id
LEFT JOIN table1_table2 t1t2 ON t1t2.table1_id = t1.id
LEFT JOIN table2 t2 ON t2.id = t1t2.table2_id
WHERE t1.user_id = 'myuserid' AND na.lang = 'en_US'
GROUP BY t1.id, na.name, t4.id
ORDER BY t1.id;
alone, it works perfectly. Same for t6. It's only when I try to gather everything at the same time that I got some problems.
If it is not clear enough, ask for details. It's really not easy to explain such a problem :).

Finding unmatched records with SQL

I'm trying write a query to find records which don't have a matching record in another table.
For example, I have a two tables whose structures looks something like this:
Table1
State | Product | Distributor | other fields
CA | P1 | A | xxxx
OR | P1 | A | xxxx
OR | P1 | B | xxxx
OR | P1 | X | xxxx
WA | P1 | X | xxxx
VA | P2 | A | xxxx
Table2
State | Product | Version | other fields
CA | P1 | 1.0 | xxxx
OR | P1 | 1.5 | xxxx
WA | P1 | 1.0 | xxxx
VA | P2 | 1.2 | xxxx
(State/Product/Distributor together form the key for Table1. State/Product is the key for Table2)
I want to find all the State/Product/Version combinations which are Not using distributor X. (So the result in this example is CA-P1-1.0, and VA-P2-1.2.)
Any suggestions on a query to do this?
SELECT
*
FROM
Table2 T2
WHERE
NOT EXISTS (SELECT *
FROM
Table1 T1
WHERE
T1.State = T2.State AND
T1.Product = T2.Product AND
T1.Distributor = 'X')
This should be ANSI compliant.
In T-SQL:
SELECT DISTINCT Table2.State, Table2.Product, Table2.Version
FROM Table2
LEFT JOIN Table1 ON Table1.State = Table2.State AND Table1.Product = Table2.Product AND Table1.Distributor = 'X'
WHERE Table1.Distributor IS NULL
No subqueries required.
Edit: As the comments indicate, the DISTINCT is not necessary. Thanks!
select * from table1 where state not in (select state from table1 where distributor = 'X')
Probably not the most clever but that should work.
SELECT DISTINCT t2.State, t2.Product, t2.Version
FROM table2 t2
JOIN table1 t1 ON t1.State = t2.State AND t1.Product = t2.Product
AND t1.Distributor <> 'X'
In Oracle:
SELECT t2.State, t2.Product, t2.Version
FROM Table2 t2, Table t1
WHERE t1.State(+) = t2.State
AND t1.Product(+) = t2.Product
AND t1.Distributor(+) = :distributor
AND t1.State IS NULL