将相邻、重叠和嵌入的范围合并为相互排斥的范围
Consolidate adjacent, overlapping and embedded ranges into mutually exclusive ranges
环境是SQLServer 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 = 先前 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 ids 和 150M detail 的顺序——下载方式的数据太多,并导致其他资源问题。我相信所有细节都可能在 1.2B 行的范围内。无论如何,如果这一切都可以在 SQL 服务器中完成,那么整个过程就会得到简化。
好的,这个答案会让你接近。我觉得有点过头了,但绝对是在正确的轨道上。我相信您可以根据自己的需要进行调整。问题的症结在于建立重叠家庭。在建立 parent 列表开始后,我使用了递归 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 服务器 2014
- 合理理解window函数
- 牢牢掌握 CTE,尤其是递归 CTE。
循序渐进
- 首先查询数据。这使用 ROW_NUMBER 函数建立一个基于 USERID 和 Ascending RangeStarts 的顺序 ORDER。稍后用于将列表组织回此顺序。 LAG 函数检索前几行的 PreviousStart 和 PreviousEnd 日期。这也会在建立 parent 时使用,并且该号码将用作基于该 parent id 的家庭标识符。
- Parent孩子根据 2 条规则填充 Parent 列。如果 previousstart 和 previousend 值为 NULL,则表示在分区中它们是第一项。我们自动将它们分配为 parent 行。然后,如果 PreviousEnd 不在开始和结束范围之间,我们也将其用作 parent。
- 家庭才是真正的魔法所在。使用递归 CTE,我们查询所有 parent,然后将所有非 parent 联合到它们关联的主订单 + 1。加号 + 1 确保我们填充所有 0 和 A.parent = 0 谓词确保我们只将未分配的家庭成员加入 parent 范围。
- 在最终结果中,我们简单地根据用户 ID 和 parent 列(现在是每个重叠系列的唯一编号)得到一个最小和最大组。
看看。很好的问题和一些有趣的脑筋急转弯。
干杯
马特
环境是SQLServer 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 = 先前 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 ids 和 150M detail 的顺序——下载方式的数据太多,并导致其他资源问题。我相信所有细节都可能在 1.2B 行的范围内。无论如何,如果这一切都可以在 SQL 服务器中完成,那么整个过程就会得到简化。
好的,这个答案会让你接近。我觉得有点过头了,但绝对是在正确的轨道上。我相信您可以根据自己的需要进行调整。问题的症结在于建立重叠家庭。在建立 parent 列表开始后,我使用了递归 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 服务器 2014
- 合理理解window函数
- 牢牢掌握 CTE,尤其是递归 CTE。
循序渐进
- 首先查询数据。这使用 ROW_NUMBER 函数建立一个基于 USERID 和 Ascending RangeStarts 的顺序 ORDER。稍后用于将列表组织回此顺序。 LAG 函数检索前几行的 PreviousStart 和 PreviousEnd 日期。这也会在建立 parent 时使用,并且该号码将用作基于该 parent id 的家庭标识符。
- Parent孩子根据 2 条规则填充 Parent 列。如果 previousstart 和 previousend 值为 NULL,则表示在分区中它们是第一项。我们自动将它们分配为 parent 行。然后,如果 PreviousEnd 不在开始和结束范围之间,我们也将其用作 parent。
- 家庭才是真正的魔法所在。使用递归 CTE,我们查询所有 parent,然后将所有非 parent 联合到它们关联的主订单 + 1。加号 + 1 确保我们填充所有 0 和 A.parent = 0 谓词确保我们只将未分配的家庭成员加入 parent 范围。
- 在最终结果中,我们简单地根据用户 ID 和 parent 列(现在是每个重叠系列的唯一编号)得到一个最小和最大组。
看看。很好的问题和一些有趣的脑筋急转弯。
干杯
马特