使用唯一 ID 对日期范围和岛屿进行分组,无需按分隔符进行分区

Grouping date-ranges and Islands with uniq ID's without Partition by seperator

我需要一些关于基于日期范围分组的帮助,我没有任何明显的分隔符分区。我有这个数据集 (Table New_Test),其中每个 ID 都有未知数量的月+年条目,但如果它们是连接的,它们应该获得相同的分组 ID(在新列中)。例如。如果 2016 年第 9 个月之后是 2016 年第 10 个月,它们都应该获得 Grouping-ID 1。如果存在差距,即 2018 年第 3 个月到 2018 年第 5 个月的情况,则新的 Grouping ID 应该是分配。

为了找到月份的连续顺序,我绘制了 Match1 和 Match2(辅助列),它们是前一个和下一个 Start- 和 EndDate 的超前和滞后函数。

为了分配 Grouping-ID,我尝试了一个 IIF 公式,其中 StartDate = Match2 或 EndDate = Match1 之间的匹配被分配值 1 或 0。我尝试用各种替代 1 Dense_rank、排名、Row_number 的版本。如果我使用 Dense_Rank() OVER (PARTITION BY ID ORDER BY ID),我得到关于 ID 1 的三个组范围的 Grouping-ID 值 1,0,1 而不是 1,2,3 的目标因为我的数据集中没有可用的分隔符。这意味着当我稍后想按日期范围 MIN 和 MAX 对这些数据进行分组时,这两个岛屿将合并为 1 个,这不是我想要的。

我希望有人对此有一些很好的意见! :)

SELECT 
ID
,StartDate
,EndDate
,LEAD(DATEADD(day,-1,StartDate),1) OVER (ORDER BY ID, Year, Month) AS Match1
,LAG(DATEADD(day,1,EndDate),1) OVER (ORDER BY ID, Year, Month) AS Match2
,IIF(StartDate= LAG(DATEADD(day,1,EndDate),1) OVER (ORDER BY ID, Year, Month)
OR EndDate =LEAD(DATEADD(day,-1,StartDate),1) OVER (ORDER BY ID, Year, Month)
,1,0) AS Grouping-ID
,Year
,Month
FROM NEW_Test

我的数据在第一次编辑后的样子:

ID  StartDate   EndDate     Match1      Match2      Year    Month
1   01-09-2016  30-09-2016  30-09-2016  NULL        2016    9
1   01-10-2016  31-10-2016  31-10-2016  01-10-2016  2016    10
1   01-11-2016  30-11-2016  30-11-2016  01-11-2016  2016    11
1   01-12-2016  31-12-2016  31-12-2016  01-12-2016  2016    12
1   01-01-2017  31-01-2017  31-01-2017  01-01-2017  2017    1
1   01-02-2017  28-02-2017  28-02-2017  01-02-2017  2017    2
1   01-03-2017  31-03-2017  31-03-2017  01-03-2017  2017    3
1   01-04-2017  30-04-2017  30-04-2017  01-04-2017  2017    4
1   01-05-2017  31-05-2017  31-05-2017  01-05-2017  2017    5
1   01-06-2017  30-06-2017  30-06-2017  01-06-2017  2017    6
1   01-07-2017  31-07-2017  31-07-2017  01-07-2017  2017    7
1   01-08-2017  31-08-2017  31-08-2017  01-08-2017  2017    8
1   01-09-2017  30-09-2017  30-09-2017  01-09-2017  2017    9
1   01-10-2017  31-10-2017  31-10-2017  01-10-2017  2017    10
1   01-11-2017  30-11-2017  30-11-2017  01-11-2017  2017    11
1   01-12-2017  31-12-2017  31-12-2017  01-12-2017  2017    12
1   01-01-2018  31-01-2018  31-01-2018  01-01-2018  2018    1
1   01-02-2018  28-02-2018  28-02-2018  01-02-2018  2018    2
1   01-03-2018  31-03-2018  30-04-2018  01-03-2018  2018    3
1   01-05-2018  31-05-2018  31-10-2018  01-04-2018  2018    5
1   01-11-2018  30-11-2018  30-11-2018  01-06-2018  2018    11
1   01-12-2018  31-12-2018  NULL        01-12-2018  2018    12
2   01-09-2016  30-09-2016  30-09-2016  NULL        2016    9
2   01-10-2016  31-10-2016  31-10-2016  01-10-2016  2016    10
2   01-11-2016  30-11-2016  30-11-2016  01-11-2016  2016    11
2   01-01-2017  31-01-2017  31-01-2017  01-01-2017  2017    1
2   01-02-2017  28-02-2017  28-02-2017  01-02-2017  2017    2
2   01-03-2017  31-03-2017  31-03-2017  01-03-2017  2017    3
2   01-04-2017  30-04-2017  30-04-2017  01-04-2017  2017    4
2   01-05-2017  31-05-2017  31-05-2017  01-05-2017  2017    5

