merging child-table date range with parent - sql

I asked a similar question in the past, and imagine there is already an answer for this, but I can't seem to figure out the wording to locate it.
I have a parent table with a date range and a child table that can have multiple date ranges within the date range of the parent table. I need to merge them into a row for each record in the series. An example should better explain what I'm trying:
Table 1 (Parent)
Date1 Date2 Person
1/1/16 7/1/16 A
and
Table 2 (Child)
Date1 Date2 Person
2/1/16 2/4/16 B
3/6/16 3/8/16 C
5/4/16 5/9/16 B
I want a merged table like so:
Merged Table
Date1 Date2 Person
1/1/16 2/1/16 A
2/1/16 2/4/16 B
2/4/16 3/6/16 A
3/6/16 3/8/16 C
3/8/16 5/4/16 A
5/4/16 5/9/16 B
5/9/16 7/1/16 A
There must be a somewhat easy way to do this? I'm fine with a complicated while loop, but am stumped on the logic for this too.

As requested, this is a modified version which considers multiple PK/FK
Declare #Table1 table (PK int,Date1 Date,Date2 Date, Person varchar(25))
Insert into #Table1 values
(1,'1/1/16','7/1/16','A'),
(2,'2/1/16','8/1/16','A')
Declare #Table2 table (FK int,Date1 Date,Date2 Date, Person varchar(25))
Insert into #Table2 values
(1,'2/1/16','2/4/16','B'),
(1,'3/6/16','3/8/16','C'),
(1,'5/4/16','5/9/16','B'),
(2,'3/1/16','3/4/16','B'),
(2,'3/6/16','3/8/16','C'),
(2,'5/4/16','5/9/16','B')
;with cteBase as (
Select *
,Gap1 = Date2
,Gap2 = Lead(Date1,1,(Select max(Date2) from #Table1 Where FK=PK)) over (Partition By FK Order by Date1)
From #Table2
)
Select PK,Date1,Date2=(Select min(Date1) from #Table2 Where FK=PK),Person From #Table1
Union All
Select FK,Date1,Date2,Person from cteBase
Union All
Select FK,Date1=Gap1,Date2=Gap2,Person=B.Person
From cteBase A
Join #Table1 B on FK=PK
Where Gap1<>Gap2
Order by PK,Date1
Returns
PK Date1 Date2 Person
1 2016-01-01 2016-02-01 A
1 2016-02-01 2016-02-04 B
1 2016-02-04 2016-03-06 A
1 2016-03-06 2016-03-08 C
1 2016-03-08 2016-05-04 A
1 2016-05-04 2016-05-09 B
1 2016-05-09 2016-07-01 A
2 2016-02-01 2016-03-01 A
2 2016-03-01 2016-03-04 B
2 2016-03-04 2016-03-06 A
2 2016-03-06 2016-03-08 C
2 2016-03-08 2016-05-04 A
2 2016-05-04 2016-05-09 B
2 2016-05-09 2016-08-01 A

Perhaps something like this?
Declare #Table1 table (Date1 Date,Date2 Date, Person varchar(25))
Insert into #Table1 values
('1/1/16','7/1/16','A')
Declare #Table2 table (Date1 Date,Date2 Date, Person varchar(25))
Insert into #Table2 values
('2/1/16','2/4/16','B'),
('3/6/16','3/8/16','C'),
('5/4/16','5/9/16','B')
;with cteBase as (
Select *
,Gap1 = Date2
,Gap2 = Lead(Date1,1,(Select max(Date2) from #Table1)) over (Order by Date1)
From #Table2
)
Select Date1,Date2=(Select min(Date1) from #Table2),Person From #Table1
Union All
Select Date1,Date2,Person from cteBase
Union All
Select Date1=Gap1,Date2=Gap2,Person=B.Person
From cteBase A
Join #Table1 B on 1=1
Order by Date1
Returns
Date1 Date2 Person
2016-01-01 2016-02-01 A
2016-02-01 2016-02-04 B
2016-02-04 2016-03-06 A
2016-03-06 2016-03-08 C
2016-03-08 2016-05-04 A
2016-05-04 2016-05-09 B
2016-05-09 2016-07-01 A

Related

Get max date for each from either of 2 columns

I have a table like below
AID BID CDate
-----------------------------------------------------
1 2 2018-11-01 00:00:00.000
8 1 2018-11-08 00:00:00.000
1 3 2018-11-09 00:00:00.000
7 1 2018-11-15 00:00:00.000
6 1 2018-12-24 00:00:00.000
2 5 2018-11-02 00:00:00.000
2 7 2018-12-15 00:00:00.000
And I am trying to get a result set as follows
ID MaxDate
-------------------
1 2018-12-24 00:00:00.000
2 2018-12-15 00:00:00.000
Each value in the id columns(AID,BID) should return the max of CDate .
ex: in the case of 1, its max CDate is 2018-12-24 00:00:00.000 (here 1 appears under BID)
in the case of 2 , max date is 2018-12-15 00:00:00.000 . (here 2 is under AID)
I tried the following.
1.
select
g.AID,g.BID,
max(g.CDate) as 'LastDate'
from dbo.TT g
inner join
(select AID,BID,max(CDate) as maxdate
from dbo.TT
group by AID,BID)a
on (a.AID=g.AID or a.BID=g.BID)
and a.maxdate=g.CDate
group by g.AID,g.BID
and 2.
SELECT
AID,
CDate
FROM (
SELECT
*,
max_date = MAX(CDate) OVER (PARTITION BY [AID])
FROM dbo.TT
) AS s
WHERE CDate= max_date
Please suggest a 3rd solution.
You can assemble the data in a table expression first, and the compute the max for each value is simple. For example:
select
id, max(cdate)
from (
select aid as id, cdate from t
union all
select bid, cdate from t
) x
group by id
You seem to only care about values that are in both columns. If this interpretation is correct, then:
select id, max(cdate)
from ((select aid as id, cdate, 1 as is_a, 0 as is_b
from t
) union all
(select bid as id, cdate, 1 as is_a, 0 as is_b
from t
)
) ab
group by id
having max(is_a) = 1 and max(is_b) = 1;

T-SQL Join Date to Nearest Date with Non-Zero Value in Another Column

I have table:
id date
1 2018-03-20
1 2018-02-05
3 2018-03-18
7 2018-03-12
I have table_2:
id date_2 value
1 2018-03-20 0
1 2018-03-19 100
1 2018-02-05 50
3 2018-03-18 200
I would like to join these to produce one table that looks like this:
id date date_2 value
1 2018-03-20 2018-03-19 100
1 2018-02-05 2018-02-05 50
3 2018-03-18 2018-03-18 200
7 2018-03-12 NULL NULL
If date corresponds to a record in table_2 that has a non-zero value, then that record should be returned. If date corresponds to a record in table_2 that has a value of 0 or does not exist in table_2, then the record with the most recent date_2 prior to date should be returned (provided it has a non-zero value).
In the tables, id 1 with date 2018-03-20 correlates to a record that has value 0. Therefore, the record from table_2 with date_2 of 2018-03-19 should be returned.
How would I join these tables to arrive at this result?
Using outer apply:
Editing based on comment that ID is also part of join
declare #t1 as table (id int, date1 date)
insert into #t1
values
(1, '2018-03-20')
,(3, '2018-03-18')
,(7, '2018-03-12')
declare #t2 as table(
id int, date_2 date ,value int)
insert into #t2
values
(1 ,'2018-03-20', 0 )
,(1 ,'2018-03-19' , 100 )
,(3 ,'2018-03-18', 200)
select t.* ,t2.date_2,t2.value
from #t1 t
outer apply (select top 1 date_2
, value
from #t2 t2
where t2.value>0
and t.date1>=t2.date_2
and t.ID=t2.ID
order by t2.date_2 desc) t2
Removed Result Set

SQL Select: Get the previous Date in column

I have a table in my SQL server with some dates. Now I would like to create a Select which gives me a column with all dates then a second column with the previous dates of the first column and a third column with the previous dates of the previous date column(c2).
For Exempel:
c1(orginal) c2(prevoius of c1) c3(previous of c2)
2017-10-15 00:00:00 2017-04-15 00:00:00 2016-10-15 00:00:00
2017-04-15 00:00:00 2016-10-15 00:00:00 2016-04-15 00:00:00
2016-10-15 00:00:00 2016-04-15 00:00:00 2015-10-15 00:00:00
2016-04-15 00:00:00 2015-10-15 00:00:00 null
2015-10-15 00:00:00 null null
Example with colors:
Is it possible to make a SELECT where the first row would be the first date from column 1, the second from column 1 and the third from column 1. The second row would be the second date from column1, the third from column 1 and the forth from column 1.
My current query
SELECT DISTINCT(BFSSTudStichdatum) AS C1, BFSSTudStichdatum AS C2,
BFSSTudStichdatum AS C3 FROM BFSStudierende
ORDER BY C1 DESC
result:
Because you need to get a distinct list of your dates first, you will need to split your query into a common table expression and then use lag to get your c2 and c3 values:
declare #t table(c1 datetime);
insert into #t values ('2017-10-15 00:00:00'),('2017-04-15 00:00:00'),('2016-10-15 00:00:00'),('2016-04-15 00:00:00'),('2015-10-15 00:00:00')
,('2017-10-15 00:00:00'),('2017-04-15 00:00:00'),('2016-10-15 00:00:00'),('2016-04-15 00:00:00'),('2015-10-15 00:00:00');
with c as
(
select distinct c1
from #t
)
select c1
,lag(c1, 1) over (order by c1) as c2
,lag(c1, 2) over (order by c1) as c3
from c
order by c1 desc;
Output:
+-------------------------+-------------------------+-------------------------+
| c1 | c2 | c3 |
+-------------------------+-------------------------+-------------------------+
| 2017-10-15 00:00:00.000 | 2017-04-15 00:00:00.000 | 2016-10-15 00:00:00.000 |
| 2017-04-15 00:00:00.000 | 2016-10-15 00:00:00.000 | 2016-04-15 00:00:00.000 |
| 2016-10-15 00:00:00.000 | 2016-04-15 00:00:00.000 | 2015-10-15 00:00:00.000 |
| 2016-04-15 00:00:00.000 | 2015-10-15 00:00:00.000 | NULL |
| 2015-10-15 00:00:00.000 | NULL | NULL |
+-------------------------+-------------------------+-------------------------+
Are you looking for lag()?
select col1,
lag(col1, 1) over (order by col1) as col1_prev,
lag(col1, 2) over (order by col1) as col1_prev2
from t;
For SQL Server 2008 and later:
WITH DataSource AS
(
SELECT DISTINCT *
,DENSE_RANK() OVER (ORDER BY c1) rowID
FROM #t
)
SELECT DS1.[c1]
,DS2.[c1]
,DS3.[c1]
FROM DataSource DS1
LEFT JOIN DataSource DS2
ON DS1.[rowID] = DS2.[rowID] + 1
LEFT JOIN DataSource DS3
ON DS1.[rowID] = DS3.[rowID] + 2;
For SQL Server 2008 and later:
Hope you did want an auto column generation with lagging one value behind from the past columns first value. Try the following snippet.
Created a dynamic query with respect to the number of columns in the dataset.
create table BFSStudierende
(
BFSSTudStichdatum datetime
)
insert into BFSStudierende
Select getdate()
union
Select dateadd(day,1,getdate())
union
Select dateadd(day,2,getdate())
union
Select dateadd(day,3,getdate())
union
Select dateadd(day,4,getdate())
Declare #count int=(Select count(BFSSTudStichdatum ) from BFSStudierende)
Declare #query nvarchar(max)='with BFSStudierendeCte as (Select *,row_number() over(order by BFSSTudStichdatum)rn from BFSStudierende) Select *from BFSStudierendeCte as BFSStudierendeCte1'
Declare #i int=2 ;
Declare #j int ;
while(#i<=#count)
begin
Set #j=#i-1
Set #query=#query+' left outer join BFSStudierendeCte as BFSStudierendeCte'+cast(#i as varchar(5)) +' on BFSStudierendeCte1.rn+'+cast(#j as varchar(5))+'=BFSStudierendeCte'+cast(#i as varchar(5))+'.rn';
set #i+=1;
End
print #query
Execute(#query)
Note: Duplicate date will not be removed from the results. If you require duplicate to be removed. Please change the following line in the above snippet.
Declare #count int=(Select count(distinct BFSSTudStichdatum ) from BFSStudierende)
Declare #query nvarchar(max)='with BFSStudierendeCte as (Select *,row_number() over(order by BFSSTudStichdatum)rn from(Select distinct BFSSTudStichdatum from BFSStudierende)l ) Select *from BFSStudierendeCte as BFSStudierendeCte1'

SQL - How to cross-join two table to repeat values

I have a 2 tables that look like this:
MonthEndDate
2016-06-30 00:00:00.000
2016-07-31 00:00:00.000
2016-08-31 00:00:00.000
2016-09-30 00:00:00.000
2016-10-31 00:00:00.000
2016-11-30 00:00:00.000
2016-12-31 00:00:00.000
AND
MonthEndDate CustomerId Flag
2016-06-30 00:00:00.000 123 1
2016-07-31 00:00:00.000 123 1
2016-08-31 00:00:00.000 123 1
2016-09-30 00:00:00.000 123 1
I would like an output that looks like this:
MonthEndDate CustomerId Flag
2016-06-30 00:00:00.000 123 1
2016-07-31 00:00:00.000 123 1
2016-08-31 00:00:00.000 123 1
2016-09-30 00:00:00.000 123 1
2016-10-31 00:00:00.000 123 0
2016-11-30 00:00:00.000 123 0
2016-12-31 00:00:00.000 123 0
Table 1 is a DimDate table that has month end date.
Table
2 is the CustomerInfo table.
Each customer has a Flag set to 1 whenever that customer has a value for the given Month End.
I want to get an output that will have every Month End Date (that's why I'm suing DimDate table) and when a customer does not have a value for the Month End I want the flag to show 0.
I'm using SQL Server 2005
Here is some sample code I used:
DECLARE #table1 TABLE
(
MonthEndDate DATETIME
)
INSERT INTO #table1
VALUES('2016-06-30 00:00:00.000')
INSERT INTO #table1
VALUES('2016-07-31 00:00:00.000')
INSERT INTO #table1
VALUES('2016-08-31 00:00:00.000')
INSERT INTO #table1
VALUES('2016-09-30 00:00:00.000')
INSERT INTO #table1
VALUES('2016-10-31 00:00:00.000')
INSERT INTO #table1
VALUES('2016-11-30 00:00:00.000')
INSERT INTO #table1
VALUES('2016-12-31 00:00:00.000')
DECLARE #table2 TABLE
(
MonthEndDate DATETIME
,CustomerId INT
,Flag INT
)
INSERT INTO #table2
VALUES('2016-06-30 00:00:00.000',123,1)
INSERT INTO #table2
VALUES('2016-07-31 00:00:00.000',123,1)
INSERT INTO #table2
VALUES('2016-08-31 00:00:00.000',123,1)
INSERT INTO #table2
VALUES('2016-09-30 00:00:00.000',123,1)
SELECt * FROM #table1
SELECt * FROM #table2
You need to do a CROSS JOIN on to get all combinations of MonthEndDate and CustomerId. When you have that, do a LEFT JOIN on table2 to get the Flag:
SELECT
t1.MonthEndDate,
c.CustomerId,
Flag = ISNULL(t2.Flag, 0)
FROM #table1 t1
CROSS JOIN (SELECT DISTINCT CustomerId FROM #table2) c
LEFT JOIN #table2 t2
ON t1.MonthEndDate = t2.MonthEndDate
AND c.CustomerId = t2.CustomerId
I think you just want a left join:
select t1.*, coalesce(t2.flag, 0) as flag
from #table1 t1 left join
#table2 t2
on t1.MonthEndDate = t2.MonthEndDate;

Get the latest value on each date

I have two tables with the following structure:
DECLARE #Table1 TABLE
(
IdColumn INT,
DateColumn DATETIME
)
DECLARE #Table2 TABLE
(
IdColumn INT,
DateColumn DATETIME,
Value NUMERIC(18,2)
)
What i want to do is get the latest value from table2 having a less or equal date in table1.
This is the query i build:
SET NOCOUNT ON
DECLARE #Table1 TABLE
(
IdColumn INT,
DateColumn DATETIME
)
DECLARE #Table2 TABLE
(
IdColumn INT,
DateColumn DATETIME,
Value NUMERIC(18,2)
)
DECLARE #RefDate DATETIME='2012-09-01'
DECLARE #NMonths INT
DECLARE #MonthsCounter INT=1
SELECT #NMonths=DATEDIFF(MM,'2012-09-01','2013-03-01')
WHILE #MonthsCounter<=#NMonths
BEGIN
INSERT INTO #Table1
SELECT 1,#RefDate
SET #RefDate=DATEADD(MM,1,#RefDate);
SET #MonthsCounter+=1;
END
INSERT #Table2
SELECT 1,'2012-09-01',1000
UNION
SELECT 1,'2012-12-01',5000
UNION
SELECT 1,'2013-01-01',3000
SELECT
T1.IdColumn,
T1.DateColumn,
T2.Value
FROM #Table1 T1
LEFT JOIN #Table2 T2
ON T2.IdColumn=T1.IdColumn AND T1.DateColumn>=t2.DateColumn
The problem is when a new value comes with a more recent date, i get all values until that date.
IdColumn DateColumn Value
----------- ----------------------- ---------------------------------------
1 2012-09-01 00:00:00.000 1000.00
1 2012-10-01 00:00:00.000 1000.00
1 2012-11-01 00:00:00.000 1000.00
1 2012-12-01 00:00:00.000 1000.00
1 2012-12-01 00:00:00.000 5000.00
1 2013-01-01 00:00:00.000 1000.00
1 2013-01-01 00:00:00.000 5000.00
1 2013-01-01 00:00:00.000 3000.00
1 2013-02-01 00:00:00.000 1000.00
1 2013-02-01 00:00:00.000 5000.00
1 2013-02-01 00:00:00.000 3000.00
The desired output is this one:
IdColumn DateColumn Value
----------- ----------------------- ---------------------------------------
1 2012-09-01 00:00:00.000 1000.00
1 2012-10-01 00:00:00.000 1000.00
1 2012-11-01 00:00:00.000 1000.00
1 2012-12-01 00:00:00.000 5000.00
1 2013-01-01 00:00:00.000 3000.00
1 2013-02-01 00:00:00.000 3000.00
How can i solve this ?
Thanks.
I am just posting Gordon's Answer with correct syntax :
select t1.*,
(select top 1 value
from #table2 t2
where t2.IdColumn = t1.IdColumn and
t2.DateColumn <= t1.DateColumn
order by t2.DateColumn desc
) t2value
from #table1 t1
I would do this with a correlated subquery:
select t1.*,
(select top 1 value
from #table2 t2
where t2.idColumn = t1.idColumn and
t2.dateColumn <= t1.dateColumn
order by t2.dateColumn desc
) t2value
from #table1 t1;
After comment, try this:
INSERT INTO #Table1 (IdColumn, DateColumn)
SELECT IdColumn, DateColumn
FROM #Table2 t
WHERE NOT EXISTS
(
SELECT 'X'
FROM #Table2 tcopy
where t.IdColumn = tcopy.IdColumn
and convert(date, t.DateColumn) = convert(date, tcopy.DateColumn)
and tCopy.DateColumn > t.DateColumn
)
I used ">" because you told me, there are no rows with the same date/time in your table2