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

时间:2015-12-04 21:02:40

标签: sql sql-server sql-server-2014

我很难尝试进行查询,获得大量数字,一系列数字,如果两个数字之间的差异大于30,则序列会从此数字重置。所以,我有下表,其中有另一列不是第一列,应保持原样:

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

感谢您的关注,我将清除对我的问题的任何疑问! :)

编辑:此表的示例:How to Import Intellisense files into vsCode (Visual Studio Code)

5 个答案:

答案 0 :(得分:4)

使用此测试用例:

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

这个SQL:

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;

根据需要产生:

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
;

更新:使用新的LAG()函数可以在没有自我加入的情况下使用更简单的SQL:

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

答案 1 :(得分:2)

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

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;

答案 2 :(得分:1)

这简直就是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;

它应该是一个评论,但它太长了,不可理解。

答案 3 :(得分:0)

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

li.erase( --(it.base()) )

SQL Fiddle

这是另一个SQL小提琴,其中有一个例子,说明如果ID不是连续的话,你会怎么做。

SQL Fiddle 2

如果sql小提琴停止工作

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

答案 4 :(得分:0)

我尝试在这里优化查询,因为需要1h20m来处理我的数据。经过一些进一步的研究,我把它降到了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

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