Generate Start Date and End Date based on the contiguous rows - sql

I have a table :
ID
Startdate
Enddate
TEXT
0011
2022-02-07
2022-02-07
TEXT1
0011
2022-02-04
2022-02-05
TEXT2
0011
2022-02-06
2022-02-06
TEXT3
0011
2022-02-03
2022-02-03
TEXT4
0011
2022-02-03
2022-02-04
TEXT5
0011
2022-02-02
2022-02-07
TEXT6
0011
2022-02-02
2022-02-02
TEXT7
0011
2021-12-01
2021-12-03
TEXT8
Expected output:
ID
Startdate
Enddate
TEXT
0011
2022-02-02
2022-02-07
TEXT1,TEXT2,TEXT3,TEXT4,TEXT5,TEXT6,TEXT7
0011
2021-12-01
2021-12-03
TEXT8
I tried with :
WITH _DAYS AS (
SELECT DATEADD(DAY, SEQ4(), '2021-12-01') AS DAY
FROM TABLE(GENERATOR(ROWCOUNT => 68))
), _GRPS AS (
SELECT *
, DATEDIFF(DAY, '2021-12-01', D.DAY) - DENSE_RANK() OVER(PARTITION BY PASS1.MEMBER_ID ORDER BY D.DAY) AS GRP
FROM _DAYS AS D
JOIN table PASS1
ON D.DAY BETWEEN PASS1.Startdate AND PASS1.Enddate
)
SELECT ID
, TEXT
, MIN(DAY) AS START_DATE
, MAX(DAY) AS END_DATE
FROM _GRPS
GROUP BY ID,TEXT, GRP
I was able to achieve the desired output with only start-date and end-date in the table but the inclusion of TEXT column did not give me the desired output.
Please suggest !

Mostly a fix-up of Greg's Answer..
With a CTE for the pass1 data:
select * from values
('0011', '2022-02-07', '2022-02-07', 'TEXT1'),
('0011', '2022-02-04', '2022-02-05', 'TEXT2'),
('0011', '2022-02-06', '2022-02-06', 'TEXT3'),
('0011', '2022-02-03', '2022-02-03', 'TEXT4'),
('0011', '2022-02-03', '2022-02-04', 'TEXT5'),
('0011', '2022-02-02', '2022-02-07', 'TEXT6'),
('0011', '2022-02-02', '2022-02-02', 'TEXT7'),
('0011', '2021-12-01', '2021-12-03', 'TEXT8')
)
WITH _DAYS AS (
SELECT
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY NULL)-1, '2021-12-01')::date AS DAY
FROM TABLE(GENERATOR(ROWCOUNT => 68))
), _GRPS AS (
SELECT *
,DATEDIFF(DAY, '2021-12-01', D.DAY) - DENSE_RANK() OVER(PARTITION BY PASS1.ID ORDER BY D.DAY) AS GRP
FROM _DAYS AS D
JOIN PASS1
ON D.DAY BETWEEN PASS1.Startdate AND PASS1.Enddate
)
SELECT
ID
,MIN(DAY) AS START_DATE
,MAX(DAY) AS END_DATE
,listagg(distinct TEXT, ',') WITHIN GROUP (ORDER BY TEXT) as TEXT
FROM _GRPS
GROUP BY ID,GRP
gives:
ID
START_DATE
END_DATE
TEXT
0011
2022-02-02
2022-02-07
TEXT1,TEXT2,TEXT3,TEXT4,TEXT5,TEXT6,TEXT7
0011
2021-12-01
2021-12-03
TEXT8
you should use ROW_NUMBER to get continuous values, as SEQx() can and do have gaps. In Greg's answer he used ORDER BY on the sub-select/CTE, when you should use the WINTHIN GROUP of the LISTAGG to ORDER BY, as it's more targeted sort.
Also given you have a generator, in _DAYS you can use that as the first half of the gaps-and-islands and thus skip the math..
WITH _DAYS AS (
SELECT
ROW_NUMBER() OVER(ORDER BY NULL)-1 as rn,
DATEADD(DAY, rn, '2021-12-01')::date AS DAY
FROM TABLE(GENERATOR(ROWCOUNT => 68))
), _GRPS AS (
SELECT *
,d.rn - DENSE_RANK() OVER(PARTITION BY PASS1.ID ORDER BY D.DAY) as grp
FROM _DAYS AS D
JOIN PASS1
ON D.DAY BETWEEN PASS1.Startdate AND PASS1.Enddate
)
...

