Assign values from another table based on time range - sql

Let's say I have T1 :
id date attribute
1 2017-04-20 t
1 2017-04-19 t
1 2017-04-18 t
2 2017-04-20 t
2 2017-04-19 f
And I also have T2 :
id date date_end attribute
1 2017-04-19 2017-04-25 f
What I want is to get a new table, which would contain data from T1 but in case there is matching ids in both table, replace the attribute value in T1 with the attribute value in T2 for the date range defined in T2.
The result would look like this:
id date attribute
1 2017-04-20 f
1 2017-04-19 f
1 2017-04-18 t
2 2017-04-20 t
2 2017-04-19 f
What I have come up with is the following:
SELECT t1.id, t1.date,
CASE WHEN max(T2.id) IS NULL THEN T1.attribute ELSE T2.attribute END
FROM T1
LEFT JOIN T2 using (id)
Can't figure out how to change the attributes for the particular range.
Any help is appreciated!

I think this is just LEFT JOIN with COALESCE():
SELECT t1.id, t1.date,
COALESCE(t2.attribute, t1.attribute) as attribute
FROM T1 LEFT JOIN
T2
ON t1.id = t2.id AND
t1.date >= t2.date and t1.date <= t2.date_end;

For BigQuery Standard SQL
#standardSQL
SELECT
a.id,
a.date,
CASE
WHEN a.date BETWEEN b.date AND b.date_end THEN b.attribute
ELSE a.attribute
END AS attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id
You can play/test it with sample data from your question
#standardSQL
WITH T1 AS (
SELECT 1 AS id, '2017-04-20' AS date, 't' AS attribute UNION ALL
SELECT 1, '2017-04-19', 't' UNION ALL
SELECT 1, '2017-04-18', 't' UNION ALL
SELECT 2, '2017-04-20', 't' UNION ALL
SELECT 2, '2017-04-19', 'f'
),
T2 AS (
SELECT 1 AS id, '2017-04-19' AS date, '2017-04-25' AS date_end, 'f' AS attribute
)
SELECT
a.id,
a.date,
CASE
WHEN a.date BETWEEN b.date AND b.date_end THEN b.attribute
ELSE a.attribute
END AS attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id
-- ORDER BY 1,2 DESC

Related

Order by in subquery and alias

