Clone rows based on the column - sql

I have data like shown below:
ID Duration Start Date End Date
------------------------------------------------------
10 2 2013-09-03 05:00:00 2013-09-03 05:02:00
I need output like below:
10 2 2013-09-03 05:00:00 2013-09-03 05:01:00 1
10 2 2013-09-03 05:01:00 2013-09-03 05:02:00 2
Based on the column Duration, if the value is 2, I need rows to be duplicated twice.
And if we see at the Output for Start Date and End Date time should be changed accordingly.
And Row count as an additional column for number rows duplicated in this case 1 / 2 shown above will help a lot.
And if duration is 0 and 1 then do nothing , only when duration > 1 then duplicate rows.
And at last Additional column for number row Sequence 1 , 2 ,3 for showing how many rows was duplicated.

try the sql below, I added some comments where I thought it was seemed necessery.
declare #table table(Id integer not null, Duration int not null, StartDate datetime, EndDate datetime)
insert into #table values (10,2, '2013-09-03 05:00:00', '2013-09-03 05:02:00')
insert into #table values (11,3, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
;WITH
numbers AS (
--this is the number series generator
--(limited to 1000, you can change that to whatever you need
-- max possible duration in your case).
SELECT 1 AS num
UNION ALL
SELECT num+1 FROM numbers WHERE num+1<=100
)
SELECT t.Id
, t.Duration
, StartDate = DATEADD(MINUTE, IsNull(Num,1) - 1, t.StartDate)
, EndDate = DATEADD(MINUTE, IsNull(Num,1), t.StartDate)
, N.num
FROM #table t
LEFT JOIN numbers N
ON t.Duration >= N.Num
-- join it with numbers generator for Duration times
ORDER BY t.Id
, N.Num

This works better when Duration = 0:
declare #table table(Id integer not null, Duration int not null, StartDate datetime, EndDate datetime)
insert into #table values (10,2, '2013-09-03 05:00:00', '2013-09-03 05:02:00')
insert into #table values (11,3, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
insert into #table values (12,0, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
insert into #table values (13,1, '2013-09-04 05:00:00', '2013-09-04 05:03:00')
;WITH
numbers AS (
--this is the number series generator
--(limited to 1000, you can change that to whatever you need
-- max possible duration in your case).
SELECT 1 AS num
UNION ALL
SELECT num+1 FROM numbers WHERE num+1<=100
)
SELECT
Id
, Duration
, StartDate
, EndDate
, num
FROM
(SELECT
t.Id
, t.Duration
, StartDate = DATEADD(MINUTE, Num - 1, t.StartDate)
, EndDate = DATEADD(MINUTE, Num, t.StartDate)
, N.num
FROM #table t
INNER JOIN numbers N
ON t.Duration >= N.Num ) A
-- join it with numbers generator for Duration times
UNION
(SELECT
t.Id
, t.Duration
, StartDate-- = DATEADD(MINUTE, Num - 1, t.StartDate)
, EndDate --= DATEADD(MINUTE, Num, t.StartDate)
, 1 AS num
FROM #table t
WHERE Duration = 0)
ORDER BY Id,Num

Related

How count of numbers id for a period of time

