差距和孤岛问题 - 查询不适用于所有时期
Posted
技术标签:
【中文标题】差距和孤岛问题 - 查询不适用于所有时期【英文标题】:Gap and Island problem - query not working for all periods 【发布时间】:2021-01-13 22:28:02 【问题描述】:我必须创建一个查询来查找日期之间的差距和孤岛。这似乎是一个标准的差距和孤岛问题。为了显示我的问题,我将使用数据样本。查询在 Snowflake 中执行。
CREATE TABLE TEST (StartDate date, EndDate date);
INSERT INTO TEST
SELECT '8/20/2017', '8/21/2017' UNION ALL
SELECT '8/22/2017', '9/22/2017' UNION ALL
SELECT '8/23/2017', '9/23/2017' UNION ALL
SELECT '8/24/2017', '8/26/2017' UNION ALL
SELECT '8/28/2017', '9/19/2017' UNION ALL
SELECT '9/23/2017', '9/27/2017' UNION ALL
SELECT '9/25/2017', '10/10/2017' UNION ALL
SELECT '10/17/2017','10/18/2017' UNION ALL
SELECT '10/25/2017','11/3/2017' UNION ALL
SELECT '11/3/2017', '11/15/2017';
这段代码给了我一个表格示例。
然后我就有了寻找缝隙和孤岛的代码:
SELECT
MIN(StartDate) AS IslandStartDate,
MAX(EndDate) AS IslandEndDate
FROM
(
SELECT
*,
CASE WHEN PreviousEndDate >= StartDate THEN 0 ELSE 1 END AS IslandStartInd,
SUM(CASE WHEN PreviousEndDate >= StartDate THEN 0 ELSE 1 END) OVER (ORDER BY Groups.RN) AS IslandId
FROM
(
SELECT
ROW_NUMBER() OVER(ORDER BY StartDate,EndDate) AS RN,
StartDate,
EndDate,
LAG(EndDate,1) OVER (ORDER BY StartDate, EndDate) AS PreviousEndDate
FROM
TEST
) Groups
) Islands
GROUP BY
IslandId
ORDER BY
IslandStartDate
结果是:
如您所见,问题出现在 2017 年 8 月 28 日至 2017 年 9 月 19 日期间。 此期间不应是一个单独的岛屿,因为它应包含在以下期间:8/23/2017 - 9/23/2017。
您知道如何修改我的查询以获得正确的结果(因此 6 我应该有 5 个岛,因为 2017 年 8 月 28 日 - 2017 年 9 月 19 日不应该是岛)。这只是数据示例,所以我正在寻找通用解决方案,但到目前为止我还没有找到正确的方法。
【问题讨论】:
用您正在使用的数据库标记您的问题。 【参考方案1】:您可以这样表达间隙和孤岛逻辑:
select min(startdate), max(enddate)
from (select t.*,
sum(case when prev_enddate >= startdate then 0 else 1 end) over (order by startdate) as grp
from (select t.*,
max(enddate) over (order by startdate rows between unbounded preceding and 1 preceding) as prev_enddate
from test t
) t
) t
group by grp
order by min(startdate);
Here 是一个 dbfiddle。
我们的想法是在所有“较早”行上查找最大结束日期。该值用于检查是否存在重叠。
因此,最里面的子查询计算上一个结束日期。中间子查询对组的开头进行累积求和以分配组标识符。
外部查询只是按组标识符聚合。
【讨论】:
谢谢,我正在验证数据,但到目前为止一切正常,您的解决方案有效,谢谢!【参考方案2】:您可以从原始集中删除重叠记录:
SELECT MinStart as StartDate, MaxEnd as EndDate
FROM Test data
CROSS APPLY (SELECT MIN(StartDate) MinStart, MAX(EndDate) MaxEnd FROM TEST lkp WHERE lkp.StartDate < data.EndDate AND lkp.EndDate > data.StartDate) bounds
GROUP BY MinStart, MaxEnd
StartDate | EndDate |
---|---|
2017-08-20 | 2017-08-21 |
2017-08-22 | 2017-09-23 |
2017-08-23 | 2017-10-10 |
2017-10-17 | 2017-10-18 |
2017-10-25 | 2017-11-03 |
2017-11-03 | 2017-11-15 |
在当前的结果集中,没有发生额外的重复,但在更大的记录集中,可能会有更大范围的连续记录。这意味着您可能需要递归执行此查找。
把这些放在一起你会得到:
SELECT
MIN(StartDate) AS IslandStartDate,
MAX(EndDate) AS IslandEndDate
FROM
(
SELECT
*,
CASE WHEN PreviousEndDate >= StartDate THEN 0 ELSE 1 END AS IslandStartInd,
SUM(CASE WHEN PreviousEndDate >= StartDate THEN 0 ELSE 1 END) OVER (ORDER BY Groups.RN) AS IslandId
FROM
(
SELECT
ROW_NUMBER() OVER(ORDER BY StartDate,EndDate) AS RN,
StartDate,
EndDate,
LAG(EndDate,1) OVER (ORDER BY StartDate, EndDate) AS PreviousEndDate
FROM
(
SELECT MinStart as StartDate, MaxEnd as EndDate
FROM Test data
CROSS APPLY (SELECT MIN(StartDate) MinStart, MAX(EndDate) MaxEnd FROM TEST lkp WHERE lkp.StartDate < data.EndDate AND lkp.EndDate > data.StartDate) bounds
GROUP BY MinStart, MaxEnd
) Normalized
) Groups
) Islands
GROUP BY
IslandId
ORDER BY
IslandStartDate
这会导致 4 个岛,而不是您最初期望的 5 个,因为您的第 2 和第 3 行输入以及第 6 和第 7 行,它们创建了一个岛跨越 8/22 - 10/10 !
SELECT '8/22/2017', '9/22/2017' UNION ALL
SELECT '8/23/2017', '9/23/2017' UNION ALL
...
SELECT '9/23/2017', '9/27/2017' UNION ALL
SELECT '9/25/2017', '10/10/2017' UNION ALL
IslandStartDate | IslandEndDate |
---|---|
2017-08-20 | 2017-08-21 |
2017-08-22 | 2017-10-10 |
2017-10-17 | 2017-10-18 |
2017-10-25 | 2017-11-15 |
【讨论】:
以上是关于差距和孤岛问题 - 查询不适用于所有时期的主要内容,如果未能解决你的问题,请参考以下文章
差距和孤岛问题是不是有正式定义?如果是这样,这个问题是不是满足它?