将相邻,重叠和嵌入范围合并为互斥范围

时间:2015-03-15 19:54:34

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

环境是SQL Server 2014。

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

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

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

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

但细节上有恶魔 - 欢迎来到现实世界的数据。

  • 连接非相邻,F(n)<= L(n-1)
    • 嵌入,L(n)<= L(n-1)
    • 重叠,L(n)> L(n-1)
  • 断开非相邻
    • gap定义了互斥的合并范围的边界
    • ME(i).last = max of prior 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服务器中完成,那么整个过程就会简化。

1 个答案:

答案 0 :(得分: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条规则填充“父”列。如果previousstart和previousend值为NULL,则意味着在partion内它们是第一个项目。我们将它们自动分配为父行。然后,如果PreviousEnd不在开始和结束范围之间,那么我们也将其用作父项。
  • 家庭是真正的魔力所在。使用递归CTE,我们查询所有父项,然后将所有非父项联合到它们的关联主顺序+ 1.加号+ 1确保我们填充所有0和A.parent = 0谓词确保我们是仅将未分配的家庭成员加入父母范围。
  • 在最后一个出版物中,我们只有userid和父列的最小和最大组(现在是每个重叠族的唯一编号)。

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

干杯

马特