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

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;

Related

Finding time differences between each row in a specific column in SQL Server

Still learning SQL, but I'm trying to see if there are any Customers that have a time frame within 24 hours of each other. So in this example ID 1 and 4 meet this criteria.
CustID Date
1 2018-04-10 11:21:00.000
1 2018-03-05 18:14:00.000
1 2018-03-05 22:53:00.000
2 2018-04-10 11:21:00.000
2 2018-03-27 14:57:00.000
2 2018-04-04 20:00:00.000
3 2018-04-10 11:21:00.000
3 2018-02-10 11:21:00.000
3 2018-04-24 11:29:00.000
4 2018-04-10 11:21:00.000
4 2018-04-10 11:20:00.000
4 2018-04-24 11:29:00.000
I'm thinking about doing something like
SELECT CustId
From Cars c
CROSS APPLY(
SELECT Date
FROM Cars
Where Date != c.Date)
WHERE Date - c.Date < 24 hours
Use lag():
select distinct custid
from (select c.*,
lag(c.date) over (partition by c.custid order by c.date) as prev_date
from cars c
) c
where date < dateadd(hour, 24, prev_date);
This answer is based on sql-server, but you should be able to translate as needed. I also assumed you had a requirement where the same datetime between two customers can't be the same. If that's a false assumption, remove the where clause. A simple self-join should get you there.
declare #t table (id int, dt datetime)
insert into #t values ('1','2018-04-10 11:21:00.000')
insert into #t values ('1','2018-03-05 18:14:00.000')
insert into #t values ('1','2018-03-05 22:53:00.000')
insert into #t values ('2','2018-04-10 11:21:00.000')
insert into #t values ('2','2018-03-27 14:57:00.000')
insert into #t values ('2','2018-04-04 20:00:00.000')
insert into #t values ('3','2018-04-10 11:21:00.000')
insert into #t values ('3','2018-02-10 11:21:00.000')
insert into #t values ('3','2018-04-24 11:29:00.000')
insert into #t values ('4','2018-04-10 11:21:00.000')
insert into #t values ('4','2018-04-10 11:20:00.000')
insert into #t values ('4','2018-04-24 11:29:00.000')
select
t1.id, t2.id
from #t t1
join #t t2 on t2.dt between dateadd(hh, -24,t1.dt) and t1.dt and t1.id<>t2.id
where t1.dt<>t2.dt

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

merging child-table date range with parent

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

Contiguous Dates

Here is the table that I am working with:
MemberID MembershipStartDate MembershipEndDate
=================================================================
123 2010-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2011-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2013-05-01 00:00:00.000 2013-12-31 00:00:00.000
123 2014-01-01 00:00:00.000 2014-12-31 00:00:00.000
123 2015-01-01 00:00:00.000 2015-03-31 00:00:00.000
What I want is to create one row that shows continuous membership,
and a second row if the membership breaks by more than 2 days, with a new start and end date..
So the output I am looking for is like:
MemberID MembershipStartDate MembershipEndDate
=================================================================
123 2010-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2013-05-01 00:00:00.000 2015-03-31 00:00:00.000
There is a memberID field attached to these dates which is how they are grouped.
I've had to deal with this kind of thing before
I use something like this
USE tempdb
--Create test Data
DECLARE #Membership TABLE (MemberID int ,MembershipStartDate date,MembershipEndDate date)
INSERT #Membership
(MemberID,MembershipStartDate,MembershipEndDate)
VALUES (123,'2010-01-01','2012-12-31'),
(123,'2011-01-01','2012-12-31'),
(123,'2013-05-01','2013-12-31'),
(123,'2014-01-01','2014-12-31'),
(123,'2015-01-01','2015-03-31')
--Create a table to hold all the dates that might be turning points
DECLARE #SignificantDates Table(MemberID int, SignificantDate date, IsMember bit DEFAULT 0)
--Populate table with the start and end dates as well as the days just before and just after each period
INSERT #SignificantDates (MemberID ,SignificantDate)
SELECT MemberID, MembershipStartDate FROM #Membership
UNION
SELECT MemberID,DATEADD(day,-1,MembershipStartDate ) FROM #Membership
UNION
SELECT MemberID,MembershipEndDate FROM #Membership
UNION
SELECT MemberID,DATEADD(day,1,MembershipEndDate) FROM #Membership
--Set the is member flag for each date that is covered by a membership
UPDATE sd SET IsMember = 1
FROM #SignificantDates sd
JOIN #Membership m ON MembershipStartDate<= SignificantDate AND SignificantDate <= MembershipEndDate
--To demonstrate what we're about to do, Select all the dates and show the IsMember Flag and the previous value
SELECT sd.MemberID, sd.SignificantDate,sd.IsMember, prv.prevIsMember
FROM
#SignificantDates sd
JOIN (SELECT
MemberId,
SignificantDate,
IsMember,
Lag(IsMember,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate desc) AS prevIsMember FROM #SignificantDates
) as prv
ON sd.MemberID = prv.MemberID
AND sd.SignificantDate = prv.SignificantDate
ORDER BY sd.MemberID, sd.SignificantDate
--Delete the ones where the flag is the same as the previous value
delete sd
FROM
#SignificantDates sd
JOIN (SELECT MemberId, SignificantDate,IsMember, Lag(IsMember,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate) AS prevIsMember FROM #SignificantDates ) as prv
ON sd.MemberID = prv.MemberID
AND sd.SignificantDate = prv.SignificantDate
AND prv.IsMember = prv.prevIsMember
--SELECT the Start date for each period of membership and the day before the following period of non membership
SELECT
nxt.MemberId,
nxt.SignificantDate AS MembershipStartDate,
DATEADD(day,-1,nxt.NextSignificantDate) AS MembershipEndDate
FROM
(
SELECT
MemberID,
SignificantDate,
LEAd(SignificantDate,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate) AS NextSignificantDate,
IsMember
FROM #SignificantDates
) nxt
WHERE nxt.IsMember = 1

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