【问题标题】:Merge Contiguous date ranges that are from same id and have same amount合并来自相同 ID 且数量相同的连续日期范围
【发布时间】:2020-03-03 03:39:33
【问题描述】:

我有以下数据集,其中人员具有 id 并且他们的数量会随着时间而变化,因此变化被记录为日期范围。有时在没有可用信息的日期范围内会出现空白。没事。但是,我想合并连续的日期范围,它们恰好具有相同的数量,而它跨越多个记录,例如 ID = 1 的第 2 行和第 3 行。

DECLARE @DataTable TABLE (
    ID [int] NULL,
    StartDate [date] NULL,
    EndDate [date] NULL,
    Amount [decimal](12,2) NULL
)

INSERT INTO @DataTable
SELECT 1, '20180101','20180513', 10.00 UNION ALL
SELECT 1, '20180630','20190301', 15.00 UNION ALL
SELECT 1, '20190302','20190615', 15.00 UNION ALL
SELECT 1, '20190616','20991231', 5.00 UNION ALL
SELECT 2, '20190101','20190331', 35.00 UNION ALL
SELECT 2, '20190401','20191031', 30.00  UNION ALL
SELECT 3, '20180505','20180930', 19.00 UNION ALL
SELECT 3, '20181001','20190228', 1.00 UNION ALL
SELECT 3, '20190501','20190815', 1.00 UNION ALL
SELECT 3, '20190819','20190827', 5.00 UNION ALL
SELECT 3, '20190828','20991231', 1.00 UNION ALL
SELECT 4, '2017-10-01', '2017-12-31',   688.96 UNION ALL
SELECT 4, '2018-01-01', '2018-04-30',   707.96 UNION ALL
SELECT 4, '2018-05-01', '2018-05-31',   783.96 UNION ALL
SELECT 4, '2018-06-01', '2018-12-31',   707.96 UNION ALL
SELECT 4, '2019-01-01', '2019-03-31',   707.96 UNION ALL
SELECT 4, '2019-04-01', '2019-04-30',   571.46 UNION ALL
SELECT 4, '2019-05-01', '2019-06-30',   707.96 UNION ALL
SELECT 4, '2019-07-01', '2099-12-31',   707.96
;

我通过使用 dimDate 在开始日期和结束日期之间生成日期线来解决这个问题,然后记录金额与先前记录相比发生变化或 ID 存在日期差距的位置。然后我使用下一个可用的记录日期作为结束日期。查询如下:

WITH DateList AS (
SELECT DT.*, DD.DateOnly AS RecordDate
FROM @DataTable DT
INNER JOIN dimDate DD ON DT.StartDate <= DD.DateOnly AND CASE WHEN DT.EndDate > GETDATE() THEN CONVERT(DATE,GETDATE()) ELSE DT.EndDate END >= DD.DateOnly
)

, PrevValue AS (
SELECT 
    *
    , LAG(RecordDate) OVER (PARTITION BY ID ORDER BY RecordDate) AS PrevDate
    , LAG(Amount) OVER (PARTITION BY ID ORDER BY RecordDate) AS PrevAmt
FROM DateList 
)
, KeepHistory AS (
SELECT 
    *
FROM PrevValue
WHERE Amount <> PrevAmt OR PrevAmt IS NULL OR DATEADD(DAY,1,PrevDate) <> RecordDate OR PrevDate IS NULL
)
, FINAL AS (
SELECT 
    *
    , LEAD(PrevDate) OVER (PARTITION BY ID ORDER BY StartDate) AS NextEndDate
FROM KeepHistory
)

SELECT 
    ID 
    , StartDate
    , CASE WHEN NextEndDate > EndDate THEN NextEndDate ELSE EndDate END AS EndDate
    , Amount
FROM FINAL 

我的问题是有另一种方法可以解决此问题,而无需通过 dimDate 或在开始日期和结束日期之间生成日期。我可以通过使用诸如here之类的间隙和孤岛问题之类的窗口函数来实现这一点吗?