I am looking for a way to count the number of id in a period of time every 30 minutes.
I wrote a SQL query, but the result is incorrect
CREATE TABLE [dbo].[#tabl]
(
[Id] [varchar](100) NULL,
[TIMEStart] [datetime] NULL,
[TIMEEnd] [datetime] NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#tabl] VALUES ('1', '2020-04-01 00:05:00.000', '2020-04-01 00:10:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('2', '2020-04-01 00:11:00.000', '2020-04-01 00:29:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('3', '2020-04-01 00:12:00.000', '2020-04-01 00:55:00.000')
WITH CTE AS
(
SELECT
[Id],
DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart], [TIMEEnd]) / 30) * 30, 0) AS RangeTime
FROM
[dbo].[#tabl]
GROUP BY
[Id], DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart],[TIMEEnd]) / 30) * 30, 0)
)
SELECT numreq, RangeTime
FROM
(SELECT COUNT(DISTINCT id) AS numreq, RangeTime
FROM CTE
GROUP BY RangeTime) temp
Correct result - table:
numreq RangeTime
-------------------------------------
3 1900-01-01 00:00:00.000
1 1900-01-01 00:30:00.000
Period of time:
1900-01-01 00:00:00.000 - includes 3 id:1, 2, 3
1900-01-01 00:30:00.000 - includes 1 id:1
I think what you need is to create a list of RangeTime values that lie within the range of the TIMEStart and TIMEEnd values in tabl (which you can do with a recursive CTE), then you can JOIN that list back to tabl on an overlapping time range and count the number of rows that overlap each RangeTime:
WITH CTE AS (
SELECT DATEADD(MINUTE,(DATEDIFF(MINUTE, 0, MIN([TIMEStart]))/30)*30,0) as RangeTime,
MAX([TIMEEnd]) AS MaxTime
FROM [dbo].[tabl]
UNION ALL
SELECT DATEADD(MINUTE, 30, RangeTime), MaxTime
FROM CTE
WHERE DATEADD(MINUTE, 30, RangeTime) < MaxTime
)
SELECT RangeTime, COUNT(tabl.Id) AS numreq
FROM CTE
LEFT JOIN tabl ON tabl.TIMEStart < DATEADD(MINUTE, 30, RangeTime)
AND RangeTime <= tabl.TIMEEnd
GROUP BY RangeTime
Output:
RangeTime numreq
2020-04-01T00:00:00Z 3
2020-04-01T00:30:00Z 1
Demo on SQLFiddle
Note: I've presumed you want the actual time for the range, not times starting at the beginning of 1900...

SQL: How to group in 30 second intervals and mark the max time in that grouping?

I have the following table:
CREATE TABLE #times
(
num int,
atime datetime
)
INSERT #times VALUES (1, '8/27/2015 1:10:00');
INSERT #times VALUES (1, '8/27/2015 1:10:15');
INSERT #times VALUES (1, '8/27/2015 1:10:28' );
INSERT #times VALUES (2, '7/3/2018 2:20:50' );
INSERT #times VALUES (2, '7/3/2018 2:21:05' );
INSERT #times VALUES (2, '7/3/2018 2:21:10' );
INSERT #times VALUES (2, '7/3/2018 2:30:55' );
INSERT #times VALUES (3, '1/1/2018 10:20:25');
INSERT #times VALUES (4, '1/1/2018 10:20:05');
INSERT #times VALUES (5, '9/15/2015 2:20:55');
I would like to group by num and atime within a 30 second interval, then mark the max time with a 0 and the other times in the grouping with a 1.
So the result dataset would be this:
1 '8/27/2015 1:10:00' 1
1 '8/27/2015 1:10:15' 1
1 '8/27/2015 1:10:28' 0 <<this is the max time of the grouping within num and 30 secs
2 '7/3/2018 2:20:50' 1
2 '7/3/2018 2:21:05' 1
2 '7/3/2018 2:21:10' 0 <<this is the max time of the grouping within num and 30 secs
2 '7/3/2018 2:30:55' 0
3 '1/1/2018 10:20:25' 0
4 '1/1/2018 10:20:05' 0
5 '9/15/2015 2:20:55' 0
I find the other answers much too complicated. Basically, you want to mark rows where the next time is more than 30 seconds away.
This is easy using lead():
select t.*,
(case when lead(atime) over (partition by num order by atime) < dateadd(second, 30, atime)
then 1
else 0
end) as flag
from #times t;
Here is a SQL Fiddle.
Try this to select the required rows
SELECT DISTINCT num,
MAX(atime) OVER(PARTITION BY num) AS maxAtime
FROM #times
WHERE DATEPART(SECOND, atime) <= 30
Or this to mark the rows
SELECT num, atime, IIF(atime = sub.maxAtime, 1, 0) AS flagged
FROM (
SELECT num,
atime,
MAX(atime) OVER(PARTITION BY num) AS maxAtime
FROM #times
WHERE DATEPART(SECOND, atime) <= 30
) AS sub;
Not sure I've understood 100%, but have a look at this, see if it helps.
WITH cte
AS (SELECT *
, CAST(atime AS DATE) D
, DATEPART(HOUR, atime) H
, DATEPART(MINUTE, atime) M
, CASE WHEN DATEPART(SECOND, atime) < 30 THEN 0
ELSE 30
END AS S
FROM #times
),
cte2
AS (SELECT *
, ROW_NUMBER() OVER (PARTITION BY num, D, H, M, S ORDER BY atime DESC) RN
FROM cte
)
SELECT cte2.num
, cte2.atime
, CASE cte2.RN WHEN 1 THEN 0 ELSE 1 END
FROM cte2
ORDER BY atime
Here you go:
;with stats1(num,mintime) as(
select num,min(atime) mintime
from #times t1
group by num
),
stats2(num,maxtime) as(
select s1.num,max(t.atime) maxtime
from stats1 s1
inner join #times t on s1.num=t.num
where t.atime<=dateadd(second,30,s1.mintime)
group by s1.num
)
select t.num, t.atime, case when t.atime>=s2.maxtime then 0 else 1 end
from stats2 s2
inner join #times t on s2.num=t.num

