Generate missing non-contiguous date ranges - tsql

I have the following example dataset (actual table is about 30000 rows).
I need to scan through the table and identify any non-contiguous dates for all references within the table that have non-contiguous data, and generate data to fill in the missing dates
So for the above reference I would need example data generated to be in bold below (showing existing rows for clarity):
ID
START_DATE
END_DATE
7172
2020-03-13
2020-10-22
7172
2020-10-23
2020-11-08
7172
2020-11-09
2020-11-09
7172
2020-11-10
2020-11-19
7172
2020-11-20
2020-11-20
7172
2020-11-21
2021-03-14
7172
2021-03-15
2021-03-17
7172
2021-03-18
2021-03-19
7172
2021-03-20
2021-03-28
7172
2021-03-29
2021-04-25
7172
2021-04-26
2021-30-04
7172
2021-05-01
2021-06-07
7172
2021-06-08
2021-06-08
7172
2021-06-09
2022-01-09
Can anyone help please?

You can make life a bit easier on respondents by making "Readily Consumable" Test Data, like the following example... (I added in the extra ID that #PatrickHurst did)...
--===== Create and populate the test table with the given test data.
-- This is NOT a part of the solution.
DROP TABLE IF EXISTS #Test;
GO
CREATE TABLE #Test
(
ID INT
, START_DATE DATE
, END_DATE DATE
)
;
INSERT INTO #Test (ID, START_DATE, END_DATE) VALUES
(7172,'2020-03-13','2020-10-22')
,(7172,'2020-10-23','2020-11-08')
--,(7172,'2020-11-09','2020-11-09') --Is Missing
,(7172,'2020-11-10','2020-11-19')
--,(7172,'2020-11-20','2020-11-20') --Is Missing
,(7172,'2020-11-21','2021-03-14')
,(7172,'2021-03-15','2021-03-17')
--,(7172,'2021-03-18','2021-03-19') --Is Missing
,(7172,'2021-03-20','2021-03-28')
,(7172,'2021-03-29','2021-04-25')
--,(7172,'2021-04-26','2021-04-30') --Is Missing (And you had a bad date here)
,(7172,'2021-05-01','2021-06-07')
--,(7172,'2021-06-08','2021-06-08') --Is Missing
,(7172,'2021-06-09','2022-01-09')
,(1234,'2020-03-13','2022-03-15')
,(1234,'2022-03-20','2022-03-25')
;
If you have SQL Server 2012 or above, LAG makes this easy (and someone can probably simplify it even more)...
WITH cte AS
(
SELECT *
,mSTART_DATE = LAG(DATEADD(dd,1,END_DATE),1,START_DATE) OVER (PARTITION BY ID ORDER BY START_DATE)
,mEND_DATE = DATEADD(dd,-1,START_DATE)
FROM #Test
)
--===== Finds the existing date ranges
SELECT ID, START_DATE, END_DATE, Comment = '' FROM #Test
UNION ALL
--===== Finds the missing date ranges
SELECT ID,mSTART_DATE,mEND_DATE, Comment = 'Was Missing' FROM cte WHERE mSTART_DATE <> START_DATE
ORDER BY ID, START_DATE
;
Here are the results...
If you truly only want the missing date ranges, then the following does it...
WITH cte AS
(
SELECT *
,mSTART_DATE = LAG(DATEADD(dd,1,END_DATE),1,START_DATE) OVER (PARTITION BY ID ORDER BY START_DATE)
,mEND_DATE = DATEADD(dd,-1,START_DATE)
FROM #Test
)
SELECT ID,START_DATE = mSTART_DATE,END_DATE = mEND_DATE FROM cte WHERE mSTART_DATE <> START_DATE
ORDER BY ID,START_DATE
;
Results:

2021-30-04 is a bad date (hehe).
I took your table and used it to generate a table variable, and added a couple of rows:
DECLARE #TABLE TABLE (ID INT, START_DATE DATE, END_DATE DATE)
INSERT INTO #TABLE (ID, START_DATE, END_DATE) VALUES
(7172, '2020-03-13', '2020-10-22'), (7172, '2020-10-23', '2020-11-08'),
(7172, '2020-11-10', '2020-11-19'), (7172, '2020-11-20', '2020-11-20'),
(7172, '2020-11-21', '2021-03-14'), (7172, '2021-03-15', '2021-03-17'),
(7172, '2021-03-20', '2021-03-28'), (7172, '2021-03-29', '2021-04-25'),
(7172, '2021-05-01', '2021-06-07'), (7172, '2021-06-09', '2022-01-09'),
(1234, '2020-03-13', '2022-03-15'), (1234, '2022-03-20', '2022-03-25');
If I understand correctly, we'd be looking to generate rows:
--(7172, '2020-11-09', '2020-11-09'),
--(7172, '2021-03-18', '2021-03-19'),
--(7172, '2021-04-26', '2021-03-04'),
--(7172, '2021-06-08', '2021-06-08'),
--(1234, '2022-03-16', '2022-03-19'),
I poked at this some, and got the result you're looking for using some recursive CTE voodoo. I can't promise this is going to be performant, but it might be a good starting point:
DECLARE #MaxDate DATE = (SELECT MAX(END_DATE) FROM #TABLE)
;WITH DateRange AS (
SELECT ID, MIN(END_DATE) AS Date FROM #TABLE GROUP BY ID--#MinDate AS Date
UNION ALL
SELECT ID, DATEADD(DAY,1,Date)
FROM DateRange
WHERE Date < #MaxDate
), MissingDates AS (
SELECT dr.ID, dr.Date, t.START_DATE, t.END_DATE
FROM DateRange dr
LEFT OUTER JOIN #TABLE t
ON dr.Date BETWEEN t.START_DATE AND t.END_DATE
AND dr.ID = t.ID
WHERE t.ID IS NULL
), ranges AS (
SELECT s.ID, s.date AS START_DATE, s.date AS END_DATE, DATEADD(DAY,1,s.Date) AS NEXT_DATE
FROM MissingDates s
LEFT OUTER JOIN MissingDates n
ON s.Date = DATEADD(DAY,1,n.Date)
AND s.ID = n.ID
WHERE n.Date IS NULL
UNION ALL
SELECT a.ID, a.START_DATE, r.Date AS END_DATE, DATEADD(DAY,1,r.Date) AS NEXT_DATE
FROM ranges a
INNER JOIN MissingDates r
ON a.NEXT_DATE = r.Date
AND a.ID = r.ID
)
SELECT ID AS ID, START_DATE, MAX(END_DATE) AS END_DATE
FROM ranges
GROUP BY ID, START_DATE
OPTION (MAXRECURSION 0)
ID START_DATE END_DATE
----------------------------
7172 2020-11-09 2020-11-09
7172 2021-03-18 2021-03-19
7172 2021-04-26 2021-04-30
7172 2021-06-08 2021-06-08
7172 2022-01-10 2022-03-25
1234 2022-03-16 2022-03-19

Related

Postgres Distinct Order by

I have to tables that I want to join, order by two timestamps and get as result the distinct values (for several columns). But it doesn't work.
See examples below:
CREATE TABLE t1(myid int, myyear int, mycol int, mdate timestamp);
INSERT INTO t1 VALUES
(11833,2022,1059,'2022-11-03 22:02:00'),(11834,2022,1059,'2022-11-17 19:56:41'),(11832,2021,1058,'2021-11-16 16:38:21'),(11839,2021,1057,'2021-11-10 18:08:09'),(11847,2021,1055,'2022-05-31 12:13:11'),(11847,2021,1055,'2022-05-31 12:13:11'),(11850,2021,1049,'2021-09-29 16:11:31'),(11853,2021,1046,'2022-01-24 11:44:41'),(11855,2021,1045,'2022-01-24 11:38:05'),(11865,2021,1044,'2022-01-24 11:23:51'),(11856,2021,1043,'2022-01-24 11:00:24'),(11840,2021,1042,'2021-11-30 12:28:13'),(11831,2021,1042,'2021-11-30 12:22:30'),(11846,2022,1042,'2022-11-02 15:06:00'),(11829,2022,1036,'2022-11-02 02:37:00'),(11826,2021,1035,'2021-09-24 13:07:48'),(11825,2021,1034,'2021-10-06 08:22:23'),(11830,2022,1033,'2022-11-03 21:18:00'),(11827,2022,1033,'2022-11-15 21:46:04'),(11828,2022,1032,'2022-11-08 16:44:08'),(11824,2022,1031,'2022-10-25 18:09:03'),(11823,2022,1031,'2022-11-02 03:10:00'),(11822,2022,1030,'2022-10-24 14:59:25')
;
CREATE TABLE t2(myid int, name varchar,idate timestamp);
INSERT INTO t2 VALUES
(11833,'Name1684','2023-01-10 15:52:55'),(11834,'Name1727','2023-01-10 15:52:55'),(11832,'Name609','2023-01-10 15:52:54'),(11839,'Name608','2023-01-10 15:52:59'),(11847,'Name606','2023-01-10 15:53:03'),(11847,'Name607','2023-01-10 15:53:03'),(11850,'Name605','2023-01-10 15:53:04'),(11853,'Name604','2023-01-10 15:53:05'),(11855,'Name603','2023-01-10 15:53:06'),(11865,'Name602','2023-01-10 15:53:10'),(11856,'Name601','2023-01-10 15:53:07'),(11840,'Name600','2023-01-10 15:52:59'),(11831,'Name1726','2023-01-10 15:52:53'),(11846,'Name1683','2023-01-10 15:53:03'),(11829,'Name1682','2023-01-10 15:52:52'),(11826,'Name599','2023-01-10 15:52:50'),(11825,'Name598','2023-01-10 15:52:49'),(11830,'Name1681','2023-01-10 15:52:52'),(11827,'Name1725','2023-01-10 15:52:51'),(11828,'Name1680','2023-01-10 15:52:51'),(11824,'Name1678','2023-01-10 15:52:48'),(11823,'Name1679','2023-01-10 15:52:48'),(11822,'Name1677','2023-01-10 15:52:47')
;
Show example which is not working before order and distinct:
Select
*
from t1
join t2
on t1.myid=t2.myid where t1.mycol =1059
=> Gives me this result:
myid
myyear
mycol
mdate
myid
name
idate
11833
2022
1059
2022-11-03 22:02:00
11833
Name1684
2023-01-10 15:52:55
11834
2022
1059
2022-11-17 19:56:41
11834
Name1727
2023-01-10 15:52:55
I want to order first by column mdate, then by idate (both to see the youngest dates) and then see only distinct values of (myyear and mycol)
CREATE TABLE expectedresult(myid int, myyear int,mycol int, mdate timestamp,name varchar,idate timestamp);
INSERT INTO expectedresult VALUES
(11834,2022,1059,'2022-11-17 19:56:41','Name1727','2023-01-10 15:52:55')
myid
myyear
mycol
mdate
name
idate
11834
2022
1059
2022-11-17 19:56:41
Name1727
2023-01-10 15:52:55
This is what I have tried:
create table t3 as(
select distinct on (subq1.myyear,subq1.mycol)
*
from(
Select
t1.myid,
t1.myyear,
t1.mycol,
t1.mdate,
t2.name,
t2.idate
from t1
join t2
on t1.myid=t2.myid
order by t1.mdate desc, t2.idate desc) subq1)
But it "distincts" the wrong row(because a younger mdate is available):
select * from t3 where mycol =1059
myid
myyear
mycol
mdate
name
idate
11833
2022
1059
2022-11-03 22:02:00
Name1684
2023-01-10 15:52:55
here also as fiddle:
https://dbfiddle.uk/eS5FoBeq
Best
SELECT DISTINCT ON (t1.myyear, t1.mycol)
*
FROM
t1
JOIN t2 ON t1.myid = t2.myid
ORDER BY
t1.myyear,
t1.mycol,
t1.mdate DESC,
t2.idate DESC;
or rewrite your query as:
SELECT DISTINCT ON (subq1.myyear, subq1.mycol)
*
FROM (
SELECT
t1.myid,
t1.myyear,
t1.mycol,
t1.mdate,
t2.name,
t2.idate
FROM
t1
JOIN t2 ON t1.myid = t2.myid
ORDER BY
t1.mdate DESC,
t2.idate DESC) subq1
ORDER BY
subq1.myyear,
subq1.mycol,
subq1.mdate DESC,
subq1.idate DESC;
if you distinct on (x,y) then you order by should be order by x,y,z
x, y is the columns that you want to get the unique row.In a group set (x,y), there are many rows, but you only want one, then you need order by z to get the only one row in a group set (x,y) in a deterministic way, otherwise, it will get a random row in a group set(x,y).
In general I try to avoid using distinct.
You can use row number to identify the number of elements with the same "myyear" and "mycol" and order them by newest date and then select the first value (rn =1).
with cte as(
Select
t1.myid,
t1.myyear,
t1.mycol,
t1.mdate,
t2.name,
t2.idate,
ROW_NUMBER() OVER (PARTITION BY myyear, mycol ORDER BY mdate DESC) as rn
from t1
join t2
on t1.myid=t2.myid
subq1)
)
Select *
from cte
where rn = 1