LISTAGG will do what you need, but there are duplicate TEXT values and they're out of order. You can order them in the table expression above the final query in the CTE and use distinct to deduplicate the values:
create table pass1(id int, startdate date, enddate date, text string);
insert into pass1(id, startdate, enddate, text) values
(0011, '2022-02-07', '2022-02-07', 'TEXT1'),
(0011, '2022-02-04', '2022-02-05', 'TEXT2'),
(0011, '2022-02-06', '2022-02-06', 'TEXT3'),
(0011, '2022-02-03', '2022-02-03', 'TEXT4'),
(0011, '2022-02-03', '2022-02-04', 'TEXT5'),
(0011, '2022-02-02', '2022-02-07', 'TEXT6'),
(0011, '2022-02-02', '2022-02-02', 'TEXT7'),
(0011, '2021-12-01', '2021-12-03', 'TEXT8');
WITH _DAYS AS (
SELECT DATEADD(DAY, SEQ4(), '2021-12-01') AS DAY
FROM TABLE(GENERATOR(ROWCOUNT => 68))
), _GRPS AS (
SELECT *
, DATEDIFF(DAY, '2021-12-01', D.DAY) - DENSE_RANK() OVER(PARTITION BY PASS1.ID ORDER BY D.DAY) AS GRP
FROM _DAYS AS D
JOIN PASS1
ON D.DAY BETWEEN PASS1.Startdate AND PASS1.Enddate
order by TEXT
)
SELECT ID
, listagg(distinct TEXT, ',') as TEXT
, MIN(DAY) AS START_DATE
, MAX(DAY) AS END_DATE
FROM _GRPS

Related

partition over period of time for a given column SQL BigQuery

My query outputs data - row by row - for each record I grab a value from last_modified_date column that is the latest date in that column BUT is not later than the date column's value. I save new value in column custom_last_modified_date. Code works fine, but it only checks for the latest date available for one date - instead of every row in a table. Data looks like this:
id date last_modified_date
A 02/28/22 2017-02-28 22:44
A 03/05/22 2017-02-28 05:14
A 03/05/22 2017-02-28 07:49
A 03/22/22 2017-02-28 06:09
A 03/22/22 2022-03-01 06:49
B 03/25/22 2022-03-20 07:49
B 03/25/22 2022-04-01 09:24
Code:
SELECT
id,
date,
MAX(
IF(
date(
string(
TIMESTAMP(
DATETIME(
parse_datetime('%Y-%m-%d %H:%M', last_modified_date)
)
)
)
) <= date,
date(
string(
TIMESTAMP(
DATETIME(
parse_datetime('%Y-%m-%d %H:%M', last_modified_date)
)
)
)
),
null
)
) OVER (PARTITION BY date, id) as custom_last_modified_date
FROM `my_table`
Output is this:
id date custom_last_modified_date
A 02/28/22 02/28/17
A 03/05/22 02/28/17
A 03/05/22 02/28/17
A 03/22/22 03/01/22
A 03/22/22 03/01/22
B 03/25/22 03/20/22
B 03/25/22 03/20/22
Desired output is:
id date custom_last_modified_date
A 02/28/22 02/28/22
A 03/05/22 03/01/22
A 03/05/22 03/01/22
A 03/22/22 03/01/22
A 03/22/22 03/01/22
B 03/25/22 03/20/22
B 03/25/22 03/20/22
I tried your query and your just have a mistake on partion by, your query will work if you removed date in your partition by clause. Your query should look like this:
NOTE: I just created a CTE to convert dates to make it readable.
with sample_data as (
select 'A' as id, '03/05/22' as date_field, '2017-02-28 22:44' as last_modified_date,
union all select 'A' as id, '03/05/22' as date_field, '2017-02-28 05:14' as last_modified_date,
union all select 'A' as id, '03/05/22' as date_field, '2017-02-28 07:49' as last_modified_date,
union all select 'A' as id, '03/22/22' as date_field, '2017-02-28 06:09' as last_modified_date,
union all select 'A' as id, '03/22/22' as date_field, '2022-03-01 06:49' as last_modified_date,
union all select 'B' as id, '03/25/22' as date_field, '2022-03-20 07:49' as last_modified_date,
union all select 'B' as id, '03/25/22' as date_field, '2022-04-01 09:24' as last_modified_date,
),
conv_data as (
select
id,
format_datetime('%m/%d/%y',parse_date('%m/%d/%y',date_field)) as date_field,
format_datetime('%m/%d/%y',parse_datetime('%Y-%m-%d %H:%M', last_modified_date)) as last_modified_date,
from sample_data
)
select
id,
date_field,
last_modified_date,
max(if(last_modified_date <= date_field, last_modified_date, null)) over (partition by id) as custom_last_modified_date,
from conv_data
Output:

