如果两个序列之间的差异大于 30,则扣除更大的序列

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

我很难尝试查询获取大量数字,一个数字序列,如果两者之间的差值大于 30,则序列将从该数字重置。所以,我有下面的table,其中除了第一列还有另一列,应该保持原样:

+----+--------+--------+
| 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     |
+----+--------+--------+

应该变成这个:

+----+--------+--------+
| 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     |
+----+--------+--------+

感谢您的关注,我将随时为您解答有关我的问题的任何疑问! :)

编辑:这里 table 的示例:http://sqlfiddle.com/#!6/ded5af

您可能需要在此之前创建另一个 cte 并使用 row_number 而不是 ID 来加入递归 cte,如果您的 ID 不是按顺序排列的

WITH cte AS 
(   SELECT 
        Id, [Number], [Status],
        0 AS Diff,
        [Number] AS [NewNumber]
    FROM
        Table1
    WHERE Id = 1
    UNION ALL 
    SELECT 
        t1.Id, t1.[Number], t1.[Status],
        CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN t1.Number - 1 ELSE Diff END,
        CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN 1 ELSE t1.[Number] - Diff END
    FROM Table1 t1
    JOIN cte ON cte.Id + 1 = t1.Id
)
SELECT Id, [NewNumber], [Status] 
FROM cte

SQL Fiddle

这是另一个 SQL Fiddle,其中包含一个示例,如果 ID 不是连续的,您会怎么做。

SQL Fiddle 2

如果sql fiddle停止工作

--Order table to make sure there is a sequence to follow
WITH OrderedSequence AS
(
    SELECT 
        ROW_NUMBER() OVER (ORDER BY Id) RnId,
        Id,
        [Number],
        [Status]
    FROM 
        Sequence
),
RecursiveCte AS 
(   SELECT 
        Id, [Number], [Status],
        0 AS Diff,
        [Number] AS [NewNumber],
        RnId
    FROM
        OrderedSequence
    WHERE Id = 1
    UNION ALL 
    SELECT 
        t1.Id, t1.[Number], t1.[Status],
        CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN t1.Number - 1 ELSE Diff END,
        CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN 1 ELSE t1.[Number] - Diff END,
        t1.RnId      
    FROM OrderedSequence t1
    JOIN RecursiveCte cte ON cte.RnId + 1 = t1.RnId
)
SELECT Id, [NewNumber], [Status] 
FROM RecursiveCte

有了这个测试用例:

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;

这个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
;

所需产量:

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

更新:使用新的 LAG() 函数允许稍微简单一些 SQL,无需早期自连接:

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
;

这只是 Pieter Geerkens 的回答略有简化。我删除了一些中间结果和列:

with renumbered as (
    select data.*, gap = number - lag(number, 1) over (order by number)
    from @data data
),
paired as (
    select *
    from renumbered 
    where gap >= 30 or gap is null
)

select Id, Number = 1 + Number - (select top 1 Number 
                             from paired 
                             where paired.id <= renumbered.id 
                             order by Number desc)
    , status
from renumbered;

本来应该是评论,但是太长了,看不懂。

我不值得回答,但我认为这更短

with gapped as 
(   select id, number, gap = number - lag(number, 1) over (order by id)
    from @data data
),
select Id, status
       ReNumber = Number + 1 - isnull( (select top 1 gapped.Number 
                                          from gapped 
                                         where gapped.id <= data.id 
                                           and gap >= 30 
                                         order by gapped.id desc), 1)  
from @data data;

我尝试优化此处的查询,因为处理我的数据需要 1 小时 20 分钟。经过进一步研究,我把它降到了 30 秒。

WITH AuxTable AS 
(   SELECT
        id, 
        number,
        status,

        relevantId = CASE WHEN 
            number = 1 OR 
            ((number - LAG(number, 1) OVER (ORDER BY id)) > 29)
        THEN id
        ELSE NULL
        END,

        deduct = CASE WHEN 
            ((number - LAG(number, 1) OVER (ORDER BY id)) > 29)
        THEN number - 1
        ELSE 0
        END

    FROM @data data
)
,AuxTable2 AS
(
    SELECT 
        id, 
        number,
        status,
        AT.deduct, 

        MAX(AT.relevantId) OVER (ORDER BY AT.id ROWS UNBOUNDED PRECEDING ) AS lastRelevantId

    FROM AuxTable AT
)

    SELECT 
        id, 
        number,
        status,   

        number - MAX(deduct) OVER(PARTITION BY lastRelevantId ORDER BY id ROWS UNBOUNDED PRECEDING ) AS ReNumber,

    FROM AuxTable2

我认为这运行得更快,但并不短。