如果您发现我当前的解决方案有任何问题,请告诉我。 谢谢。

关于@Larnu 响应,您更新后的查询适用于大多数 .我添加了 ID = 4 示例,该示例在合并其第 4 行和第 5 行时似乎会导致问题。当第 6 行的数量不同时,它也会合并第 7 行。

ID = 4 4th, 5th and 7th are merged; 6th one has different amount

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    这就是你所追求的吗?

    WITH Gaps AS(
        SELECT DT.ID,
               DT.StartDate,
               DT.EndDate,
               DT.Amount,
               DATEDIFF(DAY,LAG(DATEADD(DAY,1,DT.EndDate),1,DT.StartDate) OVER (PARTITION BY DT.ID, DT.Amount ORDER BY DT.StartDate ASC), DT.StartDate) AS Gap
        FROM @DataTable DT),
    Grps AS(
        SELECT G.ID,
               G.StartDate,
               G.EndDate,
               G.Amount,
               ROW_NUMBER() OVER (PARTITION BY G.ID ORDER BY G.StartDate) - 
               ROW_NUMBER() OVER (PARTITION BY G.ID,Amount ORDER BY G.StartDate) + Gap AS Grp
        FROM Gaps G)
    SELECT G.ID,
           MIN(G.StartDate) AS StartDate,
           MAX(EndDate) AS EndDate,
           G.Amount
    FROM Grps G
    GROUP BY G.ID,
             G.Amount,
             G.Grp
    ORDER BY ID,
             StartDate;
    

    添加了唯一 ID,以绕过“功能”:

    DECLARE @DataTable TABLE (
        UniqueID int IDENTITY(1,1),
        ID [int] NULL,
        StartDate [date] NULL,
        EndDate [date] NULL,
        Amount [decimal](12,2) NULL
    )
    
    INSERT INTO @DataTable
    SELECT 1, '20180101','20180513', 10.00 UNION ALL
    SELECT 1, '20180630','20190301', 15.00 UNION ALL
    SELECT 1, '20190302','20190615', 15.00 UNION ALL
    SELECT 1, '20190616','20991231', 5.00 UNION ALL
    SELECT 2, '20190101','20190331', 35.00 UNION ALL
    SELECT 2, '20190401','20191031', 30.00  UNION ALL
    SELECT 3, '20180505','20180930', 19.00 UNION ALL
    SELECT 3, '20181001','20190228', 1.00 UNION ALL
    SELECT 3, '20190501','20190815', 1.00 UNION ALL
    SELECT 3, '20190819','20190827', 5.00 UNION ALL
    SELECT 3, '20190828','20991231', 1.00 UNION ALL
    SELECT 4, '20171001', '20171231',   688.96 UNION ALL
    SELECT 4, '20180101', '20180430',   707.96 UNION ALL
    SELECT 4, '20180501', '20180531',   783.96 UNION ALL
    SELECT 4, '20180601', '20181231',   707.96 UNION ALL
    SELECT 4, '20190101', '20190331',   707.96 UNION ALL
    SELECT 4, '20190401', '20190430',   571.46 UNION ALL
    SELECT 4, '20190501', '20190630',   707.96 UNION ALL
    SELECT 4, '20190701', '20991231',   707.96;
    
    --SELECT *
    --FROM @DataTable;
    
    WITH Gaps AS(
        SELECT DT.UniqueID,
               DT.ID,
               DT.StartDate,
               DT.EndDate,
               DT.Amount,
               DATEDIFF(DAY,LAG(DATEADD(DAY,1,DT.EndDate),1,DT.StartDate) OVER (PARTITION BY DT.ID, DT.Amount ORDER BY DT.UniqueID ASC), DT.StartDate) AS Gap
        FROM @DataTable DT),
    Grps AS(
        SELECT G.UniqueID,
               G.ID,
               G.StartDate,
               G.EndDate,
               G.Amount,
               G.Gap,
               ROW_NUMBER() OVER (PARTITION BY G.ID ORDER BY G.UniqueID) - 
               ROW_NUMBER() OVER (PARTITION BY G.ID,Amount ORDER BY G.UniqueID) + (Gap * UniqueID) AS Grp
        FROM Gaps G)
    SELECT G.ID,
           MIN(G.StartDate) AS StartDate,
           MAX(EndDate) AS EndDate,
           G.Amount
    FROM Grps G
    GROUP BY G.ID,
             G.Amount,
             G.Grp
    ORDER BY ID,
             StartDate;
    

    【讨论】:

    • 看起来它正在合并第 8 行和第 9 行,其中日期范围存在间隙。虽然第 8 行和第 9 行共享相同的金额,但第 8 行的结束日期是 02/08/2019,而第 9 行的开始日期是 05/01/2019。
    • 嘿@Larnu 非常感谢。除了我想出的一种情况外,您的查询适用于大多数情况。在我最初的回复中,我添加了 ID = 4 记录,结果以图像形式提供。我试图玩弄您的查询以尝试解决此问题,但到目前为止还没有运气。谢谢。
    • 抱歉,我在运行您的查询时使用了“12/31/2099”。我刚才打错了 9999。我现在改正它。
    • 这与使用 12/31/9999 时可能导致的日期溢出错误无关。第 4、5、7 行仍在合并中,第 6 行数量不同。
    • 图像是您查询的结果。我在上面加载“DataTable”表变量的第一个查询中为 ID = 4 添加了额外的行。
    【解决方案2】:

    你可以如下使用递归

        DECLARE @DataTable TABLE (
            ID [int] NULL,
            StartDate [date] NULL,
            EndDate [date] NULL,
            Amount [decimal](12,2) NULL
        )
    
        INSERT INTO @DataTable
        SELECT 1, '20180101','20180513', 10.00 UNION ALL
        SELECT 1, '20180630','20190301', 15.00 UNION ALL
        SELECT 1, '20190302','20190615', 15.00 UNION ALL
        SELECT 1, '20190616','20991231', 5.00 UNION ALL
        SELECT 2, '20190101','20190331', 35.00 UNION ALL
        SELECT 2, '20190401','20191031', 30.00  UNION ALL
        SELECT 3, '20180505','20180930', 19.00 UNION ALL
        SELECT 3, '20181001','20190228', 1.00 UNION ALL
        SELECT 3, '20190501','20190815', 1.00 UNION ALL
        SELECT 3, '20190819','20190827', 5.00 UNION ALL
        SELECT 3, '20190828','20991231', 1.00;
    
    
        ;with cte as(
        select t1.id,
    t1.startDate,
    t1.EndDate,
    t1.Amount,
         row_number() over (partition by t1.id order by t1.Id,t1.startDate) R#  
    from @DataTable t1
        left outer join @DataTable t2 on dateadd(d,-1,t1.startdate)=t2.enddate and t1.Amount=t2.Amount
        and t1.id=t2.id
        where t2.amount is null),
        cte1 as
        (select 
    t1.id,
    t1.startDate,
    t1.EndDate,
    t1.Amount,
    t1.R# from cte t1
    
        union all
    
        select t1.id,
        t2.startdate,
    t1.endDate, 
    t1.amount,
    R# from @DataTable t1 inner join cte1 t2
        on dateadd(d,-1,t1.startdate)=t2.enddate and t1.Amount=t2.Amount)
        select id,
    min(stArtdate)[startdate],
    max(enddate)[enddate],
    min(amount)[amount] from cte1 GROUP by R#,id 
        Order  by id,max(enddate)
    

    【讨论】:

    猜你喜欢
    • 2018-03-05
    • 2013-03-24
    • 1970-01-01
    • 1970-01-01
    • 2015-09-13
    • 2017-02-12
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    相关资源
    最近更新 更多