create time range with 2 columns date_time

The problem I am facing is how to find distinct time periods from multiple time periods with overlap in Teradata ANSI SQL.
For example, the attached tables contain multiple overlapping time periods, how can I combine those time periods into 3 unique time periods in Teradata SQL???
I think I can do it in python with the loop function, but not sure how to do it in SQL
ID
Start Date
End Date
001
2005-01-01
2006-01-01
001
2005-01-01
2007-01-01
001
2008-01-01
2008-06-01
001
2008-04-01
2008-12-01
001
2010-01-01
2010-05-01
001
2010-04-01
2010-12-01
001
2010-11-01
2012-01-01
My expected result is:
ID
start_Date
end_date
001
2005-01-01
2007-01-01
001
2008-01-01
2008-12-01
001
2010-01-01
2012-01-01
From Oracle 12, you can use MATCH_RECOGNIZE to perform a row-by-row comparison:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY id
ORDER BY start_date
MEASURES
FIRST(start_date) AS start_date,
MAX(end_date) AS end_date
ONE ROW PER MATCH
PATTERN (overlapping_ranges* last_range)
DEFINE overlapping_ranges AS NEXT(start_date) <= MAX(end_date)
)
Which, for the sample data:
CREATE TABLE table_name (ID, Start_Date, End_Date) AS
SELECT '001', DATE '2005-01-01', DATE '2006-01-01' FROM DUAL UNION ALL
SELECT '001', DATE '2005-01-01', DATE '2007-01-01' FROM DUAL UNION ALL
SELECT '001', DATE '2008-01-01', DATE '2008-06-01' FROM DUAL UNION ALL
SELECT '001', DATE '2008-04-01', DATE '2008-12-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-01-01', DATE '2010-05-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-04-01', DATE '2010-12-01' FROM DUAL UNION ALL
SELECT '001', DATE '2010-11-01', DATE '2012-01-01' FROM DUAL;
Outputs:
ID
START_DATE
END_DATE
001
2005-01-01 00:00:00
2007-01-01 00:00:00
001
2008-01-01 00:00:00
2008-12-01 00:00:00
001
2010-01-01 00:00:00
2012-01-01 00:00:00
db<>fiddle here
Update: Alternative query
SELECT id,
start_date,
end_date
FROM (
SELECT id,
dt,
SUM(cnt) OVER (PARTITION BY id ORDER BY dt) AS grp,
cnt
FROM (
SELECT ID,
dt,
SUM(type) OVER (PARTITION BY id ORDER BY dt, ROWNUM) * type AS cnt
FROM table_name
UNPIVOT (dt FOR type IN (start_date AS 1, end_date AS -1))
)
WHERE cnt IN (1,0)
)
PIVOT (MAX(dt) FOR cnt IN (1 AS start_date, 0 AS end_date))
Or, an equivalent that does not use UNPIVOT, PIVOT or ROWNUM and works in both Oracle and PostgreSQL:
SELECT id,
MAX(CASE cnt WHEN 1 THEN dt END) AS start_date,
MAX(CASE cnt WHEN 0 THEN dt END) AS end_date
FROM (
SELECT id,
dt,
SUM(cnt) OVER (PARTITION BY id ORDER BY dt) AS grp,
cnt
FROM (
SELECT ID,
dt,
SUM(type) OVER (PARTITION BY id ORDER BY dt, rn) * type AS cnt
FROM (
SELECT r.*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY dt ASC, type DESC) AS rn
FROM (
SELECT id, 1 AS type, start_date AS dt FROM table_name
UNION ALL
SELECT id, -1 AS type, end_date AS dt FROM table_name
) r
) p
) s
WHERE cnt IN (1,0)
) t
GROUP BY id, grp
Update 2: Another Alternative
SELECT id,
MIN(start_date) AS start_date,
MAX(end_Date) AS end_date
FROM (
SELECT t.*,
SUM(CASE WHEN start_date <= prev_max THEN 0 ELSE 1 END)
OVER (PARTITION BY id ORDER BY start_date) AS grp
FROM (
SELECT t.*,
MAX(end_date) OVER (
PARTITION BY id ORDER BY start_date
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS prev_max
FROM table_name t
) t
) t
GROUP BY id, grp
db<>fiddle Oracle PostgreSQL
This is a gaps and islands problem. Try this:
with u as
(select ID, start_date, end_date,
case
when start_date <= lag(end_date) over(partition by ID order by start_date, end_date) then 0
else 1 end as grp
from table_name),
v as
(select ID, start_date, end_date,
sum(grp) over(partition by ID order by start_date, end_date) as island
from u)
select ID, min(start_date) as start_Date, max(end_date) as end_date
from v
group by ID, island;
Fiddle
Basically you can identify "islands" by comparing start_date of current row to end_date of previous row (ordered by start_date, end_date), if it precedes it then it's the same island. Then you can do a rolling sum() to get the island numbers. Finally select min(start_date) and max(end_date) from each island to get the desired output.
This may work ,with little bit of change in function , I tried it in Dbeaver :
select ID,Start_Date,End_Date
from
(
select t.*,
dense_rank () over(partition by extract (year from Start_Date) order BY End_Date desc) drnk
from testing_123 t
) temp
where temp.drnk = 1
ORDER BY Start_Date;
Try this
WITH a as (
SELECT
ID,
LEFT(Start_Date, 4) as Year,
MIN(Start_Date) as New_Start_Date
FROM
TAB1
GROUP BY
ID,
LEFT(Start_Date, 4)
), b as (
SELECT
a.ID,
Year,
New_Start_Date,
End_Date
FROM
a
LEFT JOIN
TAB1
ON LEFT(a.New_Start_Date, 4) = LEFT(TAB1.Start_Date, 4)
)
select
ID,
New_Start_Date as Start_Date,
MAX(End_Date)
from
b
GROUP BY
ID,
New_Start_Date;
Example: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=97f91b68c635aebfb752538cdd752ace

Compare dates in different columns and rows dynamically in SQL

I have a set of data like this.
Data
ID Start_dt End_dt
A 1/1/2010 12/31/2010
A 1/1/2011 12/31/2011
A 6/1/2012 12/31/2012
A 1/1/2014 12/31/2014
A 1/1/2016 10/31/2016
A 1/1/2018 12/31/2018
B 1/1/2016 2/29/2016
B 3/1/2016 10/31/2016
B 1/1/2017 7/31/2017
B 1/1/2019 12/31/9999
C 1/1/2017 12/31/2017
C 1/1/2017 12/31/2018
C 1/1/2019 12/31/9999
I need to create a query that looks at each member's row, compares the current Start_dt against the previous End_dt. If the difference is less than one year, treat those 2 records as one continuous enrollment and return the combined MIN Start_dt and MAX End_dt, and repeat that for all rows for each member. If the difference is >=1 year, treat that as separate enrollment.
Desired result
ID Start_dt End_dt
A 1/1/2010 12/31/2012
A 1/1/2014 12/31/2014
A 1/1/2016 10/31/2016
A 1/1/2018 12/31/2018
B 1/1/2016 7/31/2017
B 1/1/2019 12/31/2019
C 1/1/2017 12/31/9999
Here's a Create Table query:
if OBJECT_ID ('tempdb..#test1') is not null
drop table #test1
CREATE TABLE #test1 (
ID varchar(10),
Start_dt datetime,
End_dt datetime
);
INSERT INTO #test1 VALUES ('A', '1/1/2010', '12/31/2010')
,('A', '1/1/2011', '12/31/2011')
,('A', '6/1/2012', '12/31/2012')
,('A', '1/1/2014', '12/31/2014')
,('A', '1/1/2016', '10/31/2016')
,('A', '1/1/2018', '12/31/2018')
,('B', '1/1/2016', '2/29/2016')
,('B', '3/1/2016', '10/31/2016')
,('B', '1/1/2017', '7/31/2017')
,('B', '1/1/2019', '12/31/9999')
,('C', '1/1/2017', '12/31/2017')
,('C', '1/1/2017', '12/31/2018')
,('C', '1/1/2019', '12/31/2999')
I've been trying to solve this for days but have tried self-joins, loops but have not found a good solution. Can someone help?
Thank you!
You can use lag() or a cumulative max() to get the previous end date. Then compare it to the current start date.
When the difference is more than a year, then a new group starts. Do a cumulative sum of these new group starts to get a grouping id.
And the rest is aggregation:
select id, min(start_dt), max(end_dt)
from (select t1.*,
sum(case when prev_end_dt > dateadd(year, -1, start_dt) then 0 else 1 end) over
(partition by id order by start_dt) as grp
from (select t1.*,
max(end_dt) over (partition by id
order by start_dt
rows between unbounded preceding and 1 preceding
) as prev_end_dt
from test1 t1
) t1
) t1
group by id, grp
order by id, min(start_dt);
You could try this query
SELECT ID, StartDate, End_dt AS EndDate
FROM (
SELECT *
, LAG(End_dt) OVER(PARTITION BY ID ORDER BY ID, Start_dt, End_dt) AS PrevEnd
, DATEDIFF(DAY, LAG(End_dt) OVER(PARTITION BY ID ORDER BY ID, Start_dt, End_dt), Start_dt) AS DaysBreak
, (
CASE
WHEN DATEDIFF(DAY, LAG(End_dt) OVER(PARTITION BY ID ORDER BY ID, Start_dt, End_dt), Start_dt) > 365 THEN Start_dt
WHEN LAG(End_dt) OVER(PARTITION BY ID ORDER BY ID, Start_dt, End_dt) IS NULL THEN Start_dt
ELSE NULL
END
) AS StartDate
FROM #test1
) a
WHERE StartDate IS NOT NULL