最终结果应该是什么:

ID  StartDate   EndDate     Match1      Match2      Year Month  Grouping-ID
1   01-09-2016  30-09-2016  30-09-2016  NULL        2016    9   1
1   01-10-2016  31-10-2016  31-10-2016  01-10-2016  2016    10  1
1   01-11-2016  30-11-2016  30-11-2016  01-11-2016  2016    11  1
1   01-12-2016  31-12-2016  31-12-2016  01-12-2016  2016    12  1
1   01-01-2017  31-01-2017  31-01-2017  01-01-2017  2017    1   1
1   01-02-2017  28-02-2017  28-02-2017  01-02-2017  2017    2   1
1   01-03-2017  31-03-2017  31-03-2017  01-03-2017  2017    3   1
1   01-04-2017  30-04-2017  30-04-2017  01-04-2017  2017    4   1
1   01-05-2017  31-05-2017  31-05-2017  01-05-2017  2017    5   1
1   01-06-2017  30-06-2017  30-06-2017  01-06-2017  2017    6   1
1   01-07-2017  31-07-2017  31-07-2017  01-07-2017  2017    7   1
1   01-08-2017  31-08-2017  31-08-2017  01-08-2017  2017    8   1
1   01-09-2017  30-09-2017  30-09-2017  01-09-2017  2017    9   1
1   01-10-2017  31-10-2017  31-10-2017  01-10-2017  2017    10  1
1   01-11-2017  30-11-2017  30-11-2017  01-11-2017  2017    11  1
1   01-12-2017  31-12-2017  31-12-2017  01-12-2017  2017    12  1
1   01-01-2018  31-01-2018  31-01-2018  01-01-2018  2018    1   1
1   01-02-2018  28-02-2018  28-02-2018  01-02-2018  2018    2   1
1   01-03-2018  31-03-2018  30-04-2018  01-03-2018  2018    3   1
1   01-05-2018  31-05-2018  31-10-2018  01-04-2018  2018    5   2
1   01-11-2018  30-11-2018  30-11-2018  01-06-2018  2018    11  3
1   01-12-2018  31-12-2018  NULL        01-12-2018  2018    12  3
2   01-09-2016  30-09-2016  30-09-2016  NULL        2016    9   4
2   01-10-2016  31-10-2016  31-10-2016  01-10-2016  2016    10  4
2   01-11-2016  30-11-2016  30-11-2016  01-11-2016  2016    11  4
2   01-01-2017  31-01-2017  31-01-2017  01-01-2017  2017    1   5
2   01-02-2017  28-02-2017  28-02-2017  01-02-2017  2017    2   5
2   01-03-2017  31-03-2017  31-03-2017  01-03-2017  2017    3   5
2   01-04-2017  30-04-2017  30-04-2017  01-04-2017  2017    4   5
2   01-05-2017  31-05-2017  31-05-2017  01-05-2017  2017    5   5

这是间隙和孤岛问题的变体。

我会按如下方式进行:

  • 在子查询中,使用 ROW_NUMER() 按 id 和开始日期对记录进行排序,并设置一个标记来检查下一条记录的开始日期是否与当前记录的末尾连续,并且如果它有相同的 id
  • 在外部查询中,对标志进行window求和;行号和标志之间的差异为您提供了分组 id

考虑:

SELECT 
    x.*
    1 + rn - SUM(matched) OVER(ORDER BY id, rn) AS GroupingID
FROM (
    SELECT
        t.*
        ROW_NUMBER() OVER(ORDER BY id, StartDate) rn,
        CASE 
            WHEN 
                id = LEAD(id) OVER(ORDER BY id, StartDate)
                AND DATEADD(day, 1, EndDate) = LEAD(StartDate) OVER(ORDER BY id, StartDate)
            THEN 1 
            ELSE 0 
        END matched
    FROM mytable
) x

非常感谢GBM!你已经通过指出正确的方向解决了我的问题!如果我想在两个方向上按一个 ID 对日期范围进行分组,我需要再添加一个 AND 子句 - 意思是:

SELECT 
    x.*
    ,1 + rn - SUM(matched) OVER(ORDER BY id, rn) AS GroupingID
FROM (
    SELECT
        t.*
        ,ROW_NUMBER() OVER(ORDER BY id, StartDate) rn,
    ,CASE
        WHEN 
            ID = LEAD(ID) OVER(ORDER BY ID, StartDate)
            AND (DATEADD(day, 1, EndDate) = LEAD(StartDate) OVER(ORDER BY ID, StartDate)
            AND DATEADD(day, -1, StartDate) = LAG(EndDate) OVER(ORDER BY ID, StartDate))
        THEN 1 
        ELSE 0
    END AS Matched
FROM New_test AS t
) x