チェックインの間隔が均等でなくても、これはすべての時間枠に対応するのにかなり良い仕事だと思います。また、あなたの例には誤りがあると思います。加重平均では、部屋2の最後の値は「7」ではなく「4」です。
セットアップ:
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;