Recursively loop through a SQL table and find intervals based on Start and End Dates

I have a SQL table that contains employeeid, StartDateTime and EndDatetime as follows:
CREATE TABLE Sample
(
SNO INT,
EmployeeID NVARCHAR(10),
StartDateTime DATE,
EndDateTime DATE
)
INSERT INTO Sample
VALUES
( 1, 'xyz', '2018-01-01', '2018-01-02' ),
( 2, 'xyz', '2018-01-03', '2018-01-05' ),
( 3, 'xyz', '2018-01-06', '2018-02-01' ),
( 4, 'xyz', '2018-02-15', '2018-03-15' ),
( 5, 'xyz', '2018-03-16', '2018-03-19' ),
( 6, 'abc', '2018-01-16', '2018-02-25' ),
( 7, 'abc', '2018-03-08', '2018-03-19' ),
( 8, 'abc', '2018-02-26', '2018-03-01' )
I want the result to be displayed as
EmployeeID | StartDateTime | EndDateTime
------------+-----------------+---------------
xyz | 2018-01-01 | 2018-02-01
xyz | 2018-02-15 | 2018-03-19
abc | 2018-01-16 | 2018-03-01
abc | 2018-03-08 | 2018-03-19
Basically, I want to recursively look at records of each employee and datemine the continuity of Start and EndDates and make a set of continuous date records.
I wrote my query as follows:
SELECT *
FROM dbo.TestTable T1
LEFT JOIN dbo.TestTable t2 ON t2.EmpId = T1.EmpId
WHERE t1.EndDate = DATEADD(DAY, -1, T2.startdate)
to see if I could decipher something from the output looking for a pattern. Later realized that with the above approach, I need to join the same table multiple times to get the output I desire.
Also, there is a case that there can be multiple employee records, so I need direction on efficient way of getting this desired output.
Any help is greatly appreciated.
This will do it for you. Use a recursive CTE to get all the adjacent rows, then get the highest end date for each start date, then the first start date for each end date.
;with cte as (
select EmployeeID, StartDateTime, EndDateTime
from sample s
union all
select CTE.EmployeeID, CTE.StartDateTime, s.EndDateTime
from sample s
join cte on cte.EmployeeID=s.EmployeeID and s.StartDateTime=dateadd(d,1,CTE.EndDateTime)
)
select EmployeeID, Min(StartDateTime) as StartDateTime, EndDateTime from (
select EmployeeID, StartDateTime, Max(EndDateTime) as EndDateTime from cte
group by EmployeeID, StartDateTime
) q group by EmployeeID, EndDateTime
You can use this.
WITH T AS (
SELECT S1.SNO,
S1.EmployeeID,
S1.StartDateTime,
ISNULL(S2.EndDateTime, S1.EndDateTime) EndDateTime,
ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId ORDER BY S1.StartDateTime)
- ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId, CASE WHEN S2.StartDateTime IS NULL THEN 0 ELSE 1 END ORDER BY S1.StartDateTime ) RN,
ROW_NUMBER() OVER(PARTITION BY S1.EmployeeId, ISNULL(S2.EndDateTime, S1.EndDateTime) ORDER BY S1.EmployeeId, S1.StartDateTime) RN_END
FROM Sample S1
LEFT JOIN Sample S2 ON DATEADD(DAY,1,S1.EndDateTime) = S2.StartDateTime
)
SELECT EmployeeID, MIN(StartDateTime) StartDateTime,MAX(EndDateTime) EndDateTime FROM T
WHERE RN_END = 1
GROUP BY EmployeeID, RN
ORDER BY EmployeeID DESC, StartDateTime
Result:
EmployeeID StartDateTime EndDateTime
---------- ------------- -----------
xyz 2018-01-01 2018-02-01
xyz 2018-02-15 2018-03-19
abc 2018-01-16 2018-03-01
abc 2018-03-08 2018-03-19