SQL Server - Select with Group By together Raw_Number

I'm using SQL Server 2000 (80). So, it's not possible to use the LAG function.
I have a code a data set with four columns:
Purchase_Date
Facility_no
Seller_id
Sale_id
I need to identify missing Sale_ids. So every sale_id is a 100% sequential, so the should not be any gaps in order.
This code works for a specific date and store if specified. But i need to work on entire data set looping looping through every facility_id and every seller_id for ever purchase_date
declare #MAXCOUNT int
set #MAXCOUNT =
(
select MAX(Sale_Id)
from #table
where
Facility_no in (124) and
Purchase_date = '2/7/2020'
and Seller_id = 1
)
;WITH TRX_COUNT AS
(
SELECT 1 AS Number
union all
select Number + 1 from TRX_COUNT
where Number < #MAXCOUNT
)
select * from TRX_COUNT
where
Number NOT IN
(
select Sale_Id
from #table
where
Facility_no in (124)
and Purchase_Date = '2/7/2020'
and seller_id = 1
)
order by Number
OPTION (maxrecursion 0)
My Dataset
This column:
case when
Sale_Id=0 or 1=Sale_Id-LAG(Sale_Id) over (partition by Facility_no, Purchase_Date, Seller_id)
then 'OK' else 'Previous Missing' end
will tell you which Seller_Ids have some sale missing. If you want to go a step further and have exactly your desired output, then filter out and distinct the 'Previous Missing' ones, and join with a tally table on not exists.
Edit: OP mentions in comments they can't use LAG(). My suggestion, then, would be:
Make a temp table that that has the max(sale_id) group by facility/seller_id
Then you can get your missing results by this pseudocode query:
Select ...
from temptable t
inner join tally N on t.maxsale <=N.num
where not exists( select ... from sourcetable s where s.facility=t.facility and s.seller=t.seller and s.sale=N.num)
> because the only way to "construct" nonexisting combinations is to construct them all and just remove the existing ones.
This one worked out
; WITH cte_Rn AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY Facility_no, Purchase_Date, Seller_id ORDER BY Purchase_Date) AS [Rn_Num]
FROM (
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id
FROM MyTable WITH (NOLOCK)
) a
)
, cte_Rn_0 as (
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id,
-- [Rn_Num] AS 'Skipped Sale'
-- , case when Sale_id = 0 Then [Rn_Num] - 1 Else [Rn_Num] End AS 'Skipped Sale for 0'
, [Rn_Num] - 1 AS 'Skipped Sale for 0'
FROM cte_Rn a
)
SELECT
Facility_no,
Purchase_Date,
Seller_id,
Sale_id,
-- [Skipped Sale],
[Skipped Sale for 0]
FROM cte_Rn_0 a
WHERE NOT EXISTS
(
select * from cte_Rn_0 b
where b.Sale_id = a.[Skipped Sale for 0]
and a.Facility_no = b.Facility_no
and a.Purchase_Date = b.Purchase_Date
and a.Seller_id = b.Seller_id
)
--ORDER BY Purchase_Date ASC

