【问题标题】:Group close numbers组关闭号码
【发布时间】:2011-11-07 16:16:19
【问题描述】:

我有一个包含 2 列整数的表。第一列表示起始索引,第二列表示结束索引。

START END
1     8
9     13
14    20
20    25
30    42
42    49
60    67

到目前为止很简单。我想做的是将所有记录分组在一起:

START END
1     25
30    49
60    67

一条记录可以跟在与前一个结束索引相同的索引处或以 1 的边距开始:

START END
1     10
10    20

START END
1     10
11    20

都会导致

START END
1     20

我使用的是 SQL Server 2008 R2。

任何帮助都会很棒

【问题讨论】:

  • 我认为这是一个有趣的问题,但您是否真的尝试过自己做这件事?您尝试过的查询?
  • 您是否有任何重叠对,例如1,8 AND 3,15
  • Tx 为您的评论 Martin.. 没有重叠对。 Jadarnel27 - 我使用 sql cursor 解决了这个问题,但这个解决方案根本没有效率,我正在寻找更优雅和更好的解决方案。

标签: sql sql-server sql-server-2008 gaps-and-islands


【解决方案1】:

您可以使用number table 来解决此问题。基本上,您首先扩展范围,然后将后续项目组合成组。

这是一个实现:

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

此解决方案使用master..spt_values 作为数字表,用于扩展初始范围。但如果(全部或部分)这些范围可能跨越超过 2048 个(后续)值,那么您应该定义并使用 your own 数字表。

【讨论】:

    【解决方案2】:

    已编辑以包含另一个我认为更可靠的版本,并且也适用于重叠范围

    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
    

    我认为这应该可行 - 虽然在较大的集合上可能不是特别有效...

    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
    

    【讨论】:

    • +1 在这里测试,它工作。幸好你包含了CREATEINSERT 语句。
    • 嗨,Davin,您的第二个解决方案更加可靠,因为第一个解决方案效果不佳。实际上,原始表不包含任何重叠。如果您知道如何以更有效的方式解决问题而不重叠,我想知道。 Tx 为您提供帮助:)
    • @Liran Ben Yehuda - 在您的原始问题中,您希望 1-10,11-20 和 1-10,10-20 的示例给出 1-20 的范围 - 所以有重叠在第二种情况下 10 出现两次,这是否意味着在您的实际表中每个开始和结束范围值都是唯一的?
    • @Devin - 很抱歉造成混乱。您是对的,我的意思是不存在重叠范围,例如您的示例中的第二行 (2,15)(也是 [9,25] 行值)
    • @Liran,在这种情况下,我认为这仍然应该是相对有效的,因为 CTE 只会填补现有范围的空白,然后查看哪些落在前一行的 1 内 -您的实际表中有多少范围的数据?
    【解决方案3】:

    这适用于您的示例,如果它不适用于其他数据,请告诉我

    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;
    

    【讨论】:

    • 嗨,Aducci,您的解决方案也适用于比示例表中的数据更大的其他数据。
    • @Liran Ben Yehuda - 您是否有理由将其取消标记为答案?
    • 感谢您的支持。我只是在寻找最好的解决方案,之前我必须进行一些性能测试。
    猜你喜欢
    • 1970-01-01
    • 2022-11-01
    • 2018-08-27
    • 1970-01-01
    • 1970-01-01
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多