Merging date intervals in SQL Server
It takes longer for me to set up the sample data than to write the query - it would be better if you posted questions that include CREATE TABLE
and INSERT/SELECT
statements. I don't know what your table is called, I've called mine Periods:
create table Periods (
StartDate date not null,
EndDate date not null
)
go
insert into Periods(StartDate,EndDate)
select '19820302','19820930' union all
select '19821001','19850117' union all
select '19850626','19850726' union all
select '19850730','19911231' union all
select '19920101','19951231' union all
select '19960101','20040531' union all
select '20040605','20060131' union all
select '20060201','20110520'
go
; with MergedPeriods as (
Select p1.StartDate, p1.EndDate
from
Periods p1
left join
Periods p2
on
p1.StartDate = DATEADD(day,1,p2.EndDate)
where
p2.StartDate is null
union all
select p1.StartDate,p2.EndDate
from
MergedPeriods p1
inner join
Periods p2
on
p1.EndDate = DATEADD(day,-1,p2.StartDate)
)
select StartDate,MAX(EndDate) as EndDate
from MergedPeriods group by StartDate
Result:
StartDate EndDate
1982-03-02 1985-01-17
1985-06-26 1985-07-26
1985-07-30 2004-05-31
2004-06-05 2011-05-20
Merge overlapping time intervals, how?
You may also try this query (once more solutions beside those given by PM 77-1
in the comment above) :
WITH RECURSIVE cte( id, date_start, date_end ) AS
(
SELECT id, date_start, date_end
FROM evento
UNION
SELECT e.id,
least( c.date_start, e.date_start ),
greatest( c.date_end, e.date_end )
FROM cte c
JOIN evento e
ON e.date_start between c.date_start and c.date_end
OR
e.date_end between c.date_start and c.date_end
)
SELECT distinct date_start, date_end
FROM (
SELECT id,
min( date_start) date_start,
max( date_end ) date_end
FROM cte
GROUP BY id
) xx
ORDER BY date_start;
Demo ---> http://www.sqlfiddle.com/#!12/bdf7e/9
however for huge table the performance of this query could be horribly slow, and some procedural approach might perform better.
How to merge time intervals in SQL Server
You can use a recursive CTE to build a list of dates and then count the distinct dates.
declare @T table
(
startDate date,
endDate date
);
insert into @T values
('2011-01-01', '2011-01-05'),
('2011-01-04', '2011-01-08'),
('2011-01-11', '2011-01-15');
with C as
(
select startDate,
endDate
from @T
union all
select dateadd(day, 1, startDate),
endDate
from C
where dateadd(day, 1, startDate) < endDate
)
select count(distinct startDate) as DayCount
from C
option (MAXRECURSION 0)
Result:
DayCount
-----------
11
Or you can use a numbers table. Here I use master..spt_values:
declare @MinStartDate date
select @MinStartDate = min(startDate)
from @T
select count(distinct N.number)
from @T as T
inner join master..spt_values as N
on dateadd(day, N.Number, @MinStartDate) between T.startDate and dateadd(day, -1, T.endDate)
where N.type = 'P'
Merging groups of interval data - SQL Server
I assume that Start
is inclusive, End
is exclusive and given intervals do not overlap.
CTE_Number
is a table of numbers. Here it is generated on the fly. I have it as a permanent table in my database.
CTE_T1
and CTE_T2
expand each interval into the corresponding number of rows using a table of numbers. For example, interval [2,5)
generates rows with Values
2
3
4
This is done twice: for Type1
and Type2
.
Results for Type1
and Type2
are FULL JOINed
together on Value
.
Finally, a gaps-and-islands pass groups/collapses intervals back.
Run the query step-by-step, CTE-by-CTE and examine intermediate results to understand how it works.
Sample data
I added few rows to illustrate a case when there is a gap between values.
DECLARE @Table1 TABLE
([Start] int, [End] int, [Type1] varchar(4), [Type2] varchar(4))
;
INSERT INTO @Table1 ([Start], [End], [Type1], [Type2]) VALUES
( 0, 2, 'L', NULL),
( 2, 3, NULL, 'S'),
( 2, 5, 'L', NULL),
( 3, 5, NULL, 'S'),
( 5, 7, 'L', NULL),
( 5, 8, NULL, 'S'),
( 7, 10, 'L', NULL),
(11, 12, NULL, 'S'),
(15, 20, 'L', NULL),
(15, 20, NULL, 'S');
Query
WITH
e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
) -- 10
,e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10
,e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2) -- 10*100
,CTE_Numbers
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY n) AS Number
FROM e3
)
,CTE_T1
AS
(
SELECT
T1.[Start] + CA.Number - 1 AS Value
,T1.Type1
FROM
@Table1 AS T1
CROSS APPLY
(
SELECT TOP(T1.[End] - T1.[Start]) CTE_Numbers.Number
FROM CTE_Numbers
ORDER BY CTE_Numbers.Number
) AS CA
WHERE
T1.Type1 IS NOT NULL
)
,CTE_T2
AS
(
SELECT
T2.[Start] + CA.Number - 1 AS Value
,T2.Type2
FROM
@Table1 AS T2
CROSS APPLY
(
SELECT TOP(T2.[End] - T2.[Start]) CTE_Numbers.Number
FROM CTE_Numbers
ORDER BY CTE_Numbers.Number
) AS CA
WHERE
T2.Type2 IS NOT NULL
)
,CTE_Values
AS
(
SELECT
ISNULL(CTE_T1.Value, CTE_T2.Value) AS Value
,CTE_T1.Type1
,CTE_T2.Type2
,ROW_NUMBER() OVER (ORDER BY ISNULL(CTE_T1.Value, CTE_T2.Value)) AS rn
FROM
CTE_T1
FULL JOIN CTE_T2 ON CTE_T2.Value = CTE_T1.Value
)
,CTE_Groups
AS
(
SELECT
Value
,Type1
,Type2
,rn
,ROW_NUMBER() OVER
(PARTITION BY rn - Value, Type1, Type2 ORDER BY Value) AS rn2
FROM CTE_Values
)
SELECT
MIN(Value) AS [Start]
,MAX(Value) + 1 AS [End]
,Type1
,Type2
FROM CTE_Groups
GROUP BY rn-rn2, Type1, Type2
ORDER BY [Start];
Result
+-------+-----+-------+-------+
| Start | End | Type1 | Type2 |
+-------+-----+-------+-------+
| 0 | 2 | L | NULL |
| 2 | 8 | L | S |
| 8 | 10 | L | NULL |
| 11 | 12 | NULL | S |
| 15 | 20 | L | S |
+-------+-----+-------+-------+
SQL time data merging 30 minute intervals that span a time range per person per activity - time card
I think I have this sussed, admittedly it's not pretty and no doubt a more elegant solution will be along, however the following gives you the desired results using window functions to look at the previous row data.
(note my date format is DMY)
Test data
create table test (EmpNo int, Activity varchar(10), AStart datetime, AEnd datetime)
insert into test
select 1 EmpNo, 'Login' Activity,'17/3/2021 09:18:37' Astart,'17/3/2021 09:26:54 ' AEnd union all
select 1, 'Break','17/3/2021 10:43:25','17/3/2021 10:58:07 ' union all
select 1, 'Lunch','17/3/2021 13:23:02','17/3/2021 13:30:00 ' union all
select 1, 'Lunch','17/3/2021 13:30:00','17/3/2021 14:00:00 ' union all
select 1, 'Lunch','17/3/2021 14:00:00','17/3/2021 14:08:00 ' union all
select 1, 'Break','17/3/2021 17:16:23','17/3/2021 17:30:00 ' union all
select 1, 'Break','17/3/2021 17:30:00','17/3/2021 17:31:00 ' union all
select 1, 'Logout','17/3/2021 19:14:05','17/3/2021 19:16:02 ' union all
select 2, 'Login','17/3/2021 09:03:05','17/3/2021 09:05:02 ' union all
select 2, 'Break','17/3/2021 10:29:02','17/3/2021 10:30:00 ' union all
select 2, 'Break','17/3/2021 10:30:00','17/3/2021 10:44:19 ' union all
select 2, 'Lunch','17/3/2021 13:31:05','17/3/2021 14:00:00 ' union all
select 2, 'Lunch','17/3/2021 14:00:00','17/3/2021 14:15:00 ' union all
select 2, 'Break','17/3/2021 17:30:00','17/3/2021 17:45:00 ' union all
select 2, 'Logout','17/3/2021 19:15:00','17/3/2021 19:16:00 '
Results
with spans as (select *, Iif(Lead(activity,1) over(partition by empno order by Astart)=Activity,0,1) span from test),
ranges as (
select *,
case when IsNull(Lag(activity,1) over (partition by Empno order by AStart),'') ! =Activity then AStart end ActivityStart,
case when span=1 then AEnd end ActivityEnd
from spans s
)
select EmpNo, Activity, ActivityStart, ActivityEnd
from (
select EmpNo, Activity, AStart, IsNull(ActivityStart,Lag(ACtivityStart,1) over(partition by empno order by Astart)) ActivityStart, ActivityEnd
from ranges r
where ActivityStart is not null or span=1
)x
where ActivityEnd is not null
Merge overlapping dates in SQL Server
SQL DEMO
declare @t table (Name varchar(100), Datetime_Start datetime, Datetime_End datetime);
insert into @t values
('A' , '2017-01-02 00:00' , '2017-03-28 00:10'),
('A' , '2017-05-14 23:50' , '2017-05-29 23:50'),
('B' , '2017-05-18 00:00' , '2017-05-18 04:00'),
('B' , '2017-05-18 02:00' , '2017-05-18 03:00'),
('C' , '2017-01-02 00:00' , '2017-01-17 15:50'),
('C' , '2017-01-14 03:50' , '2017-01-28 15:50');
with Datetime_Starts as
(
select distinct name, Datetime_Start
from @t as t1
where not exists
(select * from @t as t2
where t2.name = t1.name
and t2.Datetime_Start < t1.Datetime_Start
and t2.Datetime_End >= t1.Datetime_Start)
),
Datetime_Ends as
(
select distinct name, Datetime_End
from @t as t1
where not exists
(select * from @t as t2
where t2.name = t1.name
and t2.Datetime_End > t1.Datetime_End
and t2.Datetime_Start <= t1.Datetime_End)
)
select name, Datetime_Start,
(select min(Datetime_End)
from Datetime_Ends as e
where e.name = s.name
and Datetime_End >= Datetime_Start) as Datetime_End
from Datetime_Starts as s;
Related Topics
Inner Join with Count() on Three Tables
How to View All the Metadata of Columns of a Table in Oracle Database
SQL Get the Last Date Time Record
Count of Non-Null Columns in Each Row
SQL Server Management Studio - How to Change a Field Type Without Dropping Table
Impact of Defining Varchar2 Column with Greater Length
How to Grant All Privileges on Views to Arbitrary User
Passing Dynamic Order by in Stored Procedure
How to Group and Choose Lowest Value in SQL
Rollback a Committed Transaction
Postgresql With-Delete "Relation Does Not Exists"
What Is the SQL for 'Next' and 'Previous' in a Table
Autoincrement Fields on Databases Without Autoincrement Field
SQL Query to Search for Room Availability
Is the 'As' Keyword Required in Oracle to Define an Alias