Overlapping Date Ranges - Identifying Only the Overlap

后端 未结 3 612
我寻月下人不归
我寻月下人不归 2021-01-27 00:23

I\'ve seen a lot of solutions to identify records where date ranges overlap, and still other examples of merging overlapping ranges.

However I am interested in results

相关标签:
3条回答
  • 2021-01-27 00:23
    CREATE TABLE #TMP
    (
        CustomerID INT,
        ProductID VARCHAR(12),
        Eff_Dt DATE,
        End_Dt DATE
    );
    --
    INSERT INTO #TMP
    VALUES
    -- Customer 1000: Expecting results to show 2 rows: "1/1 - 1/5"; "1/10 - 1/15"
    (1000, 'PRODUCT_A', N'2013-01-01T00:00:00', N'2013-01-31T00:00:00'),
    (1000, 'PRODUCT_B', N'2013-01-01T00:00:00', N'2013-01-05T00:00:00'),
    (1000, 'PRODUCT_B', N'2013-01-10T00:00:00', N'2013-01-15T00:00:00'),
    (1000, 'PRODUCT_C', N'2013-01-01T00:00:00', N'2013-01-31T00:00:00'),
    -- Customer 2000: Expecting results to show 1 row: "1/19 - 1/31"
    (2000, 'PRODUCT_A', N'2013-01-01T00:00:00', N'2013-01-31T00:00:00'),
    (2000, 'PRODUCT_B', N'2013-01-01T00:00:00', N'2013-01-31T00:00:00'),
    (2000, 'PRODUCT_C', N'2013-01-19T00:00:00', N'2013-01-31T00:00:00'),
    -- Customer 3000: Expecting results to show NO rows (or nulls)
    (3000, 'PRODUCT_A', N'2013-01-01T00:00:00', N'2013-01-10T00:00:00'),
    (3000, 'PRODUCT_A', N'2013-01-16T00:00:00', N'2013-01-31T00:00:00'),
    (3000, 'PRODUCT_B', N'2013-01-01T00:00:00', N'2013-01-12T00:00:00'),
    (3000, 'PRODUCT_C', N'2013-01-15T00:00:00', N'2013-01-31T00:00:00'),
    -- Customer 4000: Expecting results to show 1 row: "1/15 - 1/23"
    (4000, 'PRODUCT_A', N'2013-01-15T00:00:00', N'2013-01-31T00:00:00'),
    (4000, 'PRODUCT_B', N'2013-01-01T00:00:00', N'2013-01-31T00:00:00'),
    (4000, 'PRODUCT_C', N'2013-01-01T00:00:00', N'2013-01-23T00:00:00'),
    -- Customer 5000: Expecting results to show 0 rows
    (5000, 'PRODUCT_A', N'2013-01-17T00:00:00', N'2013-01-31T00:00:00'),
    (5000, 'PRODUCT_B', N'2013-01-01T00:00:00', N'2013-01-10T00:00:00'),
    (5000, 'PRODUCT_C', N'2013-01-07T00:00:00', N'2013-01-19T00:00:00'),
    -- Customer 6000: Expecting results to show 3 rows: "1/11 - 1/12"; "1/17 - 1/22"; "1/26 - 1/27"
    (6000, 'PRODUCT_A', N'2013-01-01T00:00:00', N'2013-01-04T00:00:00'),
    (6000, 'PRODUCT_A', N'2013-01-09T00:00:00', N'2013-01-12T00:00:00'),
    (6000, 'PRODUCT_A', N'2013-01-17T00:00:00', N'2013-01-22T00:00:00'),
    (6000, 'PRODUCT_A', N'2013-01-26T00:00:00', N'2013-01-31T00:00:00'),
    (6000, 'PRODUCT_B', N'2013-01-04T00:00:00', N'2013-01-28T00:00:00'),
    (6000, 'PRODUCT_C', N'2013-01-11T00:00:00', N'2013-01-27T00:00:00');
    --
    --
    ;
    WITH MIN_MAX
    AS (SELECT MIN(Eff_Dt) S,
               MAX(End_Dt) E
        FROM #TMP),
         ALL_DATES
    AS (SELECT MIN_MAX.S DT
        FROM MIN_MAX
        UNION ALL
        SELECT DATEADD(DAY, 1, ALL_DATES.DT)
        FROM ALL_DATES
        WHERE ALL_DATES.DT < (SELECT MIN_MAX.E FROM MIN_MAX)),
         GROUPED
    AS (SELECT Q.CustomerID,
               Q.ProductID,
               Q.DT,
               CASE WHEN MAX(TX.CustomerID) IS NULL THEN 0 ELSE 1 END YES
        FROM
        (
            SELECT *FROM ALL_DATES
                CROSS JOIN
                (SELECT DISTINCT CustomerID, ProductID FROM #TMP) AS AQ
        ) AS Q
            LEFT JOIN #TMP AS TX
                ON TX.CustomerID = Q.CustomerID
                   AND TX.ProductID = Q.ProductID
                   AND Q.DT
                   BETWEEN TX.Eff_Dt AND TX.End_Dt
        GROUP BY Q.CustomerID,
                 Q.ProductID,
                 Q.DT),
         BuildFlags
    AS (SELECT G.CustomerID,
               G.DT,
               ROW_NUMBER() OVER (PARTITION BY G.CustomerID ORDER BY G.DT) RN,
               CASE
                   WHEN WQ.tot =
                   (
                       SELECT COUNT(DISTINCT g2.ProductID)
                       FROM GROUPED AS g2
                       WHERE g2.CustomerID = G.CustomerID
                             AND g2.DT = G.DT
                             AND g2.YES = 1
                   ) THEN 1
                   ELSE 0
               END FLAG
        FROM GROUPED AS G
            CROSS APPLY
        (
            SELECT COUNT(DISTINCT E9.ProductID) tot
            FROM #TMP AS E9
            WHERE E9.CustomerID = G.CustomerID
        ) AS WQ ),
         AddRanks
    AS (SELECT *,
               BuildFlags.RN - ROW_NUMBER() OVER (PARTITION BY BuildFlags.CustomerID,
                                                               BuildFlags.FLAG
                                                  ORDER BY BuildFlags.DT
                                                 ) groupRank
        FROM BuildFlags)
    SELECT AddRanks.CustomerID,
           MIN(AddRanks.DT) AS StartDate,
           MAX(AddRanks.DT) AS EndDate
    FROM AddRanks
    WHERE AddRanks.FLAG = 1
    GROUP BY AddRanks.CustomerID,
             AddRanks.groupRank
    ORDER BY AddRanks.CustomerID,
             MIN(AddRanks.DT)
    OPTION (MAXRECURSION 0);
    

    You can Find the Execution at DB Fiddle

    0 讨论(0)
  • 2021-01-27 00:28

    Try:

    select ab.CustomerID, 
           case when ab_Eff_Dt > c.Eff_Dt then ab_Eff_Dt else c.Eff_Dt end abc_Eff_Dt,
           case when ab_End_Dt < c.End_Dt then ab_End_Dt else c.End_Dt end abc_End_Dt
    from
    (select a.CustomerID, 
            case when a.Eff_Dt > b.Eff_Dt then a.Eff_Dt else b.Eff_Dt end ab_Eff_Dt,
            case when a.End_Dt < b.End_Dt then a.End_Dt else b.End_Dt end ab_End_Dt
     from #tmp a
     join #tmp b 
       on a.CustomerID = b.CustomerID and a.Eff_Dt < b.End_Dt and b.Eff_Dt < a.End_Dt
     where a.ProductID = 'PRODUCT_A' and b.ProductID = 'PRODUCT_B') ab
    join #tmp c 
      on ab.CustomerID = c.CustomerID and ab_Eff_Dt < c.End_Dt and c.Eff_Dt < ab_End_Dt
    where c.ProductID = 'PRODUCT_C' 
    

    (SQLFiddle here)

    0 讨论(0)
  • 2021-01-27 00:40

    Here's the answer:

    select t.customerid, t.eff_dt, count(distinct t2.productId),
           MIN(t2.end_dt) as end_dt
    from #tmp t join
         #tmp t2
         on t.CustomerID = t2.CustomerID and
            t.Eff_Dt between t2.Eff_Dt and t2.End_Dt
    group by t.CustomerID, t.eff_dt
    having count(distinct t2.productId) = 3
    

    This is using a self-join to count the number of different products on each eff_dt. You want three distinct products, so that is what the having clause is doing.

    There are three distinct products until one of them ends. That would be the first end_dt after the effective date -- which is calculated by the min(end_dt).

    0 讨论(0)
提交回复
热议问题