差距和孤岛问题 - 查询不适用于所有时期

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

【讨论】:

以上是关于差距和孤岛问题 - 查询不适用于所有时期的主要内容,如果未能解决你的问题,请参考以下文章

使用差距和孤岛知识找到最长时间不改变就业(SQL)

差距和孤岛问题是不是有正式定义?如果是这样,这个问题是不是满足它?

差距和孤岛 - Microsoft Access

具有开始和结束日期的差距和孤岛(有效期)

SQL 差距和孤岛问题与扭曲 - 根据前一个标志的持续时间重置标志

SQL Server - 计算会话数 - 差距和孤岛