Finding Minimum Values with Multiple Joins - sql

I am using StandardSQL in BigQuery and have 7 tables with 10 to 75 columns each and thousands of rows. For simplicity I will only use the relevant tables and columns for what I am trying to accomplish.
Table 1
Item
Desc
12341
abcd
23451
bcda
34561
cdab
45671
dabc
Table 2
SubItem
Location
ON_OFF
OnHand
OnOrder
12345
1
ON
3
5
12345
2
ON
4
2
12345
3
ON
2
4
12346
1
ON
7
7
12346
2
ON
1
4
12346
3
ON
8
7
23451
1
OFF
1
1
23451
2
OFF
3
2
34567
1
ON
6
0
34567
2
ON
1
5
34568
1
ON
2
0
34568
2
ON
3
10
45671
2
ON
5
1
Table 3
Item
SubItem
12341
12346
23451
23451
34561
34567
34561
34568
Current Result
Item
Desc
ON_OFF
OH
OO
12341
abcd
ON
9
11
12341
abcd
ON
16
18
23451
bcda
OFF
4
3
34561
cdab
ON
7
5
34561
cdab
ON
5
10
45671
dabc
ON
5
1
Desired Result
Item
Desc
ON_OFF
OH
OO
12341
abcd
ON
9
18
23451
bcda
OFF
4
3
34561
cdab
ON
5
5
45671
dabc
ON
5
1
I am looking for the Minimum OH and Minimum OO Value for each item and as in the case of Item 45671, that does not correspond with the same SubItem number.
Current code providing me with the Current Result table is:
Select
Table1.Item,
Table1.Desc,
Table2.ON_OFF,
Table2.OH,
Table2.OO
From Table1
Left Join Table3
On Table1.Item = Table3.Item
Left Join
(Select SubItem, ON_OFF, Sum(OnHand) As OH, Sum(OnOrder) As OO
From Table 2
Group by 1,2)
ON Table3.SubItem = Table2.SubItem;
Looking for ideas as I am still fairly new to SQL and the current actual code ties 7 tables with various joins to build a final table with 45 columns and thousands of rows. I have looked at using RowNumber() and Partition By, but I am not sure where it would go. Was also thinking separating the OO and OH into two joins might help.
Any suggestions welcome! Thank you!

Looking at your sample data, It seems that You can use group by and aggregate function min as follows:
Select Table1.Item,
Table1.Desc,
Table2.ON_OFF,
Min(Table2.OH),
Min(Table2.OO)
From Table1
Left Join Table3
On Table1.Item = Table3.Item
Left Join (Select SubItem,
ON_OFF,
Sum(OnHand) As OH,
Sum(OnOrder) As OO
From Table2
Group by 1,2) Table2
ON Table3.SubItem = Table2.SubItem
Group by Table1.Item, Table1.Desc, Table2.ON_OFF;

I worked with it after getting some much needed sleep...
I came up with the below and it is working!
SELECT T1.Item,
T1.Desc,
T3_1.ONOFF,
T3_1.OH,
T3_2.OO
FROM Table1 T1
Left Join
(Select Item, SubItem,T2_1.O_O as ONOFF, T2_1.OH1 as OH,
ROW_NUMBER() OVER(PARTITION BY Item ORDER BY T2_1.OH1) as rn
FROM T2_1
Left Join
(Select SubItem, SUM(IFNULL(OnHand,0)) AS OH1,
FROM Table2
GROUP BY 1) T2_1
ON T2_1.SubItem = T3_1.SubItem) T3_1
On T1.Item = T3_1.Item
Left Join
(Select Item, SubItem, T2_2.OO1 as OO,
ROW_NUMBER() OVER(PARTITION BY Item ORDER BY T2_2.OO1) as rn2
FROM T2_2
Left Join
(Select SubItem, SUM(IFNULL(OnOrder,0)) AS OO1,
FROM Table2
GROUP BY 1) T2_2
ON T2_2.SubItem = T3_2.SubItem) T3_2
On T1.Item = T3_2.Item
Where rn = 1 and rn2 = 1;
Thanks!

Related

SQL Server : multiple rows single line

