【问题标题】:Grouping date ranges into rolling group of 15 days将日期范围分组为 15 天的滚动组
【发布时间】:2019-08-14 17:06:37
【问题描述】:

我在每一行都有开始和结束日期时间的数据,我正在尝试对结束日期时间后 15 天内的所有行进行分组,当开始日期不属于时,使用结束日期时间重置并开始新组15天。

我尝试了不同的解决方案,例如使用逻辑进行自我加入,并在查找结束日期时间和结束日期时间 + 30 之间的开始日期时间后尝试选择最小结束日期,但我无法确定继续使用结束日期时间的方法当开始日期时间在 15 天内时,从开始/上一行开始,直到找到没有的行。

with cte as
(
SELECT 1 id, '2018-12-25' col1, '2019-01-05' col2
UNION ALL
SELECT 1, '2019-03-01' col1,    '2019-03-10' col2
UNION ALL
SELECT 1, '2019-03-15' col1,    '2019-03-19' col2
UNION ALL
SELECT 1, '2019-03-22' col1,    '2019-03-28' col2
UNION ALL
SELECT 1, '2019-03-30' col1,    '2019-04-02' col2
UNION ALL
SELECT 1, '2019-04-10' col1,    '2019-04-15' col2
UNION ALL
SELECT 1, '2019-04-18' col1,    '2019-04-25' col2
), STG AS
(
SELECT A.*,  MIN(B.COL2) AS GRP_COL2
FROm CTE A
LEFT OUTER JOIN CTE B ON A.col1 BETWEEN B.col2 AND DATEADD(day, 15, B.col2)
GROUP BY A.id, A.col1, A.col2
)
SELECT A.id, A.col1, A.col2, COALESCE(GRP_COL2, A.COL2) AS GRP_COL2
FROM STG A

ORDER By 1,2
--this returns wrong result for 04/10 and 04/18 dated rows in this case

例如我有以下数据:

ProductId   ProductStartDt   ProductEndDt
1           2018-12-25       2019-01-05
1           2019-03-01       2019-03-10
1           2019-03-15       2019-03-19
1           2019-03-22       2019-03-28
1           2019-03-30       2019-04-02
1           2019-04-10       2019-04-15
1           2019-04-18       2019-04-25

所以我要做的是从最小结束日期开始,并将开始日期在结束日期 + 15 天范围内的所有行分组为一个组,并将下一行作为新组开始,并继续使用结束日期该行作为组的新开始。

预期输出

ProductId   ProductStartDt   ProductEndDt   GroupNo
1           2018-12-25       2019-01-05     1
1           2019-03-01       2019-03-10     2
1           2019-03-15       2019-03-19     2
1           2019-03-22       2019-03-28     2
1           2019-03-30       2019-04-02     3
1           2019-04-10       2019-04-15     3
1           2019-04-18       2019-04-25     4

对于第 2 组,开始日期不在第 1 组或开始行的结束日期的 15 天内,这将触发新行的开始。我们看到 03/15 和 03/22 在 03/10 的 15 天内,我们将它们分组为一个组。 03/30 不属于前一组,这将导致新组 3 的开始,直到我们找到结束日期时间 + 15 天内的所有开始日期。

【问题讨论】:

  • 请发布您的代码并指出您遇到问题的地方,以便我们提供帮助。
  • 这看起来将是一个“两次通过过程”。您首先需要找到每个 GroupNo 的开始记录,然后确定属于该组的行。其他方法看起来会演变为逐行分析。考虑现在需要的实际流程。

标签: sql sql-server


【解决方案1】:

使用recursive CTE,我们可以遍历初始表,并确定当前行属于现有组还是新组:

;with Product as (
    select * from (
        VALUES 
(1, '2018-12-25','2019-01-05' ),
(1, '2019-03-01','2019-03-10' ),
(1, '2019-03-15','2019-03-19' ),
(1, '2019-03-22','2019-03-28' ),
(1, '2019-03-30','2019-04-02' ),
(1, '2019-04-10','2019-04-15' ),
(1, '2019-04-18','2019-04-25' )
    ) as a1 (ProductId   ,ProductStartDt   ,ProductEndDt)
), OrderedProduct as (
    select *, ROW_NUMBER() over (order by ProductStartDt) as RowNum
    from Product
), DateGroupsInterim (RowNum, GroupNum, GrpStartDt) as (
    select RowNum, 1, ProductEndDt
    from OrderedProduct
    where RowNum=1
    union all 
    select OrderedProduct.RowNum, 
        CASE WHEN OrderedProduct.ProductStartDt <= dateadd(day, 15, dgi.GrpStartDt) 
                THEN dgi.GroupNum 
                ELSE dgi.GroupNum + 1
        END,
        CASE WHEN OrderedProduct.ProductStartDt <= dateadd(day, 15, dgi.GrpStartDt) 
                THEN dgi.GrpStartDt 
                ELSE OrderedProduct.ProductEndDt
        END
    from DateGroupsInterim dgi
        join OrderedProduct on OrderedProduct.RowNum=dgi.RowNum+1
) select OrderedProduct.ProductId, OrderedProduct.ProductStartDt, OrderedProduct.ProductEndDt, DateGroupsInterim.GroupNum as GroupNo
from DateGroupsInterim
    JOIN OrderedProduct on OrderedProduct.RowNum = DateGroupsInterim.RowNum;

【讨论】:

  • 谢谢,这很有帮助。你认为这可以不使用递归吗?
  • 老实说我不知道​​。我怀疑递归或while 循环是不可避免的,因为组中的行取决于组中的第一行,组中的第一行取决于前一组,依此类推
猜你喜欢
  • 2014-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-04
  • 2021-11-16
  • 1970-01-01
相关资源
最近更新 更多