How to find the first and last date prior to a particular date in Postgresql?

I am a SQL beginner. I have trouble on finding the answer of this question
For each customer_id who made an order on January 1, 2006, what was their historical (prior to January 1, 2006) first and last order dates?
I've tried to solve it using a subquery. But I don't know how to find the first and last order dates prior to Jan 1.
Columns of table A:
customer_id
order_id
order_date
revenue
product_id
Columns of table B:
product_id
category_id
SELECT customer_id, order_date FROM A
(
SELECT customer_id FROM A
WHERE order_date = ‘2006-01-01’
)
WHERE ...
There are two subqueries actually. First for "For each customer_id who made an order on January 1, 2006" and second for "their historical (prior to January 1, 2006) first and last order dates"
So, first:
select customer_id from A where order_date = '2006-01-01';
and second:
select customer_id, min(order_date) as first_date, max(order_date) as last_date
from A
where order_date < '2006-01-01' group by customer_id;
Finally you need to get only those customers from second subquery who exists in the first one:
select customer_id, min(order_date) as first_date, max(order_date) as last_date
from A as t1
where
order_date < '2006-01-01' and
customer_id in (
select customer_id from A where order_date = '2006-01-01')
group by customer_id;
or, could be more efficient:
select customer_id, min(order_date) as first_date, max(order_date) as last_date
from A as t1
where
order_date < '2006-01-01' and
exists (
select 1 from A as t2
where t1.customer_id = t2.customer_id and t2.order_date = '2006-01-01')
group by customer_id;
You can use conditionals in aggregate functions:
SELECT customer_id, MIN(order_date) AS first, MAX(order_date) AS last FROM A
WHERE customer_id IN (SELECT customer_id FROM A WHERE order_date = ‘2006-01-01’) AND order_date < '2006-01-01'
GROUP BY customer_id;