SQL - Total Time on day

My table looks a lot like the table shown in the following StackOverflow URL:
Calculating total time excluding overlapped time & breaks in SQLServer
My table also includes an OwnerID. Each person has an unique OwnerID, and I could easily join in the person name belonging to that ID.
The result requested should be just like in the linked URL, but per Owner. I tried modifying the selected answer for his URL but that gives me the following error:
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
This is the query I try to run...
;WITH addNR AS ( -- Add row numbers
SELECT StartDate, EndDate, ROW_NUMBER() OVER (ORDER BY StartDate, EndDate) AS RowID
FROM dbo.FollowUp AS T
WHERE StartDate > '2017-10-02 08:30:00.000'
), createNewTable AS ( -- Recreate table according overlap time
SELECT StartDate, EndDate, RowID
FROM addNR
WHERE RowID = 1
UNION ALL
SELECT
CASE
WHEN a.StartDate <= AN.StartDate AND AN.StartDate <= a.EndDate THEN a.StartDate
ELSE AN.StartDate END AS StartTime,
CASE WHEN a.StartDate <= AN.EndDate AND AN.EndDate <= a.EndDate THEN a.EndDate
ELSE AN.EndDate END AS EndTime,
AN.RowID
FROM addNR AS AN
INNER JOIN createNewTable AS a
ON a.RowID + 1 = AN.RowID
), getMinutes AS ( -- Get difference in minutes
SELECT DATEDIFF(MINUTE,StartDate,MAX(EndDate)) AS diffMinutes
FROM createNewTable
GROUP BY StartDate
)
SELECT SUM(diffMinutes) AS Result
FROM getMinutes
Where I replaced StartTime=StartDate and EndTime=EndDate since my columns are named so..
Sample Data
Coincidence #vitalygolub .
Try my script with various sample data.Also Time Calendar table should be permanent table so it is only time creation.
It is not Recursive so it should perform better.If output is thrown then distinct can be avoided.
create table #tbl (ownerid int,StartTime datetime,enddate datetime);
insert into #tbl values
(1,'2014-10-01 10:30:00.000','2014-10-01 12:00:00.000') -- 90 mins
,(1,'2014-10-01 10:40:00.000','2014-10-01 12:00:00.000') -- 0 since its overlapped with previous
,(1,'2014-10-01 10:42:00.000','2014-10-01 12:20:00.000') -- 20 mins excluding overlapped time
,(1,'2014-10-01 10:40:00.000','2014-10-01 13:00:00.000') -- 40 mins
,(1,'2014-10-01 10:44:00.000','2014-10-01 12:21:00.000') -- 0 previous ones have already covered this time range
,(1,'2014-10-13 15:50:00.000','2014-10-13 16:00:00.000') -- 10 mins
create table #Timetable(timecol time primary key )
insert into #Timetable
select dateadd(minute,(c.rn-1),'00:00')
from(
select top (24*60) row_number()over(order by number)rn from
master..spt_values order by number)c
SELECT c.ownerid
,cast(c.StartTime AS DATE)
,count(DISTINCT timecol) TimeMin
FROM #Timetable t
CROSS APPLY (
SELECT *
FROM #tbl c
WHERE timecol >= cast(c.StartTime AS TIME)
AND timecol < cast(c.enddate AS TIME)
) c
GROUP BY c.ownerid
,cast(c.StartTime AS DATE)
drop table #Timetable
drop table #tbl
Ok, here is working code, am not sure about performance. The idea: create "calendar" with 1 minute precision, fill it for every OwnerId and calculate number of records
DECLARE #table TABLE (OwnerId int,StartTime DateTime2, EndTime DateTime2)
INSERT INTO #table SELECT 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000'
INSERT INTO #table SELECT 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000'
----------------------------------------------------------------------------
INSERT INTO #table SELECT 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
declare #period int, #start datetime;;
select #period=datediff(mi, MIN(starttime),MAX(endtime)),#start =MIN(StartTime) from #table;
declare #seconds table(num int identity(0,1),garbage bit not null);
insert into #seconds(garbage) values(0);
while( select COUNT(*) from #seconds) < #period
insert into #seconds(garbage ) select garbage from #seconds;
with a(ownerId, usedminute ) as
(
select distinct t.ownerID,s.num from #seconds s join #table t on
dateadd(mi,s.num, #start) between t.StartTime and dateadd(s,-1,t.EndTime)
)
select ownerId, count(*) time_in_minutes from a group by ownerID;
You can do this without while loops using a derived tally table and regular set based joins, which as a result will perform very efficiently:
-- Define test data
declare #table table (ownerid int,starttime datetime2, endtime datetime2);
insert into #table select 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000';
insert into #table select 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000';
insert into #table select 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000';
----------------------------------------------------------------------------
insert into #table select 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
-- Query
declare #MinStartTime datetime;
declare #Minutes int;
-- Define data boundaries
select #MinStartTime = min(starttime)
,#Minutes = datediff(minute,min(starttime), max(endtime))+1
from #table;
-- Initial Numbers Table - 10 rows
with t(t) 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)
-- Create tally table of minutes by cross joining numbers table many times to generate 1m rows
,n(n) as (select top(#Minutes) dateadd(minute,row_number() over (order by (select null))-1,#MinStartTime) from t t1, t t2, t t3, t t4, t t5, t t6)
-- Define largest possible range for each OwnerID
,o(i,s,e) as (select ownerid, min(starttime), max(endtime) from #table group by ownerid)
select o.i as OwnerID
,cast(n.n as date) as DateValue
,count(n.n) as TotalMinutes
from o
join n -- Return minutes for each OwnerID range,
on n.n between o.s and o.e
where exists(select null -- where that minute should be included.
from #table as t
where n.n >= t.starttime
and n.n < t.endtime)
group by o.i
,cast(n.n as date)
order by o.i
,DateValue
Output:
+---------+------------+--------------+
| OwnerID | DateValue | TotalMinutes |
+---------+------------+--------------+
| 1 | 2014-10-01 | 150 |
| 1 | 2014-10-13 | 10 |
| 2 | 2014-10-01 | 111 |
+---------+------------+--------------+

T-SQL - query for records before and after current time in same statement

For you T-SQL gurus:
I have the following table:
ID Arrival
1 06:16:00
2 06:17:00
3 07:19:00
4 08:21:00
5 10:22:00
6 13:21:00
7 20:22:00
Say the time is currently 08:00 AM and I want to select 2 records before and after record with closest time to now. Result should return records with IDs 2,3,4,5 and 6.
Getting the records before and after record with ID=4 is straight forward but so far, I cannot figure out how to return the complete set as part of the same query. I have these two select statements:
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) < 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate())
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) >= 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) asc
which return records before and after. I tried using a union on both statements but this requires dropping the first order by clause which invalidates my query criteria.
Any ideas will help, thanks.
We can use ROW_NUMBER to partition on if the arrival is before or after and order by the absolute value of the difference between the arrival and the input time.
DECLARE #CurrentTime as time
SET #CurrentTime = '08:00 AM'
DECLARE #Schedules table (id int, arrival time)
INSERT INTO #Schedules
VALUES (1 , '06:16:00' ),
(2, '06:17:00' ),
(3, '07:19:00'),
(4, '08:21:00'),
(5, '10:22:00'),
(6, '13:21:00'),
(7, '20:22:00')
DECLARE #closestTime as time
SELECT TOP 1 #closestTime = arrival FROM #Schedules ORDER BY ABS(DATEDIFF(mi, #CurrentTime ,arrival))
;WITH cte
AS (SELECT id,
arrival,
Row_number() OVER (PARTITION BY (CASE WHEN #closestTime > arrival THEN 1
WHEN #closestTime < arrival THEN 2 END)
ORDER BY Abs(Datediff(mi, #closestTime, arrival))) rn
FROM #Schedules)
SELECT *
FROM cte
WHERE rn < 3
OR arrival = #closestTime
ORDER BY id
Results in
id arrival rn
----------- ---------------- --------------------
2 06:17:00.0000000 2
3 07:19:00.0000000 1
4 08:21:00.0000000 1
5 10:22:00.0000000 1
6 13:21:00.0000000 2
See working example at this data.se query
Can you try the following? I have tested, got the result that u want
create table Arriavel (ID int, Arr_time time)
insert into Arriavel values (1, '06:16:00')
insert into Arriavel values (2, '06:17:00')
insert into Arriavel values (3, '07:19:00')
insert into Arriavel values (4, '08:21:00')
insert into Arriavel values (5, '10:22:00')
insert into Arriavel values (6, '13:21:00')
insert into Arriavel values (7, '20:22:00')
declare #cur_time time
set #cur_time = '08:00:00'
select max(arr_time) arr_time
from Arriavel
where arr_time < #cur_time
union all
select min(arr_time) arr_time
from Arriavel
where arr_time > #cur_time

Group close numbers

I have a table with 2 columns of integers. The first column represents start index and the second column represents end index.
START END
1 8
9 13
14 20
20 25
30 42
42 49
60 67
Simple So far. What I would like to do is group all the records that follow together:
START END
1 25
30 49
60 67
A record can follow by Starting on the same index as the previous end index or by a margin of 1:
START END
1 10
10 20
And
START END
1 10
11 20
will both result in
START END
1 20
I'm using SQL Server 2008 R2.
Any help would be Great
This works for your example, let me know if it doesn't work for other data
create table #Range
(
[Start] INT,
[End] INT
)
insert into #Range ([Start], [End]) Values (1, 8)
insert into #Range ([Start], [End]) Values (9, 13)
insert into #Range ([Start], [End]) Values (14, 20)
insert into #Range ([Start], [End]) Values (20, 25)
insert into #Range ([Start], [End]) Values (30, 42)
insert into #Range ([Start], [End]) Values (42, 49)
insert into #Range ([Start], [End]) Values (60, 67)
;with RangeTable as
(select
t1.[Start],
t1.[End],
row_number() over (order by t1.[Start]) as [Index]
from
#Range t1
where t1.Start not in (select
[End]
from
#Range
Union
select
[End] + 1
from
#Range
)
)
select
t1.[Start],
case
when t2.[Start] is null then
(select max([End])
from #Range)
else
(select max([End])
from #Range
where t2.[Start] > [End])
end as [End]
from
RangeTable t1
left join
RangeTable t2
on
t1.[Index] = t2.[Index]-1
drop table #Range;
Edited to include another version which i think is a bit more reliable, and also works with overlapping ranges
CREATE TABLE #data (start_range INT, end_range INT)
INSERT INTO #data VALUES (1,8)
INSERT INTO #data VALUES (2,15)
INSERT INTO #data VALUES (9,13)
INSERT INTO #data VALUES (14,20)
INSERT INTO #data VALUES (13,26)
INSERT INTO #data VALUES (12,21)
INSERT INTO #data VALUES (9,25)
INSERT INTO #data VALUES (20,25)
INSERT INTO #data VALUES (30,42)
INSERT INTO #data VALUES (42,49)
INSERT INTO #data VALUES (60,67)
;with ranges as
(
SELECT start_range as level
,end_range as end_range
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range) as row
FROM #data
UNION ALL
SELECT
level + 1 as level
,end_range as end_range
,row
From ranges
WHERE level < end_range
)
,ranges2 AS
(
SELECT DISTINCT
level
FROM ranges
)
,ranges3 AS
(
SELECT
level
,row_number() OVER (ORDER BY level) - level as grouping_group
from ranges2
)
SELECT
MIN(level) as start_number
,MAX(level) as end_number
FROM ranges3
GROUP BY grouping_group
ORDER BY start_number ASC
I think this should work - might not be especially efficient on larger sets though...
CREATE TABLE #data (start_range INT, end_range INT)
INSERT INTO #data VALUES (1,8)
INSERT INTO #data VALUES (2,15)
INSERT INTO #data VALUES (9,13)
INSERT INTO #data VALUES (14,20)
INSERT INTO #data VALUES (21,25)
INSERT INTO #data VALUES (30,42)
INSERT INTO #data VALUES (42,49)
INSERT INTO #data VALUES (60,67)
;with overlaps as
(
select *
,end_range - start_range as range
,row_number() OVER (PARTITION BY (SELECT NULL) ORDER BY start_range ASC) as line_number
from #data
)
,overlaps2 AS
(
SELECT
O1.start_range
,O1.end_range
,O1.line_number
,O1.range
,O2.start_range as next_range
,CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END as overlap
,O1.line_number - DENSE_RANK() OVER (PARTITION BY (CASE WHEN O2.start_range - O1.end_range < 2 THEN 1 ELSE 0 END) ORDER BY O1.line_number ASC) as overlap_group
FROM overlaps O1
LEFT OUTER JOIN overlaps O2 on O2.line_number = O1.line_number + 1
)
SELECT
MIN(start_range) as range_start
,MAX(end_range) as range_end
,MAX(end_range) - MIN(start_range) as range_span
FROM overlaps2
GROUP BY overlap_group
You could use a number table to solve this problem. Basically, you first expand the ranges, then combine subsequent items in groups.
Here's one implementation:
WITH data (START, [END]) AS (
SELECT 1, 8 UNION ALL
SELECT 9, 13 UNION ALL
SELECT 14, 20 UNION ALL
SELECT 20, 25 UNION ALL
SELECT 30, 42 UNION ALL
SELECT 42, 49 UNION ALL
SELECT 60, 67
),
expanded AS (
SELECT DISTINCT
N = d.START + v.number
FROM data d
INNER JOIN master..spt_values v ON v.number BETWEEN 0 AND d.[END] - d.START
WHERE v.type = 'P'
),
marked AS (
SELECT
N,
SeqID = N - ROW_NUMBER() OVER (ORDER BY N)
FROM expanded
)
SELECT
START = MIN(N),
[END] = MAX(N)
FROM marked
GROUP BY SeqID
This solution uses master..spt_values as a number table, for expanding the initial ranges. But if (all or some of) those ranges may span more than 2048 (subsequent) values, then you should define and use your own number table.