Conditional RowNumber - sql

I am trying to put conditional numbering depending on a result from RowNum column.
When the RowNum is 1 I would like to have new column with brand new increment by 1.
In the picture in column RoomNum 5 should be replaced by 2, 9 by 3m 13 by 4, etc. What am I doing wrong in this query?
SELECT CASE
WHEN rownum < 2
THEN
Row_number() OVER (
PARTITION BY Scheme ORDER BY Scheme ASC
)
ELSE NULL
END AS RoomNum,
CASE
WHEN rownum > 1
THEN NULL
ELSE scheme
END AS Scheme
,RowNum

You need to partition by whether or not RoomNm is NULL. The resulting value would also have a CASE:
select (case when roomnum is not null
then row_number() over (partition by scheme, (case when roomnum is not null then 1 else 0 end)
order by roomnum
)
end) as RoomNum

--I think you can work around this way
--I have little different scenario but hope logic help
declare #ts table
(WK_DAYS int
,DAY_NAME VARCHAR(12)
,WORKTYPE varchar(50)
,WK_HOURS int
,workday int)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (1,'MON','SICK',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (2,'TUE','LABOR',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (3,'WED','LABOR',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (4,'THU','VACATION',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (5,'FRI','LABOR',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (6,'SAT','LABOR',8)
insert into #ts (WK_DAYS,DAY_NAME,WORKTYPE,WK_HOURS) values (7,'SUN','LABOR',8)
SELECT * FROM #TS
SELECT
X.*
,Y.WORKING_DAY
FROM
(SELECT * FROM #ts)X
LEFT JOIN
(SELECT *,ROW_NUMBER()OVER (ORDER BY WK_DAYS) AS WORKING_DAY FROM #ts WHERE WORKTYPE NOT IN ('SICK','VACATION','SATURDAY','SUNDAY'))Y
ON X.WK_DAYS = Y.WK_DAYS

Related

how to pick top 2 rows in a table based on the indicator

I have a sample data like this
Declare #table Table
(
ID INT,
Value VARCHAR(10),
Is_failure int
)
insert into #table(ID, Value, Is_failure) values (1, 'Bits', 0)
insert into #table(ID, Value, Is_failure) values (2, 'Ip', 0)
insert into #table(ID, Value, Is_failure) values (3, 'DNA', 0)
insert into #table(ID, Value, Is_failure) values (6, 'DCP', 1)
insert into #table(ID, Value, Is_failure) values (8, 'Bits', 0)
insert into #table(ID, Value, Is_failure) values (11, 'calc', 0)
insert into #table(ID, Value, Is_failure) values (14, 'DISC', 0)
insert into #table(ID, Value, Is_failure) values (19, 'DHCP', 1)
Looks like this:
ID Value Is_failure
1 Bits 0
2 Ip 0
3 DNA 0
6 DCP 1
8 Bits 0
11 calc 0
14 DISC 0
19 DHCP 1
Data continuous like this ... I need to fetch top 2 records along with Is_failure whenever Is_failure = 1 comes if it is 0 no need to pick up .
Sample output:
ID Value Is_failure
2 Ip 0
3 DNA 0
6 DCP 1
11 calc 0
14 DISC 0
19 DHCP 1
Suggest on this I have tried with having count(*) and other things but not fruitful.
You can use this query
Declare #tmptable Table
(
ID INT,
Value VARCHAR(10),
Is_failure int,
rowNum int
)
Declare #continuousRows int =2
insert into #tmptable
select *,ROW_NUMBER() over (order by id) from #table
;with cte1 as
(select *
from #tmptable t
where (select sum(Is_failure) from #tmptable t1 where t1.rowNum between t.rowNum-#continuousRows and t.rowNum
having count(*)=#continuousRows+1)=1
and t.Is_failure=1
)
,cte2 as
(
select t.* from #tmptable t
join cte1 c on t.rowNum between c.rowNum-#continuousRows and c.rowNum
)
select c.ID,value,Is_failure from cte2 c
You can use window functions for this:
select id, value, is_failure
from (select t.*,
lead(Is_failure) over (order by id) as next_if,
lead(Is_failure, 2) over (order by id) as next_if2
from #table t
) t
where 1 in (Is_failure, next_if, next_if2)
order by id;
You can simplify this with a windowing clause:
select id, value, is_failure
from (select t.*,
max(is_failure) over (order by id rows between current row and 2 following) as has_failure
from #table t
) t
where has_failure > 0
order by id;

How to get the each record with some condition

I have following data:
DECLARE #temp TABLE (
ID int
,sn varchar(200)
,comment varchar(2000)
,rownumber int
)
insert into #temp values(1,'sn1',NULL,1)
insert into #temp values(2,'sn1','aaa',2)
insert into #temp values(3,'sn1','bbb',3)
insert into #temp values(4,'sn1',NULL,4)
insert into #temp values(5,'sn2',NULL,1)
insert into #temp values(6,'sn2',NULL,2)
insert into #temp values(7,'sn2',NULL,3)
select * from #temp
And I want to output like this:
2 sn1 aaa 2
5 sn2 NULL 1
same sn, if comment have value, get this lower rownumber's record. For sn1, have two records with comment value, so here, get the the record with rownumber=2
If comment doesn't have value, get the lower rownumber's record. For sn2, get the record with rownumber=1
May I know how to write this SQL?
This is a prioritization query. I think row_number() is the simplest method:
select t.*
from (select t.*,
row_number() over (partition by sn
order by (case when comment is not null then 1 else 2 end),
rownumber
) as seqnum
from #temp t
) t
where seqnum = 1;
Here is a db<>fiddle.

SQL: Pinned rows and row number calculation

We have a requirement to assign row number to all rows using following rule
Row if pinned should have same row number
Otherwise sort it by GMD
Example:
ID GMD IsPinned
1 2.5 0
2 0 1
3 2 0
4 4 1
5 3 0
Should Output
ID GMD IsPinned RowNo
5 3 0 1
2 0 1 2
1 2.5 0 3
4 4 1 4
3 2 0 5
Please Note row number for Id's 2 and 4 stayed intact as they are pinned with values of 2 and 4 respectively even though the GMD are not in any order
Rest of rows Id's 1, 3 and 5 row numbers are sorted using GMD desc
I tried using RowNumber SQL 2012 however, it is pushing pinned items from their position
Here's a set-based approach to solving this. Note that the first CTE is unnecessary if you already have a Numbers table in your database:
declare #t table (ID int,GMD decimal(5,2),IsPinned bit)
insert into #t (ID,GMD,IsPinned) values
(1,2.5,0), (2, 0 ,1), (3, 2 ,0), (4, 4 ,1), (5, 3 ,0)
;With Numbers as (
select ROW_NUMBER() OVER (ORDER BY ID) n from #t
), NumbersWithout as (
select
n,
ROW_NUMBER() OVER (ORDER BY n) as rn
from
Numbers
where n not in (select ID from #t where IsPinned=1)
), DataWithout as (
select
*,
ROW_NUMBER() OVER (ORDER BY GMD desc) as rn
from
#t
where
IsPinned = 0
)
select
t.*,
COALESCE(nw.n,t.ID) as RowNo
from
#t t
left join
DataWithout dw
inner join
NumbersWithout nw
on
dw.rn = nw.rn
on
dw.ID = t.ID
order by COALESCE(nw.n,t.ID)
Hopefully my naming makes it clear what we're doing. I'm a bit cheeky in the final SELECT by using a COALESCE to get the final RowNo when you might have expected a CASE expression. But it works because the contents of the DataWithout CTE is defined to only exist for unpinned items which makes the final LEFT JOIN fail.
Results:
ID GMD IsPinned RowNo
----------- --------------------------------------- -------- --------------------
5 3.00 0 1
2 0.00 1 2
1 2.50 0 3
4 4.00 1 4
3 2.00 0 5
Second variant that may perform better (but never assume, always test):
declare #t table (ID int,GMD decimal(5,2),IsPinned bit)
insert into #t (ID,GMD,IsPinned) values
(1,2.5,0), (2, 0 ,1), (3, 2 ,0), (4, 4 ,1), (5, 3 ,0)
;With Numbers as (
select ROW_NUMBER() OVER (ORDER BY ID) n from #t
), NumbersWithout as (
select
n,
ROW_NUMBER() OVER (ORDER BY n) as rn
from
Numbers
where n not in (select ID from #t where IsPinned=1)
), DataPartitioned as (
select
*,
ROW_NUMBER() OVER (PARTITION BY IsPinned ORDER BY GMD desc) as rn
from
#t
)
select
dp.ID,dp.GMD,dp.IsPinned,
CASE WHEN IsPinned = 1 THEN ID ELSE nw.n END as RowNo
from
DataPartitioned dp
left join
NumbersWithout nw
on
dp.rn = nw.rn
order by RowNo
In the third CTE, by introducing the PARTITION BY and removing the WHERE clause we ensure we have all rows of data so we don't need to re-join to the original table in the final result in this variant.
this will work:
CREATE TABLE Table1
("ID" int, "GMD" number, "IsPinned" int)
;
INSERT ALL
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (1, 2.5, 0)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (2, 0, 1)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (3, 2, 0)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (4, 4, 1)
INTO Table1 ("ID", "GMD", "IsPinned")
VALUES (5, 3, 0)
SELECT * FROM dual
;
select * from (select "ID","GMD","IsPinned",rank from(select m.*,rank()over(order by
"ID" asc) rank from Table1 m where "IsPinned"=1)
union
(select "ID","GMD","IsPinned",rank from (select t.*,rank() over(order by "GMD"
desc)-1 rank from (SELECT * FROM Table1)t)
where "IsPinned"=0) order by "GMD" desc) order by rank ,GMD;
output:
2 0 1 1
5 3 0 1
1 2.5 0 2
4 4 1 2
3 2 0 3
Can you try this query
CREATE TABLE Table1
(ID int, GMD numeric (18,2), IsPinned int);
INSERT INTO Table1 (ID,GMD, IsPinned)
VALUES (1, 2.5, 0),
(2, 0, 1),
(3, 2, 0),
(4, 4, 1),
(5, 3, 0)
select *, row_number () over(partition by IsPinned order by (case when IsPinned =0 then GMD else id end) ) [CustOrder] from Table1
This took longer then I thought, the thing is row_number would take a part to resolve the query. We need to differentiate the row_numbers by id first and then we can apply the while loop or cursor or any iteration, in our case we will just use the while loop.
dbo.test (you can replace test with your table name)
1 2.5 False
2 0 True
3 3 False
4 4 True
6 2 False
Here is the query I wrote to achieve your result, I have added comment under each operation you should get it, if you have any difficultly let me know.
Query:
--user data table
DECLARE #userData TABLE
(
id INT NOT NULL,
gmd FLOAT NOT NULL,
ispinned BIT NOT NULL,
rownumber INT NOT NULL
);
--final result table
DECLARE #finalResult TABLE
(
id INT NOT NULL,
gmd FLOAT NOT NULL,
ispinned BIT NOT NULL,
newrownumber INT NOT NULL
);
--inserting to uer data table from the table test
INSERT INTO #userData
SELECT t.*,
Row_number()
OVER (
ORDER BY t.id ASC) AS RowNumber
FROM test t
--creating new table for ids of not pinned
CREATE TABLE #ids
(
rn INT,
id INT,
gmd FLOAT
)
-- inserting into temp table named and adding gmd by desc
INSERT INTO #ids
(rn,
id,
gmd)
SELECT DISTINCT Row_number()
OVER(
ORDER BY gmd DESC) AS rn,
id,
gmd
FROM #userData
WHERE ispinned = 0
--declaring the variable to loop through all the no pinned items
DECLARE #id INT
DECLARE #totalrows INT = (SELECT Count(*)
FROM #ids)
DECLARE #currentrow INT = 1
DECLARE #assigningNumber INT = 1
--inerting pinned items first
INSERT INTO #finalResult
SELECT ud.id,
ud.gmd,
ud.ispinned,
ud.rownumber
FROM #userData ud
WHERE ispinned = 1
--looping through all the rows till all non-pinned items finished
WHILE #currentrow <= #totalrows
BEGIN
--skipping pinned numers for the rows
WHILE EXISTS(SELECT 1
FROM #finalResult
WHERE newrownumber = #assigningNumber
AND ispinned = 1)
BEGIN
SET #assigningNumber = #assigningNumber + 1
END
--getting row by the number
SET #id = (SELECT id
FROM #ids
WHERE rn = #currentrow)
--inserting the non-pinned item with new row number into the final result
INSERT INTO #finalResult
SELECT ud.id,
ud.gmd,
ud.ispinned,
#assigningNumber
FROM #userData ud
WHERE id = #id
--going to next row
SET #currentrow = #currentrow + 1
SET #assigningNumber = #assigningNumber + 1
END
--getting final result
SELECT *
FROM #finalResult
ORDER BY newrownumber ASC
--dropping table
DROP TABLE #ids
Output:

Trying to group by a value in SQL

I have a table called TESTTABLE
The table script and some sample date
CREATE TABLE Test_Table(
NODE VARCHAR(10) NOT NULL PRIMARY KEY
,EVENTID CHAR(255) NOT NULL
,TYPE INTEGER NOT NULL
,FIRSTOCCURRENCE VARCHAR(16) NOT NULL
,LASTOCCURRENCE VARCHAR(16) NOT NULL
,TALLY INTEGER NOT NULL
,TICKETNUMBER VARCHAR(20)
,TIME_DELTA VARCHAR(5)
);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Washington','ReachabilityProblem',2,'12/13/2017 23:24','12/13/2017 23:24',1,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('San Diego','ReachabilityProblem',1,'12/13/2017 23:23','12/13/2017 23:23',1,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Richmond','ReachabilityProblem',1,'12/13/2017 14:23','12/13/2017 14:23',1,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Richmond','ReachabilityProblem',1,'12/13/2017 23:23','12/13/2017 23:23',1,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('New York','ReachabilityProblem',2,'12/13/2017 23:24','12/13/2017 23:24',1,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('New York','ReachabilityProblem',2,'12/13/2017 11:32','12/13/2017 11:33',2,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('New York','ReachabilityProblem',1,'12/13/2017 16:35','12/13/2017 16:35',1,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Landsdown','ReachabilityProblem',2,'12/13/2017 23:24','12/13/2017 23:24',1,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Houston','ReachabilityProblem',2,'12/13/2017 14:24','12/13/2017 14:24',1,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Houston','ReachabilityProblem',1,'12/13/2017 11:31','12/13/2017 11:32',2,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Dallas','ReachabilityProblem',1,'12/13/2017 23:23','12/13/2017 23:23',1,NULL,NULL);
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Dallas','ReachabilityProblem',2,'12/13/2017 23:24','12/13/2017 23:24',1,NULL,'1 sec');
INSERT INTO Test_Table(NODE,EVENTID,TYPE,FIRSTOCCURRENCE,LASTOCCURRENCE,TALLY,TICKETNUMBER,TIME_DELTA) VALUES ('Coco Beach','ReachabilityProblem',1,'12/13/2017 23:23','12/13/2017 23:23',1,NULL,NULL);
I'm trying to obtain this
I have tried this
Select DATEDIFF(Day, GETDATE(), DATEADD(HOUR, 15, GETDATE()))
Select
[NODE]
,[EVENTID]
,[TYPE]
,[FIRSTOCCURRENCE]
,LASTOCCURRENCE]
,DATEDIFF(Minute, FIrst OCCURENCE, LAST OCCURENCE) as [Outage in MIN]
,[TicketNumber]
,[Severity]
,Tally]
From
[XYZ].[XYZ].[XYZ_STATUS]
Where
[FIRST OCCURRENCE] >= DATEADD(hh, -24, GETDATE())
Group by node;
Please help a rookie
Group by returns a relation/table with a row for each group, if you are going to use the GROUP BY clause, so in your SELECT statement you can only select the column that you are grouping by and use aggregate functions on that column because the other columns will not appear in the resulting table.
Maybe this is what you want...
Select
DATEDIFF (DAY, GETDATE(), DATEADD(Hour, 15, GETDATE())),
,Node
,EventID
,Type
,Severity
,Tally
FROM xyz.xyz.xyz_status
GROUP BY Node,EventID,Type,Severity,Tally
When we group by two or more columns, it is saying "Group them so that all of those with the same col1 and col2 are in the same group, and then calculate all the aggregate functions (Count, Sum, Average, etc.) for each of those groups"
Maybe you want this...
SELECT DATEDIFF(minute,(SELECT TOP(1) FIRSTOCCURRENCE FROM
xyz.xyz.xyz_status),(SELECT TOP(1) LASTOCCURRENCE FROM
xyz.xyz.xyz_status))
FROM xyz.xyz.xyz_status
WHERE node = 'Houston';
Here you can take a look at more examples of DATEDIFF function.
This should put you on track although Writing reports in SQL is probably a bad idea. What I believe you're wanting to do it output. You can also look at the ROLLUP options some of which are deprecated.
with data as (
select
NODE, EVENTID, TYPE, FIRSTOCCURRENCE, LASTOCCURRENCE,
DATEDIFF(Minute, FIRSTOCCURRENCE, LASTOCCURRENCE) as OutageInMin,
TicketNumber, Tally,
ROW_NUMBER() OVER (PARTITION BY NODE ORDER BY FIRSTOCCURRENCE) as rn
from Test_Table
--WHERE FIRSTOCCURRENCE >= DATEADD(hh, -24, GETDATE())
)
select
case when grouping(rn) = 1 then 'SITE TOTAL' else NODE end as NODE,
case when grouping(rn) = 1 then null else min(EVENTID) end as EVENTID,
case when grouping(rn) = 1 then null else min(TYPE) end as TYPE,
case when grouping(rn) = 1 then null else min(FIRSTOCCURRENCE) end as FIRSTOCCURRENCE,
case when grouping(rn) = 1 then null else min(LASTOCCURRENCE) end as LASTOCCURRENCE,
case when grouping(rn) = 1 then null else min(Tally) end as Tally,
case when grouping(rn) = 1 then null else min(TicketNumber) end as TicketNumber,
case when grouping(node) = 1
then min(OutageInMin) else sum(OutageInMin) end as "Outage In MIN"
from
data
group by grouping sets ( (NODE, rn), (NODE) )
order by data.NODE, grouping(rn), rn;
http://rextester.com/DZIHJ81264
GROUP BY is only authorized in SQL when you are aggregating something. The easiest exemple is a count.
Example : you want to know how much EventID are linked to a given Node :
SELECT Count(EventId), node FROM xyz.xyz.xyz_status GROUP BY node;
Here is a site that present the Group By function. If you clarify what you are searching for, we'll give you a more concrete example.

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.