这就是我解决问题的方式。
我要做的第一件事是创建一个如下所示的序列表。由于各种原因,在 SQL 中拥有一个基本上无限(或至少是大)的数字序列可能非常有用。像这样的东西:
create table dbo.sequence
(
seq_no int not null primary key clustered ,
)
declare @v int
set @v = -100000
while @v <= 100000
begin
insert dbo.sequence values ( @v )
set @v = @v+1
end
实际上,我会使用批量复制以不同的方式为表设置种子,甚至编写一个 CLR 表值函数来生成所需的范围。上述查询将......在加载表时不会表现出理想的性能特征。
一旦我有了类似的东西,我会写一个如下的查询。它将为您提供一份完整的报告,列出指定报告期内每天所需的每个报告桶。通过设置适当的变量,一切都可以调整。如果您想要稀疏报告,请将 final 更改left join
为标准内部联接。
免责声明:此代码尚未经过测试,但它类似于我为执行相同操作而编写的代码。这种方法是合理的,尽管代码本身很可能包含错误。
-----------------------------------------------------------------------------
-- define the range of days in which we are interested
-- it might well be more than 1, but for this example, we'll define the start
-- and end days as the same, so we are interested in just one day.
-----------------------------------------------------------------------------
declare @dtFrom datetime
declare @dtThru datetime
set @dateFrom = '2012-06-01'
set @dateThru = '2012-06-01'
------------------------------------------------------------------------------
-- the next thing in which we are interested in are the boundaries of
-- the time period in which we are interested, and the interval length
-- of each reporting bucket, in minutes.
--
-- For this example, we're interesting in the time period
-- running from 6am through 9pm, such that 6am >= x < 9pm.
--
-- We also need a value defining start-of-day (midnight).
--
-- Setting a datetime value to '' will give you the epoch: 1900-01-01 00:00:00.000
-- Setting a datetime value to just a time-of-day string literal will
-- give you the epoch day at the desired time, so '06:00:00' converts to
-- '1900-01-01 06:00:00'. Crazy, but that's SQL Server.
--
------------------------------------------------------------------------------
declare @start_of_day datetime
declare @timeFrom datetime
declare @timeThru datetime
declare @interval_length_in_minutes int
set @start_of_day = '00:00:00'
set @timeFrom = '06:00:00'
set @timeThru = '21:00:00'
set @interval_length_in_minutes = 15
------------------------------------------------------------------------------
--
-- On to the meat of the matter. This query has three parts to it.
--
-- 1. Generate the set of reporting days, using our sequence table
-- 2. Generate the set of reporting buckets for each day, again, using our sequence table
--
-- The left join of these two virtual tables produces the set of all reporting periods
-- that we will use to match up to the source data that will fill the report.
--
-- 3. Finally, assign each row to 0 or more reporting buckets.
-- A given record has a time range in which it was 'active'.
-- Consequently, it may fall into multiple reporting buckets, and hence,
-- the comparison is a little wonky: A record is assigned to a reporting bucket
-- if both of these are true for the data record:
--
-- * Its active period ended *on or after* the start of the reporting period/bucket.
-- * Its active period began *on or before* the end of the reporting period.
--
-- It take a while to get your head around that, but it works.
--
-- When all that is in place, we use GROUP BY and the aggregate function SUM()
-- to collapse each reporting bucket into a single row and compute the active count.
-- We use SUM() in preference to COUNT() as we want a full report,
-- so we use left joins. Unlike other aggregate functions, COUNT() does not
-- exclude null rows/expressions in its computation.
--
-- There you go. Easy!
--
-----------------------------------------------------------------------------------
select timeFrom = dateadd(minute, times.offset , days.now ) ,
timeThru = dateadd(minute, times.offset + @interval_length_in_minutes , days.now ) ,
N = sum( case when t.id is null then 0 else 1 end ) -- we sum() here rather than count() since we don't want missing rows from dbo.myFunkyTable to increment the count
from ( select now = dateadd(day, seq_no , @dateFrom ) -- get the set of 'interesting' days
from dbo.sequence -- via our sequence table
where seq_no >= 0 --
and seq_no < datediff(day,@dateFrom,@dateThru) --
) days --
left join ( select offset = seq_no -- get the set of time buckets
from dbo.sequence -- each bucket is defined by its offset
where seq_no >= datediff(minute,@start_of_day,@timeFrom) -- as defined in minutes-since-start-of-day
and seq_no < datediff(minute,@start_of_day,@timeThru) -- and is @interval_length_in_minuts long
and 0 = seq_no % @interval_length_in_minutes --
) times
left join dbo.myFunkyTable t on t.Part_StartTime < dateadd(minute, times.offset + @interval_length_in_minutes , days.now )
and t.Part_EndTime >= dateadd(minute, times.offset , days.now )
group by dateadd(minute, times.offset , days.now ) ,
dateadd(minute, times.offset + @interval_length_in_minutes , days.now )
order by 1 , 2