SQL查询按间隔拆分记录

时间:2015-01-13 14:29:51

标签: sql-server sql-server-2008-r2 intervals

假设我有一个表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

我可以这样做,它会将每条记录分成日期,然后从记录中减去取消的日期,然后合并间隔,但由于我有很多记录,我发现这非常低效,我很确定我可以忽略简单的事情。

1 个答案:

答案 0 :(得分:1)

您想要实现的任务非常重要。可能的解决方案是将所有 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来处理“取消”的其他组合&#39;以下&#39;未取消&#39; (反之亦然)事件。

使用OP中提供的数据,上面产生以下输出:

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