I have a problem with a order by in oracle query.
select KEY, B, C, (select D from TABLE1 a where a.KEY = b.KEY and a.DATE<
b.DATE order BY a.DATE and rownum =1 )
FROMSTATUS from TABLE2 b
I known the "order by" is not working in subquery. I modify my query as:
select KEY, B, C, (select * from (select D from TABLE1 a where a.KEY =
b.KEY and a.DATE< b.DATE order by DATE) where rownum = 1)
FROMSTATUS from TABLE2 b
But in this way the B.KEY and B.DATE has not resolved by oracle
I need select only a 1 value from TABLE2 and the value is the first previous a.DATE
Example:
TABLE1
KEY DATE A B C
1 01/31/2000 1 2 3
2 02/25/2000 X Y Z
TABLE2
KEY DATE D
1 01/30/2000 1
1 01/27/2000 2
1 01/25/2000 2
2 02/20/2000 4
2 02/13/2000 1
I need this result:
TABLE1.KEY TABLE1.DATE TABLE1.A TABLE1.B TABLE1.C TABLE2.DATE TABLE2.D
1 01/31/2000 1 2 3 01/30/2000 1
2 02/25/2000 X Y Z 02/20/2000 4
Can you help me?
(i am sorry for my bad english)
row_number() after union will get your output.
select tFinal.DATE, tFinal.KEY
from (select row_number() over (partition by KEY order by t1.T, t1.DATE desc) as rn, t1.DATE, t1.KEY
from
(select DATE, KEY, 't1' as T from TABLE1
union all
select DATE, KEY, 't2' as T from TABLE2) t1) tFinal
Where rn = 2
You can use window functions for this:
WITH cte AS (
SELECT TABLE2.KEY, TABLE2.B, TABLE2.C, TABLE1.D
, ROW_NUMBER() OVER (PARTITION BY TABLE2.KEY, TABLE2.DATE ORDER BY TABLE1.DATE DESC) AS rn
FROM TABLE2
LEFT JOIN TABLE1 ON TABLE2.KEY = TABLE1.KEY AND TABLE2.DATE > TABLE1.DATE
)
SELECT *
FROM cte
WHERE rn = 1
Here's an answer that uses aggregation:
WITH t1 AS (SELECT 1 KEY, to_date('31/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('25/02/2000', 'dd/mm/yyyy') dt FROM dual),
t2 AS (SELECT 1 KEY, to_date('30/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 1 KEY, to_date('27/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 1 KEY, to_date('25/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('20/02/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('13/02/2000', 'dd/mm/yyyy') dt FROM dual)
SELECT t1.KEY,
t1.dt t1_date,
MAX(t2.dt) t2_date
FROM t1
LEFT OUTER JOIN t2 ON t1.key = t2.key AND t2.dt < t1.dt
GROUP BY t1.key, t1.dt
ORDER BY t1.key;
KEY T1_DATE T2_DATE
---------- ----------- -----------
1 31/01/2000 30/01/2000
2 25/02/2000 20/02/2000
I'm assuming here that t1.key is a unique column. Whether this is more performant than any of the other answers for your data is up to you to test *{:-)
In Oracle you can use KEEP LAST for this:
select
key,
b,
c,
(
select max(d) keep (dense_rank last order by t2.date)
from table2 t2
where t2.key = t1.key and t2.date < t1.date
) as fromstatus
from table1 t1;
As of Oracle 12c you can also use FETCH FIRST ROW:
select
key,
b,
c,
(
select d
from table2 t2
where t2.key = t1.key and t2.date < t1.date
order by t2.date desc
fetch first row only
) as fromstatus
from table1 t1;
or, moving the subquery to the FROM clause:
select
t1.key,
t1.b,
t1.c,
first_t2.d as fromstatus
from table1 t1
outer apply
(
select d
from table2 t2
where t2.key = t1.key and t2.date < t1.date
order by t2.date desc
fetch first row only
) first_t2;
This last query has the advantage that you could easily select more values from the table2 row than just one.

SQL joining on max ID or dates between date

I have the following tables:
Table 1:
with two columns (PatientID,Name)
Table 2:
with four columns (ID,PatientID,FromDate,ToDate)
I need to join (left join) table1 to table2 (on patientid) to get the values in table2 that has getdate() within Fromdate and todate and if there is no such record, then get the latest id.
I am using SQL 2016.
Table 1 Data:
1 Peter
2 Fady
Table 2 data
1 2019-01-01 2019-02-01
1 2019-03-01 2019-04-01
2 2019-06-01 2019-12-01
2 2020-01-01 2020-01-01
I should get:
1 2019-03-01 2019-04-01
2 2019-06-01 2019-12-01
I think apply does what you want. I think you simply want:
select t1.*, t2.*
from table1 t1 outer apply
(select top (1) t2.*
from table2 t2
where t2.patientid = t.patientid
order by fromdate desc
) t2;
I am guessing that you don't have future fromdates. If you do, then the order by can be tweaked to handle this.
EDIT:
If you can have future dates, then this would be tweaked to:
select t1.*, t2.*
from table1 t1 outer apply
(select top (1) t2.*
from table2 t2
where t2.patientid = t.patientid
order by (case when getdate() >= fromdate and getdate < todate() then 1 else 2 end), id desc
) t2;
You can use a temp table. First get the matching data, then update the missing IDs with the Max(Id) as below:
select table1.*, table2.ID
into #temp
from table1 t1
left outer join table2 t2 on t1.PatientID = t2.PatientID
where getdate() between t2.fromdate and t2.todate
update t
set ID = (select max(ID) from table2 t2 where t.PatientID=t2.PatientID)
from #temp t
where t.ID is null
select * from #temp
You can do a union, only one part will have value:
;WITH cte(ID,PatientID,FromDate,ToDate)
AS
(
SELECT t2.ID,t2.PatientID,t2.FromDate,t2.ToDate
FROM Table1 t1
INNER JOIN Table2 t2
ON t1.PatientID=t2.PatientID
WHERE t2.FromDate >=GETDATE() AND t2.Todate<=GETDATE()
),
cte1 (ID)
AS
(
SELEC TMAX(ID) FROM Table2 WHERE NOT EXISTS(SELECT 1 FROM cte)
)
SELECT ID,PatientID,FromDate,ToDate
FROM cte
UNION ALL
SELECT ID,NULL,NULL,NULL
FROM cte1

Get previous record where date is not null - SQL SERVER

I have two tables as below :
table 1:
ID B C D E Date
1 b c D E 2018/10/10
1 c d A B 2018/10/14
Table 2 :
ID B C Date
1 b c 2018/10/10
1 x y 2018/10/11
1 y x 2018/10/12
1 p q 2018/10/13
1 c d 2018/10/14
Table A has 6 columns, where as table 2 has 4 columns.
Using left join the result is :
Select * from Table2 t2 left join table1 t1
on t2.id=t1.id and t2.Date = t1.Date
Left join result is :
ID B C D E Date1 ID B C Date2
1 b c D E 2018/10/10 1 b c 2018/10/10
- - - - - - 1 x y 2018/10/11
- - - - - - 1 y x 2018/10/12
- - - - - 1 p q 2018/10/13
1 c d A B 2018/10/14 1 c d 2018/10/14
Note :
'-' represnt NULL.
Dates are sorted in left join result -- order by table2.date ,
table1.date asc
I have taken result of join in cte. not able to
generate final result.
Expected Result :
ID B C D E Date
1 b c D E 2018/10/10
1 x y D E 2018/10/11
1 y x D E 2018/10/12
1 p q D E 2018/10/13
1 c d A B 2018/10/14
Where Date from table1 is null in left join result, the search for previous non null date of table 1 , which would be just less than current date of table 2.
And fetch values of column D and E from there and keep the values of column B and C from current record where Date1 is null.
Being a newbie in sql, I am stuck in this. Please help.
Thanks in advance.
use outer apply with top 1. It will give your result, is fast and very short:
-- create data from example:
-- ------------------------
select * into #Table1 from
(select 1 as ID, 'b' as B, 'c' as C, 'D' as D, 'E' as E, cast('2018-10-10' as date) as [Date]
union all select 1, 'c', 'd', 'A', 'B', '2018-10-14')t
select * into #Table2 from
(select 1 as ID, 'b' as B, 'c' as C , cast('2018-10-10' as date) as [Date]
union all select 1, 'x', 'y', '2018-10-11'
union all select 1, 'y', 'x', '2018-10-12'
union all select 1, 'p', 'q', '2018-10-13'
union all select 1, 'c', 'd', '2018-10-14')t
-- SOLUTION
-- --------
select
T2.ID,
T2.B,
T2.C,
T1.D,
T1.E,
T2.[Date]
from
#Table2 T2
outer apply
(
select top 1 * from #Table1 T1
where T1.ID=T2.ID and T1.[Date] <= T2.[Date]
order by T1.[Date] desc
) T1
-- clean everything
-- ----------------
drop table #Table1
drop table #Table2
If you are using SQL Server 2012 or later, the following query returns the value you expected. I have used CTE and first_value() function and the query is optimized.
with
cte
as
(
select
t2.ID ,
t2.B ,
t2.C ,
t1.D ,
t1.E ,
t2.[Date] ,
sum(case when t1.D is null then 0 else 1 end) over (order by t2.[Date]) as D_partition,
sum(case when t1.E is null then 0 else 1 end) over (order by t2.[Date]) as E_partition
from
Table2 t2
left join
table1 t1
on
t2.id = t1.id
and
t2.[Date] = t1.[Date]
)
select
cte.ID ,
cte.B ,
cte.C ,
first_value(D) over(partition by D_partition order by D desc) as D ,
first_value(E) over(partition by E_partition order by E desc) as E ,
cte.Date
from
cte;
It seems you are left joining table2 to table 1, and you want the values of table1 if they exist, else from table2. This "if" in the level of data is typically implemented with the CASE function. However, in your case, we can use a more specific function, ISNULL(a,b), which returns a when a has a value and b when a is null:
select
t2.ID,
isnull(t1.B,t2.B) as B,
isnull(t1.C,t2.C) as C,
isnull(t1.D,t2.D) as D,
isnull(t1.E,t2.E) as E,
isnull(t1.[Date],t2.[Date]) as [Date]
from Table2 t2
left join table1 t1
on t2.id=t1.id and t2.Date = t1.Date
However, are you sure that t2.Date = t1.Date is needed? Ususally, the column named ID is unique/primary key, so that would make the additional join condition on date redundunt. You should remove it if this is the case.
Could you check to see if below is something you are looking for?
Select t2.ID,t2.B,t2.C,t1.D,t1.E, t2.Date from Table2 t2 left join table1 t1
on t2.id=t1.id and (t2.Date >= t1.Date)
where not exists (select 1 from table1 t12 where t2.Date > t1.Date and t2.Date >= t12.Date and t12.Date > t1.Date)
Here we tried to open table 1 two times (t1 and t12) to make sure the date in table 2 must >= a date in table1 and < an other date.

join tables on different amount of columns

I wanna replace values in the column Attribute in T1 with the values from T2. If sub_id is NULL in T2, then replace f values in T1 with t for all the rows with ID in T2, otherwise do it only for the rows with sub_id.
Below are the dummy tables and the result I get.
WITH T1 AS ( SELECT 1 AS id, 2 as sub_id, '2017-08-20' AS date, 'f' AS attribute UNION ALL
SELECT 1, 3 as sub_id, '2017-08-19', 'f' UNION ALL
SELECT 1, 4 as sub_id,'2017-08-18', 'f' UNION ALL
SELECT 2, 5 as sub_id,'2017-08-20', 'f' UNION ALL
SELECT 2, 6 as sub_id,'2017-08-19', 'f'
),
T2 AS (
SELECT 1 AS id, null as sub_id, '2017-08-19' AS date, '2017-08-25' AS date_end, 't' AS attribute UNION ALL
SELECT 2, 5 as sub_id,'2017-08-19', '2017-08-25' AS date_end, 't' UNION ALL
SELECT 2, 6 as sub_id,'2017-08-19', '2017-08-25' AS date_end, 't'
)
SELECT
a.id,
a.date,
a.sub_id,
COALESCE(b.attribute, a.attribute) as attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id AND a.date >= b.date AND a.date <= b.date_end
AND a.sub_id = case when b.sub_id is not null then a.sub_id end
In the above result I wanna have the attribute column with t values in all the eligible rows.
I achieved what I want with another subquery:
WITH T1 AS ( SELECT 1 AS id, 2 as sub_id, '2017-08-20' AS date, 'f' AS attribute UNION ALL
SELECT 1, 3 as sub_id, '2017-08-19', 'f' UNION ALL
SELECT 1, 4 as sub_id,'2017-08-18', 'f' UNION ALL
SELECT 2, 5 as sub_id,'2017-08-20', 'f' UNION ALL
SELECT 2, 6 as sub_id,'2017-08-19', 'f'
),
T2 AS (
SELECT 1 AS id, null as sub_id, '2017-08-19' AS date, '2017-08-25' AS date_end, 't' AS attribute UNION ALL
SELECT 2, 5 as sub_id,'2017-08-19', '2017-08-25' AS date_end, 't' UNION ALL
SELECT 2, 6 as sub_id,'2017-08-19', '2017-08-25' AS date_end, 't'
) ,
adv_level AS ( SELECT
a.id,
a.date,
a.sub_id,
COALESCE(b.attribute, a.attribute) as attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id AND a.date >= b.date AND a.date <= b.date_end AND a.sub_id = case when b.sub_id is not null then a.sub_id end)
SELECT
a.id,
a.date,
a.sub_id,
COALESCE(b.attribute, a.attribute) as attribute
FROM adv_level AS a
LEFT JOIN T2 AS b
ON a.id = b.id AND a.date >= b.date AND a.date <= b.date_end
but is there a way to do it just once?

Return matching rows only SQL Server

I am sure this simple but i am really stuck. Here is a example of the resultset i want from two tables that have the same structure and in this case data or records
TableA
Ref cola colb id
------------------
1 a b 14
1 a b 24
TableB
Ref cola colb id
------------------
1 a b 1
1 a b 2
1 a b 3
Expected result:
Ref cola colb id Ref1 cola1 colb1 id1
----------------------------------------
1 a b 14 1 a b 1
1 a b 24 1 a b 2
Use:
SELECT *
FROM table1 t1
JOIN Table2 t2
ON t1.Ref =t2.Ref AND t1.cola = t2.cola
AND t1.colb = t2.colb AND t1.id = t2.id
or
SELECT *
FROM table1 t1
JOIN Table2 t2
USING ( Ref , cola , colb, id )
one more way would be
;with cte
as
(
select Ref, cola, colb, id,
hashbytes('sha1',concat(Ref, cola, colb)) as tb1hash
from table1
)
select
t1.Ref, --all required cols
from cte c
join
(
select Ref, cola, colb, id,
hashbytes('sha1',concat(Ref, cola, colb)) as tb2hash
from table2
) b
on
b.tb2hash=c.tb1hash
Guessing:
;WITH TableAWithRowNum
AS (
SELECT *, ROW_NUMBER(ORDER BY id) AS RowNum
FROM dbo.TableA
), TableBWithRowNum
AS (
SELECT *, ROW_NUMBER(ORDER BY id) AS RowNum
FROM dbo.TableB
)
SELECT a.*, b.*
FROM TableAWithRowNum a
INNER JOIN TableBWithRowNum b ON a.Ref = b.Ref
--AND other join predicates on ColA, ... etc.
AND a.RowNum = b.RowNum