I would like to get the representation of one record based on the primary key value from multiple tables. As shown below, each table can have multiple values based on this primary key value.
TABLE-1
ID
NAME
1
AA
2
BB
3
CC
4
DD
5
EE
TABLE-2
ID
SCHOOL
AUT
1
11
A
2
11
A
2
12
B
3
11
A
4
12
A
4
13
B
5
13
A
TABLE-3
ID
TC
1
101
2
102
2
103
2
104
3
105
4
106
4
107
5
108
The result below is the value obtained with an OUTER JOIN.
SELECT
T1.ID, T2.SCHOOL, T3.TC, T2.AUT
FROM
T1
LEFT OUTER JOIN
T2 ON T1.ID = T2.ID
LEFT OUTER JOIN
T3 ON T1.ID = T3.ID
ORDER BY
T1.ID ASC
ID
SCHOOL
TC
AUT
1
11
101
A
2
11
102
A
2
12
102
B
2
11
103
A
2
12
103
B
2
11
104
A
2
12
104
B
3
11
105
A
4
12
106
A
4
13
106
B
4
12
107
A
4
13
107
B
5
13
106
A
How can I get the result like below?
ID
SCHOOL
TC1
TC2
TC3
1
11
101
2
11
102
103
104
3
11
105
4
12
106
107
5
13
108
The important thing here is that in the result value, SCHOOL only shows that AUT is 'A'.
I would appreciate it if you let me know your query.
It looks, from your desired results, you just need to use row_number in combination with a conditional aggregate. Your sample data seems a little inadequate, I can't see any requirement for table1 at all.
Try the following:
with t as (
select t2.id,t2.school,t3.tc, Row_Number() over(partition by t2.id order by t3.tc) col
from t2 join t3 on t2.id=t3.id
where aut='A'
)
select id,school,
max(case when col=1 then tc end) TC1,
max(case when col=2 then tc end) TC2,
max(case when col=3 then tc end) TC3
from t
group by id, school
Example SQL Fiddle
SELECT
T1.ID, T2.SCHOOL,
GROUP_CONCAT(T3.TC),
GROUP_CONCAT(T2.AUT)
FROM
T1
LEFT OUTER JOIN
T2 ON T1.ID = T2.ID
LEFT OUTER JOIN
T3 ON T1.ID = T3.ID
GROUP BY
T1.ID, T2.SCHOOL
WHERE
T2.AUT = ‘A’
ORDER BY
T1.ID ASC
Notice that GROUP_CONCAT concatenates the values in the row.
EDIT: oh my, haven't seen that it's a SQL Server question!
Just replace GROUP_CONCAT with STRING_AGG if you’re using SQL Server 2017 or newer.

Join two tables, using value from the first unless it is null, otherwise use value from the second

