我认为这在适应所有时间框架方面做得很好,即使签入间隔不均匀。另外,我认为您的示例中有错误;在您的加权平均值中,房间 2 的最后一个值是“4”而不是“7”。
设置:
if object_id(N'avgTbl', N'U') is not null
drop table avgTbl;
create table avgTbl (
UserId int not null,
RoomName nvarchar(10) not null,
CheckInTime datetime not null,
UserCount int not null,
constraint pk_avgTbl primary key (UserId, RoomName, CheckInTime)
);
insert into avgTbl (UserId, RoomName, CheckInTime, UserCount) values
(4, 'Room 4', '2012-08-03 14:00:00', 3),
(2, 'Room 2', '2012-08-03 14:00:00', 3),
(3, 'Room 3', '2012-08-03 14:00:00', 1),
(1, 'Room 1', '2012-08-03 14:00:00', 2),
(3, 'Room 3', '2012-08-03 14:15:00', 1),
(2, 'Room 2', '2012-08-03 14:15:00', 4),
(1, 'Room 1', '2012-08-03 14:15:00', 3),
(1, 'Room 1', '2012-08-03 14:30:00', 6),
(1, 'Room 1', '2012-08-03 14:45:00', 3),
(2, 'Room 2', '2012-08-03 14:45:00', 7),
(3, 'Room 3', '2012-08-03 14:45:00', 8),
(4, 'Room 4', '2012-08-03 14:45:00', 4);
查询:
/*
* You just need to enter the start and end times below.
* They can be any intervals, as long as the start time is
* before the end time.
*/
declare
@startTime datetime = '2012-08-03 14:00:00',
@endTime datetime = '2012-08-03 15:00:00';
declare
@totalTime numeric(18,1) = datediff(MINUTE, @startTime, @endTime);
/*
* This orders the observations, and assigns a sequential number so we can
*join on it later.
*/
with diffs as (
select
row_number() over (order by RoomName, CheckInTime) as RowNum,
CheckInTime,
UserCount,
RoomName
from avgTbl
),
/*
* Get the time periods,
* calc the number of minutes,
* divide by the total minutes in the period,
* multiply by the UserCount to get the weighted value,
* sum the weighted values to get the weighted avg.
*/
mins as (
select
cur.RoomName,
/*
* If we do not have an observation for a given room, use "0" instead
* of "null", so it does not affect calculations later.
*/
case
when prv.UserCount is null then 0
else prv.UserCount
end as UserCount,
/* The current observation time. */
cur.CheckInTime as CurrentT,
/* The prior observation time. */
prv.CheckInTime as PrevT,
/*
* The difference in minutes between the current, and previous qbservation
* times. If it is the first observation, then use the @startTime as the
* previous observation time. If the current time is null, then use the
* end time.
*/
datediff(MINUTE,
case
when prv.CheckInTime is null then @startTime
else prv.CheckInTime
end,
case
when cur.CheckInTime is null then @endTime
else cur.CheckInTime
end) as Mins
from diffs as cur
/*
* Join the observations based on the row numbers. This gets the current,
* and previous observations together in the same record, so we can
* perform our calculations.
*/
left outer join diffs as prv on cur.RowNum = prv.RowNum + 1
and cur.RoomName = prv.RoomName
union
/*
* Add the end date as a period end, assume that the user count is the same
* as the last observation.
*/
select
d.RoomName,
d.UserCount,
@endTime,
d.CheckInTime, -- The last recorded observation time.
datediff(MINUTE, d.CheckInTime, @endTime) as Mins
from diffs as d
where d.RowNum in (
select MAX(d2.RowNum)
from diffs as d2
where d2.RoomName = d.RoomName
)
group by d.RoomName, d.CheckInTime, d.UserCount
)
/* Now we just need to get our weighted average calculations. */
select
m.RoomName,
count(1) - 1 as NumOfObservations,
/*
* m.Min = minutes during which "UserCount" is the active number.
* @totalTime = total minutes between start and end.
* m.Min / @totalTime = the % of the total time.
* (m.Min / @totalTime) * UserCount = The weighted value.
* sum(..above..) = The total weighted average across the observations.
*/
sum((m.Mins/@totalTime) * m.UserCount) as WgtAvg
from mins as m
group by m.RoomName
order by m.RoomName;