SQL 查询按间隔拆分记录

SQL query to split records by intervals

假设我有一个 table,它有列 FromTo,它们是日期和一个位类型列,它标识它是否是一个取消(1 = 取消)。还有一个 Id 是 PK,CancelId 引用被取消的内容。

假设我有如下记录:

Id From       To         IsCancel CancelId
1  2015-01-01 2015-01-31 0        NULL
2  2015-01-03 2015-01-09 1        1
3  2015-01-27 2015-01-31 1        1

我期待结果显示当时未取消记录的间隔时间仍未取消:

Id From       To
1  2015-01-01 2015-01-02
1  2015-01-10 2015-01-26

我可以将每条记录拆分为日期,然后从记录中减去已取消的日期,然后合并间隔,但由于我有很多记录,我发现这非常低效并且我很确定我我忽略了一些简单的事情。

您想完成的任务很重要。一个可能的解决方案是将所有 From / To 日期置于 有序序列 中。以下UNPIVOT操作:

SELECT ID, EventDate, StartStop, 
            ROW_NUMBER() OVER (ORDER BY ID, EventDate, StartStop) AS EventRowNum,
            IsCancel                         
FROM
    (SELECT ID, IsCancel, [From], [To]
     FROM Event) Src
UNPIVOT (
     EventDate FOR StartStop IN ([From], [To])
) AS Unpvt 

生成此结果集:

    ID  EventDate   StartStop   EventRowNum IsCancel
   --------------------------------------------------
    1   2015-01-01  From        1           0
    2   2015-01-03  From        2           1
    2   2015-01-09  To          3           1
    3   2015-01-27  From        4           1
    3   2015-01-31  To          5           1
    1   2015-01-31  To          6           0

使用 CTE,您可以随后模拟 LEAD 函数(可从 SQL Server 2012 起使用)以便将当前日期和下一个日期放在一条记录中上面的序列:

;WITH StretchEventDates AS 
( 
    -- above query goes here
), CTE AS
(
   SELECT s.ID, s.EventDate, s.StartStop, s.IsCancel,
          sLead.EventDate As LeadEventDate, sLead.StartStop AS LeadStartStop, sLead.IsCancel AS LeadIsCancel
   FROM StretchEventDates AS s
   LEFT JOIN StretchEventDates AS sLead ON s.EventRowNum + 1 = sLead.EventRowNum
)

以上生成以下结果集:

    ID  EventDate   StartStop   IsCancel    LeadEventDate   LeadStartStop   LeadIsCancel
   --------------------------------------------------------------------------------------
    1   2015-01-01  From        0           2015-01-03      From            1
    2   2015-01-03  From        1           2015-01-09      To              1
    2   2015-01-09  To          1           2015-01-27      From            1
    3   2015-01-27  From        1           2015-01-31      To              1
    3   2015-01-31  To          1           2015-01-31      To              0
    1   2015-01-31  To          0           NULL            NULL            NULL

使用 CASE 语句,您可以过滤这些记录以获得所需的输出。

综合起来:

;WITH StretchEventDates AS 
( 
    SELECT ID, EventDate, StartStop, 
            ROW_NUMBER() OVER (ORDER BY EventDate, StartStop) AS EventRowNum,
            IsCancel                         
    FROM
        (SELECT ID, IsCancel, [From], [To]
        FROM Event) Src
    UNPIVOT (
        EventDate FOR StartStop IN ([From], [To])
    ) AS Unpvt
), CTE AS
(
   SELECT s.ID, s.EventDate, s.StartStop, s.IsCancel,
          sLead.EventDate As LeadEventDate, sLead.StartStop AS LeadStartStop, sLead.IsCancel AS LeadIsCancel
   FROM StretchEventDates AS s
   LEFT JOIN StretchEventDates AS sLead ON s.EventRowNum + 1 = sLead.EventRowNum
), CTE_FINAL AS
(SELECT *,
       CASE WHEN StartStop = 'From' AND IsCancel = 0 THEN EventDate
            WHEN StartStop = 'To' AND IsCancel = 1 THEN DATEADD(d, 1, EventDate)
       END AS [From],
       CASE WHEN LeadStartStop = 'From' AND LeadIsCancel = 1 THEN DATEADD(d, -1, LeadEventDate)
            WHEN LeadStartStop = 'To' AND LeadIsCancel = 0 THEN LeadEventDate
       END AS [To]
FROM CTE
)
SELECT ID, [From], [To]
FROM CTE_FINAL
WHERE [From] IS NOT NULL AND [To] IS NOT NULL AND [From] <= [To]

您可能需要在上述查询中添加额外的 CASEs 以处理 'non-canceled' 事件之后 'cancelations' 的额外组合(反之亦然)。

根据 OP 中提供的数据,以上内容产生以下输出:

ID  From    To
---------------------------
1   2015-01-01  2015-01-02
2   2015-01-10  2015-01-26