Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If the difference between two sequences is bigger than 30, deduct bigger sequence

I'm having a hard time trying to make a query that gets a lot of numbers, a sequence of numbers, and if the difference between two of them is bigger than 30, then the sequence resets from this number. So, I have the following table, which has another column other than the number one, which should be maintained intact:

+----+--------+--------+
| Id | Number | Status |
+----+--------+--------+
|  1 |      1 | OK     |
|  2 |      1 | Failed |
|  3 |      2 | Failed |
|  4 |      3 | OK     |
|  5 |      4 | OK     |
|  6 |     36 | Failed |
|  7 |     39 | OK     |
|  8 |     47 | OK     |
|  9 |     80 | Failed |
| 10 |    110 | Failed |
| 11 |    111 | OK     |
| 12 |    150 | Failed |
| 13 |    165 | OK     |
+----+--------+--------+

It should turn it into this one:

+----+--------+--------+
| Id | Number | Status |
+----+--------+--------+
|  1 |      1 | OK     |
|  2 |      1 | Failed |
|  3 |      2 | Failed |
|  4 |      3 | OK     |
|  5 |      4 | OK     |
|  6 |      1 | Failed |
|  7 |      4 | OK     |
|  8 |     12 | OK     |
|  9 |      1 | Failed |
| 10 |      1 | Failed |
| 11 |      2 | OK     |
| 12 |      1 | Failed |
| 13 |     16 | OK     |
+----+--------+--------+

Thanks for your attention, I will be available to clear any doubt regarding my problem! :)

EDIT: Sample of this table here: http://sqlfiddle.com/#!6/ded5af

like image 755
Lucas Garcia Avatar asked Mar 14 '23 23:03

Lucas Garcia


1 Answers

With this test case:

declare @data table (id int identity, Number int, Status varchar(20));
insert @data(number, status) values
     ( 1,'OK')
    ,( 1,'Failed')
    ,( 2,'Failed')
    ,( 3,'OK')
    ,( 4,'OK')
    ,( 4,'OK')      -- to be deleted, ensures IDs are not sequential
    ,(36,'Failed')  -- to be deleted, ensures IDs are not sequential
    ,(36,'Failed')
    ,(39,'OK')
    ,(47,'OK')
    ,(80,'Failed')
,(110,'Failed')
,(111,'OK')
,(150,'Failed')
,(165,'OK')
;

delete @data where id between 6 and 7;

This SQL:

with renumbered as (
    select rn = row_number() over (order by id), data.*
    from @data data
),
paired as (
    select
        this.*,
        startNewGroup = case when this.number - prev.number >= 30 
                               or prev.id is null then 1 else 0 end
    from renumbered this
    left join renumbered prev on prev.rn = this.rn -1
),
groups as (
    select Id,Number, GroupNo = Number from paired where startNewGroup = 1
)
select
     Id
    ,Number = 1 + Number - (
                    select top 1 GroupNo 
                    from groups where groups.id <= paired.id 
                    order by GroupNo desc)
    ,status
from paired
;

yields as desired:

Id          Number      status
----------- ----------- --------------------
1           1           OK
2           1           Failed
3           2           Failed
4           3           OK
5           4           OK
8           1           Failed
9           4           OK
10          12          OK
11          1           Failed
12          1           Failed
13          2           OK
14          1           Failed
15          16          OK

Update: using the new LAG() function allows somewhat simpler SQL without a self-join early on:

with renumbered as (
    select
         data.*
        ,gap = number - lag(number, 1) over (order by number)
    from @data data
),
paired as (
    select
        *,
        startNewGroup = case when gap >= 30 or gap is null then 1 else 0 end
    from renumbered 
),
groups as (
    select Id,Number, GroupNo = Number from paired where startNewGroup = 1
)
select
     Id
    ,Number = 1 + Number - ( select top 1 GroupNo 
                             from groups 
                             where groups.id <= paired.id 
                             order by GroupNo desc
                           )
    ,status
from paired
;
like image 67
Pieter Geerkens Avatar answered Mar 16 '23 11:03

Pieter Geerkens