Overlapping Date Ranges - Identifying Only the Overlap

后端 未结 3 613
我寻月下人不归
我寻月下人不归 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

提交回复
热议问题