I have three tables which look like those:
TABLE 1
id j_id
1 1
2 2
3 3
TABLE 2
id j_id table1_id
1 57 1
2 84 1
3 1 1
4 9 2
5 2 2
and every j has a value in a third table
id value
1 1abc
2 2bcd
3 3abc
57 57abc
84 84abc
9 9abc
I am trying to write a query which will join table 1 and table 2 and use the J value from the third table instead of the j_id, but the problem is that I want to use the j value from the second table if it exists and otherwise use the value from the first table.
in order the make it clearer this is my query result without using the third table:
tbl1.j_id tbl2.j_id
1 1
1 84
1 57
2 2
2 9
3 null
I want the end query result to use the second table's j value unless it is null:
tbl1.j_id tbl2.j_id j_id
1 1 1abc
1 84 84abc
1 57 57abc
2 2 2abc
2 9 9abc
3 null 3abc
(Question and title edits are more than welcome, weren't that sure how to phrase them..)
You can simply JOIN to table3 on the COALESCE of table2.j_id and table1.j_id:
SELECT t1.j_id AS t1_j_id, t2.j_id AS t2_j_id, t3.value
FROM table1 t1
LEFT JOIN table2 t2 ON t2.table1_id = t1.id
JOIN table3 t3 ON t3.id = COALESCE(t2.j_id, t1.j_id)
Output:
t1_j_id t2_j_id value
1 1 1abc
1 57 57abc
1 84 84abc
2 2 2bcd
2 9 9abc
3 null 3abc
Demo on dbfiddle
One solution is to left join table3 twice:
select
t1.j_id,
t2.j_id,
coalesce(t31.value, t32.value) j_value
from
table1 t1
left join table2 t2 on t2.table1_id = t1.id
left join table3 t31 on t31.id = t2.j_id
left join table3 t32 on t32.id = t1.j_id

Join table by id and nearest date for every date

I have 2 tables:
TABLE 1
id date_measured value 1
1 01/01/2017 5
1 02/20/2017 6
1 04/01/2017 5
2 03/02/2017 5
2 04/02/2017 3
TABLE 2
id date_measured value 2
1 01/06/2017 5
1 03/01/2017 6
2 02/01/2017 5
2 03/09/2017 7
2 04/05/2017 4
I want to join it such that each id matches and the closest date matches so:
id date_measured1 value 1 date_measured2 value 2
1 01/01/2017 5 01/06/2017 5
1 02/20/2017 6 03/01/2017 6
2 02/01/2017 5 02/01/2017 5
2 03/02/2017 5 03/09/2017 7
2 04/02/2017 3 04/05/2017 4
etc. IE for each id for each date measured take the closest measured date in the other table and make it a row. Something closeish to
SELECT *
FROM table1 a
INNER JOIN table2 b
ON a.id = b.id
AND <date from a is closest date from b>
But I have no idea how to do the second part. Any suggestions?
In standard SQL, you can get the date using a correlated subquery:
select t1.*,
(select t2.date_measured
from table2 t2
where t2.id = t1.id
order by abs(t2.date_measured - t1.date_measured) asc
fetch first 1 row only
) as t2_date_measured
from table1 t1;
You can then join back to table2 to get additional information from that row.
The above is generic SQL (not necessarily standard SQL). Date/time functions tend to be peculiar to each database; so - may not work for the difference. Not all databases support fetch first 1 row only, but almost all support some mechanism for doing the same thing.
If you have window functions use ROW_NUMBER():
SQL DEMO I use postgresql so date function may vary on your rdbms
WITH cte as (
SELECT *,
t1.id as t1_id,
t1.date_measured as t1_date,
t1.value1,
t2.id as t2_id,
t2.date_measured as t2_date,
t2.value2,
date_part('day', age(t1.date_measured, t2.date_measured)) as days,
ROW_NUMBER() OVER (PARTITION BY t1.id, t1.date_measured
ORDER BY abs(date_part('day', age(t1.date_measured, t2.date_measured)))
) as rn
FROM table1 t1
JOIN table2 t2
ON t1.id = t2.id
)
SELECT *
FROM cte
WHERE rn = 1
ORDER BY t1_id, t1_date

How to get Oracle to return unique results in a one to many relationship tables with a left join

I have a three tables
Table 1
Id Department
1 A
2 B
3 C
4 D
Table 2
Id DepartId Name
1 1 ABC
2 1 DEF
3 1 ASD
4 2 FGH
5 2 HJK
6 3 ZXC
Table 3
Id Depart Area
1 A pp
2 B
3 C nn
4 D oo
I need the result
Id Depart Name Area
1 A ABC pp
2 B FGH Null
3 C ZXC nn
4 D NULL oo
I need one matching entry from table 2 and table 3 to corresponding entry in the table 1
Do a left join to also get t1 rows without any reference in the t2 table. GROUP BY to get only 1 row per Department.
select t1.id, t1.Department, min(t2.Name)
from t1
left join t2 on t1.id = t2.DepartId
group by t1.id, t1.Department
I think I would do this with a correlated subquery:
select t1.*,
(select t2.name
from t2
where t1.id = t2.DepartId and rownum = 1
) as t2name
from t1;
This saves the overhead of an aggregation. An index on t2(DepartId, name) is optimal for this query.
by the way not the answer to your specific question but if instead of just one you want all the names you can use listagg
SELECT t1.id,
department,
LISTAGG (name, ',') WITHIN GROUP (ORDER BY name) names
FROM t1, t2
WHERE t1.id = t2.departId(+)
GROUP BY t1.id, department
ORDER BY 1
ID Department Names
1 A ABC,ASD,DEF
2 B FGH, HJK
3 C ZXC
4 D

query regarding joining of two tables

Suppose I have 2 below tables
sql> select * from fraud_types ;
fraud_id fraud_name
-------- ----------
1 Fraud 1
2 Fraud 2
3 Fraud 3
4 Fraud 4
5 Fraud 5
sql> select * from alarms ;
fraud_id dealer count
-------- ------ -----
1 Deal 1 5
3 Deal 1 3
5 Deal 1 4
1 Deal 2 2
2 Deal 2 6
3 Deal 2 1
4 Deal 2 7
5 Deal 2 9
I want to join the two tables and get the output as
dealer fraud_id count
------ -------- -----
Deal 1 1 5
Deal 1 2 0
Deal 1 3 3
Deal 1 4 0
Deal 1 5 4
Deal 2 1 2
Deal 2 2 6
Deal 2 3 1
Deal 2 4 7
Deal 2 5 9
Basically I want to include the fields from fraud_types also and just display 0 in the output if it is not present in the alarms table. How can I achieve this ? Please help
Regards
You can do this with a cross join to get all combinations and then a left outer join:
select d.dealer, f.fraud_id, coalesce(cnt, 0)
from (select distinct dealer from fraud_types) d cross join
fraud_types f left outer join
(select dealer, fraud_id, count(*) as cnt
from fraud_types
group by dealer, fraud_id
) df
on df.dealer = d.dealer and df.fraud_id = f.fraud_id
order by d.dealer, f.fraud_id;
Partitioned outer join is very useful for cases like this:
select a.dealer, f.fraud_id, nvl(a.count,0) count
from fraud_types f
left outer join alarms a
partition by (a.dealer)
on a.fraud_id = f.fraud_id
order by a.dealer, f.fraud_id
This does an outer join between alarms and fraud_types for every value of dealer found in alarms.
--
If the alarms table does not have (fraud,dealer) as key, then you can do a group by before the partition outer join:
select a.dealer, f.fraud_id, nvl(a.count,0) count
from fraud_types f
left outer join (
select fraud_id
, dealer
, sum(count) count
from alarms
group by fraud_id, dealer
) a
partition by (a.dealer)
on a.fraud_id = f.fraud_id
order by a.dealer, f.fraud_id
select distinct f.fraud_id,dealer,
(case when f.fraud_id=t.fraud_id then COUNT else 0 end) counts
from
fraud_types f
left join
alarms t
partition by (dealer)
on f.fraud_id=t.fraud_id
order by dealer