Join table by id and nearest date for every date - sql

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

Related

Getting the record with latest record on join order by date

Consider two tables like this
id name
1 john
2 doe
3 jan
device id id devicename purchasedate
1 1 iphone 2018-02-22
2 1 iphone2 2019-02-22
3 1 iphone3 2020-02-22
4 2 iphone4 2019-02-22
5 2 iphone5 2019-02-25
6 3 iphone6 2020-03-15
So the result must be the join of the two tables and only the latest purchased date of a record must be selected. So the expected select result will be like this and if there is a record in the first table with no relationship in the second table, it should not be selected so only inner join can be used.
id name device id device name
1 john 3 iphone3
2 doe 5 iphone5
3 jan 6 iphone6
You can cross apply:
select t1.name, t2.* -- or whatever columns you want
from table1 t1 cross apply
(select top (1) t2.*
from table2 t2
where t2.id = t1.id
order by t2.purchaseddate desc
) t2

T-SLQ Can you use aggregate in on clause?

I have 2 call tables. Table 1 has call/client info. Table 2 has the call logs, what happened during call. I need to join table 2 to table 1 but only pull the 'Time' for the first phone. There are multiple 'Phone's in the call logs, need the time for the first one.
Table 1 Table 2 Goal
Call ID Client State Call ID Method Time Call ID Client State Time
1 John TX 1 Inbound 11:00 1 John TX 11:01
2 Mike CA 1 Phone 11:01 2 Mike CA 11:31
3 Susan NA 1 Answer 11:01 3 Susan NA ...
4 ... .... 1 Phone2 11:05 4
1 Call End 11:22 . etc
2 Inbound 11:30
2 Phone 11:31
Kinda want to do something like this.
SELECT *
FROM Table1 t1 on Table2 t2 = t1.call_id = t2.call_id and t2.Method like '%Phone%'
but this will pull all the phones times. Need MIN(phone). Can you use an aggregate in on clause?
No, you cannot use an aggregation in an on clause. Instead, use apply:
SELECT *
FROM Table1 t1 CROSS APPLY
(SELECT TOP (1) t2.*
FROM Table2 t2
WHERE t2.call_id = t1.call_id and
t2.Method like '%Phone%'
ORDER BY phone
) t2;
You can do it with ROW_NUMBER() window function:
select t1.*, t2.time
from table1 t1 inner join (
select *, row_number() over (partition by callid order by time) rn
from table2
where method like '%Phone%'
) t2 on t2.callid = t1.callid
where t2.rn = 1
or:
select t1.*,
(select min(time) from table2 where method like '%Phone%' and callid = t1.callid) time
from table1 t1

How do I join two tables in SQL limiting the second table data depending on the first one?

I have two tables looking something like this:
IMP DATE CAT
A 03/03/2016 1
B 04/04/2016 1
C 09/09/2016 2
D 01/01/2017 1
E 02/02/2017 1
F 03/03/2017 2
G 04/04/2017 2
===================
EXP DATE CAT
H 01/01/2016 1
I 05/05/2016 1
J 07/07/2016 2
K 11/11/2016 2
L 01/01/2017 1
M 03/03/2017 1
N 04/04/2017 2
O 05/05/2017 2
I want to join the first table to the second one but limit the lines joined from the second table by the latest date on the first table (per category).
The result I'm looking for would be every row in both tables except Item "M" (because Cat 1 in Table 1 has a latest date of February) and Item "O" (because Cat 2 in Table 1 has a latest date of April).
I've tried conditionalds within a where clause in the 2nd table but haven't got far.
Is there a simple way to do this? Any help is appreciated. I'm using SQL Server 2008 by the way.
Your description of the problem is specifically about using join. That suggests a query like this:
select . . .
from (select t1.*, max(t1.date) over (partition by t1.cat) as maxdate
from table1 t1
) t1 join
table2 t2
on t1.cat = t2.cat and t2.date <= t1.maxdate;
Desire output and its format is still not clear.
are you looking for this ?
;With CTE as
(
select *, row_number()over(partition by cat order by DATEs desc) rn
from #table1
)
--select * from cte
--where rn=1
select * from cte t1
left join #table2 t2
on t1.CAT=t2.CAT and t2.DATEs<=t1.DATEs
where rn=1

SQL Server 2012 Update table rate based on rate in table 2

I have two tables and below is the example
Table1
KEY BEGIN_DATE TOTAL_RATE
1 1974-01-01 3
1 1981-01-01 3
1 1983-01-01 4
1 1985-07-01 4
1 1989-10-01 7
1 1990-07-01 10
1 1997-10-01 11
1 2008-04-01 13
TABLE2
KEY END_DATE RATE_REDUCED
1 1989-09-30 2
1 1997-09-31 4
From Table 2 if key matches then we need to reduce TOTALRATE from Table 1
with RATEREDUCED in Table 2 where BEGINDATE > ENDDATE and it should happen
till the end of table 2 ENDDATE
EXPECTED RESULTS/UPDATE TO TABLE 1:
RESULT
KEY BEGIN_DATE NEW_RATE
1 1974-01-01 3
1 1981-01-01 3
1 1983-01-01 4
1 1985-07-01 4
1 1989-10-01 7 - 2 = 5 (Date is Greater than 1989-09-30)
1 1990-07-01 10 - 2 = 8 (Date is Greater than 1989-09-30)
1 1997-10-01 11 - 2 - 4 = 5 (Date is Greater than two dates)
1 2008-04-01 13 - 2 - 4 = 7 (Date is Greater than two dates)
I have many keys in table two and table one.
Is update with join possible
Thanks in advance
Similar to Gordon's, here we use use an Update in a CROSS APPLY. This approach will only update the records which match the criteria
Update Table1 Set TOTAL_RATE=TOTAL_Rate-B.Adj
From Table1 A
Cross Apply (
Select Adj=sum(RATE_REDUCED)
From Table2
Where END_DATE<=A.BEGIN_DATE and [Key]=A.[Key]
) B
Where B.Adj is not NULL
The Updated Table1 Looks like this now
KEY BEGIN_DATE TOTAL_RATE
1 1974-01-01 3
1 1981-01-01 3
1 1983-01-01 4
1 1985-07-01 4
1 1989-10-01 5
1 1990-07-01 8
1 1997-10-01 5
1 2008-04-01 7
This looks like a good application of outer apply:
select t1.*,
(t1.total_rate - coalesce(t2.rate_reduced, 0)) as total_rate
from table1 t1 outer apply
(select sum(t2.rate_reduced) as rate_reduced
from table2 t2
where t1.begin_date > t2.end_date and
t1.key = t2.key
) t2;
EDIT:
If you want to turn this into an update, that is easy:
update t1
set total_rate = (t1.total_rate - coalesce(t2.rate_reduced, 0))
from table1 t1 outer apply
(select sum(t2.rate_reduced) as rate_reduced
from table2 t2
where t1.begin_date > t2.end_date and
t1.key = t2.key
) t2;
I'm not sure about the difference in performance between using a join or using a subquery. Maybe you can try out the solutions mentioned in the other answers and compare it to a (simpler?) subquery approach:
update table1
set total_rate = total_rate -
(select COALESCE(sum(new_rate),0)
from table2
where begin_date > end_date
and table1.key = table2.key)

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