Redshift PostgreSQL Distinct ON Operator

I have a data set that I want to parse for to see multi-touch attribution. The data set is made up by leads who responded to a marketing campaign and their marketing source.
Each lead can respond to multiple campaigns and I want to get their first marketing source and their last marketing source in the same table.
I was thinking I could create two tables and use a select statement from both.
The first table would attempt to create a table with the most recent marketing source from every person (using email as their unique ID).
create table temp.multitouch1 as (
select distinct on (email) email, date, market_source as last_source
from sf.campaignmember
where date >= '1/1/2016' ORDER BY DATE DESC);
Then I would create a table with deduped emails but this time for the first source.
create table temp.multitouch2 as (
select distinct on (email) email, date, market_source as first_source
from sf.campaignmember
where date >= '1/1/2016' ORDER BY DATE ASC);
Finally I wanted to simply select the email and join the first and last market sources to it each in their own column.
select a.email, a.last_source, b.first_source, a.date
from temp.multitouch1 a
left join temp.multitouch b on b.email = a.email
Since distinct on doesn't work on redshift's postgresql version I was hoping someone had an idea to solve this issue in another way.
EDIT 2/22: For more context I'm dealing with people and campaigns they've responded to. Each record is a "campaign response" and every person can have more than one campaign response with multiple sources. I'm trying make a select statement which would dedupe by person and then have columns for the first campaign/marketing source they've responded to and the last campaign/marketing source they've responded to respectively.
EDIT 2/24: Ideal output is a table with 4 columns: email, last_source, first_source, date.
The first and last source columns would be the same for people with only 1 campaign member record and different for everyone who has more than 1 campaign member record.
I believe you could use row_number() inside case expressions like this:
SELECT
email
, MIN(first_source) AS first_source
, MIN(date) first_date
, MAX(last_source) AS last_source
, MAX(date) AS last_date
FROM (
SELECT
email
, date
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date ASC) = 1 THEN market_source
ELSE NULL
END AS first_source
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date DESC) = 1 THEN market_source
ELSE NULL
END AS last_source
FROM sf.campaignmember
WHERE date >= '2016-01-01'
) s
WHERE first_source IS NOT NULL
OR last_source IS NOT NULL
GROUP BY
email
tested here: SQL Fiddle
PostgreSQL 9.3 Schema Setup:
CREATE TABLE campaignmember
(email varchar(3), date timestamp, market_source varchar(1))
;
INSERT INTO campaignmember
(email, date, market_source)
VALUES
('a#a', '2016-01-02 00:00:00', 'x'),
('a#a', '2016-01-03 00:00:00', 'y'),
('a#a', '2016-01-04 00:00:00', 'z'),
('b#b', '2016-01-02 00:00:00', 'x')
;
Query 1:
SELECT
email
, MIN(first_source) AS first_source
, MIN(date) first_date
, MAX(last_source) AS last_source
, MAX(date) AS last_date
FROM (
SELECT
email
, date
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date ASC) = 1 THEN market_source
ELSE NULL
END AS first_source
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date DESC) = 1 THEN market_source
ELSE NULL
END AS last_source
FROM campaignmember
WHERE date >= '2016-01-01'
) s
WHERE first_source IS NOT NULL
OR last_source IS NOT NULL
GROUP BY
email
Results:
| email | first_source | first_date | last_source | last_date |
|-------|--------------|---------------------------|-------------|---------------------------|
| a#a | x | January, 02 2016 00:00:00 | z | January, 04 2016 00:00:00 |
| b#b | x | January, 02 2016 00:00:00 | x | January, 02 2016 00:00:00 |
& a small extension to the request, count the number of contact points.
SELECT
email
, MIN(first_source) AS first_source
, MIN(date) first_date
, MAX(last_source) AS last_source
, MAX(date) AS last_date
, MAX(numof) AS Numberof_Contacts
FROM (
SELECT
email
, date
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date ASC) = 1 THEN market_source
ELSE NULL
END AS first_source
, CASE
WHEN ROW_NUMBER() OVER (PARTITION BY email ORDER BY date DESC) = 1 THEN market_source
ELSE NULL
END AS last_source
, COUNT(*) OVER (PARTITION BY email) as numof
FROM campaignmember
WHERE date >= '2016-01-01'
) s
WHERE first_source IS NOT NULL
OR last_source IS NOT NULL
GROUP BY
email
You can use the good old left join groupwise maximum.
SELECT DISTINCT c1.email, c1.date, c1.market_source
FROM sf.campaignmember c1
LEFT JOIN sf.campaignmember c2
ON c1.email = c2.email AND c1.date > c2.date AND c1.id > c2.id
LEFT JOIN sf.campaignmember c3
ON c1.email = c3.email AND c1.date < c3.date AND c1.id > c3.id
WHERE c1.date >= '1/1/2016' AND c2.date >= '1/1/2016'
AND (c2.email IS NULL OR c3.email IS NULL)
This assumes you have an unique id column, if (date, email) is unique id is not needed.

