【问题标题】:Consolidate adjacent, overlapping and embedded ranges into mutually exclusive ranges将相邻、重叠和嵌入的范围合并为互斥范围
【发布时间】:2015-05-17 21:29:17
【问题描述】:

环境是 SQL Server 2014。

我正在处理将许多保险投保细节(第一个和最后一个小范围)缩减为更大的互斥 (ME) 连续投保范围。

为了清楚起见,问题简化为按 id、first、last 排序的样本数据。 F(n) 和 L(n) 是 id 内记录 n 中的第一个和最后一个值。

大多数细节范围都是典型的

  • 相邻,F(n) = L(n-1) + 1

但细节里有魔鬼——欢迎来到真实世界的数据。

  • 连接不相邻,F(n)
  • 嵌入式,L(n)
  • 重叠,L(n) > L(n-1)
  • 断开非相邻
    • gap 定义了相互排斥的合并范围的边界
    • ME(i).last = 先前 L 的最大值
  • 这张图展示了大多数情况

    Have
      1      30       60       90      120
      +-------+--------+--------+--------+
    1 +-------+                             (1:30)
    2          +-------+                    (31:60) adjacent
    3             +--+                      (40:50) embedded
    4                   +                   (61:61) adjacent some earlier
    5                   +-+                 (61:65) adjacent some earlier
    6                   +--+                (61:75) adjacent some earlier
    7                     +--+              (65:80) overlap
    8                          +---------+  (85:120) gap, boundaries of ME ranges located
    9                            +-------+  (91:120)
    10                                +--+  (110:120)
    
    Want
    
      1      30       60       90      120
      +-------+--------+--------+--------+
    1 +----------------------+              (1:80)
    2                          +---------+  (85:120)
    
    There are other unusual cases, such as embed followed by gap
    
    .....
      ..
          ....
    AAAAA BBBB
    
    
    DROP TABLE #Details
    CREATE TABLE #Details (id int, first int, last int);
    
    insert into #Details values (1,   1, 30);
    insert into #Details values (1,  31, 60);
    insert into #Details values (1,  40, 50);
    insert into #Details values (1,  61, 75);
    insert into #Details values (1,  65, 80);
    insert into #Details values (1,  85, 120);
    insert into #Details values (1,  91, 120);
    insert into #Details values (1, 110, 120);
    

    我在堆栈和Refactoring Ranges 上阅读了一些答案,但无法实现我的数据排列。

    --对于 jpw--

    典型的分析可能涉及 20,000 个 ID 和 200 条详细记录。这些情况已通过下载到本地机器并在 SAS 数据步骤中处理(以类似光标的方式)进行处理。最坏的情况是订购 650K id 和 150M 详细信息 - 下载方式的数据过多,并导致其他资源问题。我相信所有细节都可能在 1.2B 行的范围内。无论如何,如果这一切都可以在 SQL Server 中完成,那么整个过程就被简化了。

    【问题讨论】:

    • 表格大概有多少行?数百、数千、数百万或更多?
    • 典型分析可能涉及 20,000 个 id 和 200 条详细记录。
    • 我在一个更大的数据集上尝试了我的方法,但结果根本不起作用......要多考虑一下。
    • 我正在为你做几个例子,Richard。我处理过类似的事情。只是想找到我的代码。 2014 年的 LEAD 窗口功能也很有帮助。

    标签: tsql date-range sql-server-2014 gaps-and-islands


    【解决方案1】:

    好吧,这个答案会让你接近。对我来说感觉有点过头了,但绝对是在正确的轨道上。我相信您可以根据自己的需要进行调整。问题的症结在于建立重叠族。在建立父列表开始后,我使用了递归 cte。请参阅下面的解释了解更多详细信息。

    初始数据

    USERID      RangeStart  RangeEnd
    ----------- ----------- -----------
    1           1           2
    1           2           4
    1           3           5
    1           6           7
    2           1           3
    2           5           9
    2           11          14
    2           14          15
    

    查询

    DECLARE @USERID TABLE (USERID INT, RangeStart INT, RangeEnd INT)
    INSERT INTO @USERID (USERID, RangeStart,RangeEnd) VALUES
    (1,1,2),(1,2,4),(1,3,5),(1,6,7),
    (2,1,3),(2,5,9),(2,11,14),(2,14,15)
    
    ;WITH Data AS (
        SELECT  ROW_NUMBER() OVER (ORDER BY USERID, RangeStart) AS MasterOrdering,
                USERID,
                RangeStart,
                RangeEnd,
                LAG(RangeStart) OVER (PARTITION BY USERID ORDER BY RangeStart ASC) AS PreviousStart,
                LAG(RangeEnd) OVER (PARTITION BY USERID ORDER BY RangeStart ASC) AS PreviousEnd
        FROM    @USERID
    ), ParentChild AS (
        SELECT  *,
                Parent  =   CASE
                                WHEN PreviousStart IS NULL AND PreviousEnd IS NULL THEN MasterOrdering
                                WHEN PreviousEnd NOT BETWEEN RangeStart AND RangeEnd THEN MasterOrdering
                                ELSE 0
                            END
        FROM    Data
    ), Family AS (
        SELECT  MasterOrdering,
                USERID,
                RangeStart,
                RangeEnd,
                PreviousStart,
                PreviousEnd,
                Parent
        FROM    ParentChild
        WHERE   Parent > 0
        UNION   ALL
        SELECT  A.MasterOrdering,
                A.USERID,
                A.RangeStart,
                A.RangeEnd,
                A.PreviousStart,
                A.PreviousEnd,
                F.Parent
        FROM    ParentChild AS A
                INNER JOIN Family AS F ON ( A.MasterOrdering = F.MasterOrdering + 1 
                                            AND A.parent = 0)
    )
    SELECT  USERID, 
            MIN(RangeStart) AS RangeStart, 
            MAX(RangeEnd) AS RangeEnd,
            MIN(MasterOrdering) AS MasterOrdering
    FROM    Family
    GROUP   BY UserID,Parent
    ORDER   BY MIN(MasterOrdering)
    

    结果

    USERID      RangeStart  RangeEnd    MasterOrdering
    ----------- ----------- ----------- --------------------
    1           1           5           1
    1           6           7           4
    2           1           3           5
    2           5           9           6
    2           11          15          7
    

    查询遍历

    假设

    • SQL Server 2014
    • 对窗口函数的合理理解
    • 牢牢掌握 CTE,尤其是递归 CTE。

    一步一步

    • 从查询数据开始。这使用 ROW_NUMBER 函数根据 USERID 和 Ascending RangeStarts 建立顺序 ORDER。它稍后用于将列表组织回此顺序。 LAG 函数检索以前的行 PreviousStart 和 PreviousEnd 日期。这也会在建立父母时使用,并且该号码会用作基于该父母 ID 的家庭标识符。
    • ParentChild 根据 2 条规则填充 Parent 列。如果previousstart 和previousend 值为NULL,则表示在part 中它们是第一项。我们自动将它们分配为父行。然后,如果 PreviousEnd 不在开始和结束范围之间,我们也将其用作父级。
    • 家庭才是真正的魔力所在。使用递归 CTE,我们查询所有父级,然后将所有非父级联合到其关联的主订单 + 1。加号 + 1 确保我们填充所有 0,而 A.parent = 0 谓词确保我们只加入非分配家庭成员到父范围。
    • 在最后的输出中,我们只是按用户 ID 和父列(现在是每个重叠系列的唯一编号)组成了一个最小和最大组。

    看看。很好的问题和一些有趣的脑筋急转弯。

    干杯

    马特

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多