find time slots with sql

I have a scenario (SQL 2008) where I need to find the occupied timeframes /non-gaps from the below table. For e.g . I have created this dummy table.
CREATE TABLE Job
(
JobID INT NOT NULL,
WorkerID INT NOT NULL,
JobStart DATETIME NOT NULL,
JobEnd DATETIME NOT NULL
);
INSERT INTO Job (JobID, WorkerID, JobStart, JobEnd)
VALUES (1, 25, '2012-11-17 16:00', '2012-11-17 17:00'),
(2, 25, '2012-11-17 16:00', '2012-11-17 16:50'),
(3, 25, '2012-11-19 18:00', '2012-11-19 18:30'),
(4, 25, '2012-11-19 17:30', '2012-11-19 18:10'),
(5, 26, '2012-11-18 16:00', '2012-11-18 17:10'),
(6, 26, '2012-11-18 16:00', '2012-11-19 16:50');
so for this , the qry shd return data like this:
WorkerID | StartDate | EndDate
25 2012-11-17 16:00 2012-11-17 17:00
25 2012-11-17 17:30 2012-11-17 18:30
26 2012-11-18 16:00 2012-11-18 17:10
I am able to get the result but I am using while loop and its a pretty iterative method. Any chance , I can avoid using while to get the result
This is a Packing Date and Time Interval problem. Itzik Ben-Gan has published an article that provides many solutions to this problem. Using one of Itzik's solution, here is a query to solve your problem:
SQL Fiddle
WITH C1 AS(
SELECT
JobID, WorkerId, JobStart AS ts, +1 AS type, NULL AS e,
ROW_NUMBER() OVER(PARTITION BY WorkerId ORDER BY JobStart, JobId) AS s
FROM Job
UNION ALL
SELECT
JobID, WorkerId, JobEnd AS ts, -1 AS type,
ROW_NUMBER() OVER(PARTITION BY WorkerId ORDER BY JobEnd, JobId) AS e,
NULL AS s
FROM Job
),
C2 AS(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY WorkerId ORDER BY ts, type DESC, JobId) AS se
FROM C1
),
C3 AS(
SELECT ts, WorkerId,
FLOOR((ROW_NUMBER() OVER(PARTITION BY WorkerId ORDER BY ts) - 1) / 2 + 1) AS grpnum
FROM C2
WHERE COALESCE(s - (se - s) - 1, (se - e) - e) = 0
)
SELECT
WorkerId,
MIN(ts) AS StartDate,
MAX(ts) AS EndDate
FROM C3
GROUP BY WorkerID, grpnum
ORDER BY WorkerID
Result
WorkerId StartDate EndDate
----------- ----------------------- -----------------------
25 2012-11-17 16:00:00.000 2012-11-17 17:00:00.000
25 2012-11-19 17:30:00.000 2012-11-19 18:30:00.000
26 2012-11-18 16:00:00.000 2012-11-19 16:50:00.000