Subtract the previous row of data where the id is the same as the row above

I have been trying all afternoon to try and achieve this with no success.
I have a db in with info on customers and the date that they purchase products from the store. It is grouped by a batch ID which I have converted into a date format.
So in my table I now have:
CustomerID|Date
1234 |2011-10-18
1234 |2011-10-22
1235 |2011-11-16
1235 |2011-11-17
What I want to achieve is to see the number of days between the most recent purchase and the last purchase and so on.
For example:
CustomerID|Date |Outcome
1234 |2011-10-18 |
1234 |2011-10-22 | 4
1235 |2011-11-16 |
1235 |2011-11-17 | 1
I have tried joining the table to itself but the problem I have is that I end up joining in the same format. I then tried with my join statement to return where it did <> match date.
Hope this makes sense, any help appreciated. I have searched all the relevant topics on here.
Will there be multiple groups of CustomerID? Or only and always grouped together?
DECLARE #myTable TABLE
(
CustomerID INT,
Date DATETIME
)
INSERT INTO #myTable
SELECT 1234, '2011-10-14' UNION ALL
SELECT 1234, '2011-10-18' UNION ALL
SELECT 1234, '2011-10-22' UNION ALL
SELECT 1234, '2011-10-26' UNION ALL
SELECT 1235, '2011-11-16' UNION ALL
SELECT 1235, '2011-11-17' UNION ALL
SELECT 1235, '2011-11-18' UNION ALL
SELECT 1235, '2011-11-19'
SELECT CustomerID,
MIN(date),
MAX(date),
DATEDIFF(day,MIN(date),MAX(date)) Outcome
FROM #myTable
GROUP BY CustomerID
SELECT a.CustomerID,
a.[Date],
ISNULL(DATEDIFF(DAY, b.[Date], a.[Date]),0) Outcome
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY [CustomerID] ORDER BY date) Row,
CustomerID,
Date
FROM #myTable
) A
LEFT JOIN
(
SELECT ROW_NUMBER() OVER(PARTITION BY [CustomerID] ORDER BY date) Row,
CustomerID,
Date
FROM #myTable
) B ON a.CustomerID = b.CustomerID AND A.Row = B.Row + 1