SQL 查询按间隔拆分记录
SQL query to split records by intervals
假设我有一个 table,它有列 From
和 To
,它们是日期和一个位类型列,它标识它是否是一个取消(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
假设我有一个 table,它有列 From
和 To
,它们是日期和一个位类型列,它标识它是否是一个取消(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