以下显示了使用 Tally 表(又名“数字表”表)的一种方法。
我做出的主要假设:
- 开始时间和结束时间都是已知的
- 开始和结束时间是精确的时间(例如 1:00、2:00,而不是 13:23 或 00:00.00.007)。 (这就是我使用 smalldatetime 数据类型的原因)
- 结束时间总是大于开始时间
我进一步假设数据存储在多个实体的表中 - 也就是说,您最终希望在一个查询中处理多个项目。如果您一次只想对一个项目执行此操作,(a) 下面的查询可以很容易地被砍掉,并且 (b) 在 C# 或任何调用语言中可能会更容易。
设置测试数据:
-- DROP TABLE Testing
CREATE TABLE Testing
(
EntryId int not null
,StartTime smalldatetime not null
,EndTime smalldatetime not null
)
INSERT Testing values
(1, 'Aug 12, 2014 17:00', 'Aug 14, 2014 12:00') -- Original problem
,(2, 'Aug 11, 2014 00:00', 'Aug 11, 2014 23:00') -- 23 hours
,(3, 'Aug 11, 2014 00:00', 'Aug 12, 2014 00:00') -- 24 hour shift
,(4, 'Aug 11, 2014 12:00', 'Aug 12, 2014 12:00') -- Noon to Noon
,(11, 'Aug 22, 2014 4:00', 'Aug 22, 2014 5:00') -- One-hour problem cases
,(12, 'Aug 22, 2014 10:00', 'Aug 22, 2014 11:00') -- One-hour problem cases
,(13, 'Aug 22, 2014 11:00', 'Aug 22, 2014 12:00') -- One-hour problem cases
,(14, 'Aug 22, 2014 12:00', 'Aug 22, 2014 13:00') -- One-hour problem cases
,(21, 'Aug 23, 2014 18:00', 'Aug 23, 2014 19:00') -- One-hour problem cases
,(22, 'Aug 23, 2014 22:00', 'Aug 23, 2014 23:00') -- One-hour problem cases
,(23, 'Aug 23, 2014 23:00', 'Aug 24, 2014 00:00') -- One-hour problem cases
,(24, 'Aug 24, 2014 00:00', 'Aug 24, 2014 1:00') -- One-hour problem cases
我的日常:
DECLARE
@Earliest smalldatetime
,@Latest smalldatetime
-- This could be thrown in as a first CTE, but doing so would make the overall query that much less comprehensible.
SELECT
@Earliest = min(StartTime)
,@Latest = max(EndTime)
from Testing
--where <filtering criteria, if you're not parsing the whole table)
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Tally as (select row_number() over(order by C) as Number from Pass4),
DateRange as (select
dateadd(hh, ta.Number, @Earliest) ShiftHour
from Tally ta
where dateadd(hh, ta.Number, @Earliest) <= @Latest)
SELECT
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date) [Date]
,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 1 else 0 end) [11:00 to 23:00]
,sum(case when datepart(hh, dateadd(hh, -1, dr.Shifthour)) between 11 and 22 then 0 else 1 end) [23:00 to 11:00]
from Testing te
inner join DateRange dr
on dr.ShiftHour > te.StartTime
and dr.ShiftHour <= te.Endtime
group by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
order by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
将其插入以显示没有分组的结果,这对于弄清楚其中发生了什么非常宝贵:
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Tally as (select row_number() over(order by C) as Number from Pass4),
DateRange as (select
dateadd(hh, ta.Number, @Earliest) ShiftHour
from Tally ta
where dateadd(hh, ta.Number, @Earliest) <= @Latest)
SELECT
te.EntryId
,dateadd(hh, -1, dr.Shifthour)
,cast(dateadd(hh, -1, dr.Shifthour) as date) [Date]
,datepart(hh, dateadd(hh, -1, dr.Shifthour))
from Testing te
inner join DateRange dr
on dr.ShiftHour > te.StartTime
and dr.ShiftHour <= te.Endtime
order by
te.EntryId
,cast(dateadd(hh, -1, dr.Shifthour) as date)
,dateadd(hh, -1, dr.Shifthour)
困难的部分是:
- 将第 24 小时(8 月 23 日,00:00)获取到前一天(8 月 24 日)。这就是为什么我把所有的东西都往后移一天 [编辑] 一小时
-
< 和 > 处理多个条目时的逻辑
- 接受“一天中午到中午”样本的结果看起来很傻的事实。
是的,这可能是过度设计,但有时您只需要尝试一下即可。我不能说它在大型数据集上的表现如何,如果您的第一次和最后一次日期相隔超过四年,您需要在构建计数表时添加“Pass5”。