T-SQL,SQL Server 2008及更高版本
给出
的样本表 StatusSetDateTime | UserID | Status | StatusEndDateTime | StatusDuration(in seconds)
============================================================================
2012-01-01 12:00:00 | myID | Available | 2012-01-01 13:00:00 | 3600
我需要将其分解为使用15分钟间隔的视图,例如:
IntervalStart | UserID | Status | Duration
===========================================
2012-01-01 12:00:00 | myID | Available | 900
2012-01-01 12:15:00 | myID | Available | 900
2012-01-01 12:30:00 | myID | Available | 900
2012-01-01 12:45:00 | myID | Available | 900
2012-01-01 13:00:00 | myID | Available | 0
etc....
现在我已经能够搜索并找到一些会破坏的查询 我在MySql Here找到了类似的东西:
适用于T-SQL Here
但是在第二个例子中,他们正在对结果求和,而我需要按用户按状态将总持续时间除以间隔时间(900秒)。
我能够调整第二个链接中的示例,将所有内容拆分为间隔,但返回总持续时间,我无法弄清楚如何将间隔持续时间分割(并且仍然总计为原始持续时间总计) )。
提前感谢任何见解!
编辑:首次尝试
;with cte as
(select MIN(StatusDateTime) as MinDate
, MAX(StatusDateTime) as MaxDate
, convert(varchar(14),StatusDateTime, 120) as StartDate
, DATEPART(minute, StatusDateTime) /15 as GroupID
, UserID
, StatusKey
, avg(StateDuration) as AvgAmount
from AgentActivityLog
group by convert(varchar(14),StatusDateTime, 120)
, DATEPART(minute, StatusDateTime) /15
, Userid,StatusKey)
select dateadd(minute, 15*GroupID, CONVERT(datetime,StartDate+'00'))
as [Start Date]
, UserID, StatusKey, AvgAmount as [Average Amount]
from cte
编辑:第二次尝试
;With cte As
(Select DateAdd(minute
, 15 * (DateDiff(minute, '20000101', StatusDateTime) / 15)
, '20000101') As StatusDateTime
, userid, statuskey, StateDuration
From AgentActivityLog)
Select StatusDateTime, userid,statuskey,Avg(StateDuration)
From cte
Group By StatusDateTime,userid,statuskey;
答案 0 :(得分:4)
;with cte_max as
(
select dateadd(mi, -15, max(StatusEndDateTime)) as EndTime, min(StatusSetDateTime) as StartTime
from AgentActivityLog
), times as
(
select StartTime as Time from cte_max
union all
select dateadd(mi, 15, c.Time)
from times as c
cross join cte_max as cm
where c.Time <= cm.EndTime
)
select
t.Time, A.UserID, A.Status,
case
when t.Time = A.StatusEndDateTime then 0
else A.StatusDuration / (count(*) over (partition by A.StatusSetDateTime, A.UserID, A.Status) - 1)
end as Duration
from AgentActivityLog as A
left outer join times as t on t.Time >= A.StatusSetDateTime and t.Time <= A.StatusEndDateTime
<强> sql fiddle demo 强>
答案 1 :(得分:3)
我从未习惯使用日期数学将事物拆分为分区。似乎存在各种陷阱。
我更喜欢做的是创建一个表(预定义的,表值函数,表变量),其中每个日期分区范围都有一行。表值函数方法特别有用,因为您可以根据需要为任意范围和分区大小构建它。然后,您可以加入此表以分解内容。
paritionid starttime endtime
---------- ------------- -------------
1 8/1/2012 5:00 8/1/2012 5:15
2 8/1/2012 5:15 8/1/2012 5:30
...
我无法谈论此方法的性能,但我发现查询更加直观。
答案 2 :(得分:1)
如果你有一个每15分钟时间戳的帮助表,通过BETWEEN加入你的基表,这是相对简单的。您可以动态构建帮助程序表,也可以将其永久保存在数据库中。对你公司的下一个人来说也很容易理解:
// declare a table and a timestamp variable
declare @timetbl table(t datetime)
declare @t datetime
// set the first timestamp
set @t = '2012-01-01 00:00:00'
// set the last timestamp, can easily be extended to cover many years
while @t <= '2013-01-01'
begin
// populate the table with a new row, every 15 minutes
insert into @timetbl values (@t)
set @t = dateadd(mi, 15, @t)
end
// now the Select query:
select
tt.t, aal.UserID, aal.Status,
case when aal.StatusEndDateTime <= tt.t then 0 else 900 end as Duration
// using a shortcut for Duration, based on your comment that Start/End are always on the quarter-hour, and thus always 900 seconds or zero
from
@timetbl tt
INNER JOIN AgentActivityLog aal
on tt.t between aal.StatusSetDateTime and aal.StatusEndDateTime
order by
aal.UserID, tt.t
答案 3 :(得分:0)
您可以使用recursive Common Table Expression,在StatusEndDateTime大于IntervalStart时继续添加持续时间,例如
;with cte as (
select StatusSetDateTime as IntervalStart
,UserID
,Status
,StatusDuration/(datediff(mi, StatusSetDateTime, StatusEndDateTime)/15) as Duration
, StatusEndDateTime
From AgentActivityLog
Union all
Select DATEADD(ss, Duration, IntervalStart) as IntervalStart
, UserID
, Status
, case when DATEADD(ss, Duration, IntervalStart) = StatusEndDateTime then 0 else Duration end as Duration
, StatusEndDateTime
From cte
Where IntervalStart < StatusEndDateTime
)
select IntervalStart, UserID, Status, Duration from cte
答案 4 :(得分:0)
这是一个不需要帮助表即可为您完成工作的查询。 (我没有反对帮助表,它们很有用,我使用它们。有时候也可能不使用它们。)这个查询允许活动随时开始和结束,即使不是以00结尾的整个分钟, :15,:30,:45。如果有毫秒部分,那么你将不得不做一些实验,因为按照你的模型,我只进行了第二次分辨。
如果您有一个已知的硬最大持续时间,则删除@MaxDuration并将其替换为该值,以分钟为单位。 N <= @MaxDuration
对查询效果至关重要。
DECLARE @MaxDuration int;
SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);
WITH
L0 AS(SELECT 1 c UNION ALL SELECT 1),
L1 AS(SELECT 1 c FROM L0, L0 B),
L2 AS(SELECT 1 c FROM L1, L1 B),
L3 AS(SELECT 1 c FROM L2, L2 B),
L4 AS(SELECT 1 c FROM L3, L3 B),
L5 AS(SELECT 1 c FROM L4, L4 B),
Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
SELECT
S.IntervalStart,
Duration = DateDiff(second, S.IntervalStart, E.IntervalEnd)
FROM
#AgentActivityLog L
CROSS APPLY (
SELECT N, Offset = (N.N - 1) * 900
FROM Nums N
WHERE N <= @MaxDuration
) N
CROSS APPLY (
SELECT Edge =
DateAdd(second, N.Offset, DateAdd(minute,
DateDiff(minute, '20000101', L.StatusSetDateTime)
/ 15 * 15, '20000101')
)
) G
CROSS APPLY (
SELECT IntervalStart = Max(T.BeginTime)
FROM (
SELECT L.StatusSetDateTime
UNION ALL SELECT G.Edge
) T (BeginTime)
) S
CROSS APPLY (
SELECT IntervalEnd = Min(T.EndTime)
FROM (
SELECT L.StatusEndDateTime
UNION ALL SELECT G.Edge + '00:15:00'
) T (EndTime)
) E
WHERE
N.Offset <= L.StatusDuration
ORDER BY
L.StatusSetDateTime,
S.IntervalStart;
如果您想尝试,请使用以下设置脚本:
CREATE TABLE #AgentActivityLog (
StatusSetDateTime datetime,
StatusEndDateTime datetime,
StatusDuration AS (DateDiff(second, 0, StatusEndDateTime - StatusSetDateTime))
);
INSERT #AgentActivityLog -- weird end times
SELECT '20120101 12:00:00', '20120101 13:00:00'
UNION ALL SELECT '20120101 13:00:00', '20120101 13:27:56'
UNION ALL SELECT '20120101 13:27:56', '20120101 13:28:52'
UNION ALL SELECT '20120101 13:28:52', '20120120 11:00:00'
INSERT #AgentActivityLog -- 15-minute quantized end times
SELECT '20120101 12:00:00', '20120101 13:00:00'
UNION ALL SELECT '20120101 13:00:00', '20120101 13:30:00'
UNION ALL SELECT '20120101 13:30:00', '20120101 14:00:00'
UNION ALL SELECT '20120101 14:00:00', '20120120 11:00:00'
此外,这是一个期望只有整个分钟结束时间的版本:00,:15,:30或:45。
DECLARE @MaxDuration int;
SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);
WITH
L0 AS(SELECT 1 c UNION ALL SELECT 1),
L1 AS(SELECT 1 c FROM L0, L0 B),
L2 AS(SELECT 1 c FROM L1, L1 B),
L3 AS(SELECT 1 c FROM L2, L2 B),
L4 AS(SELECT 1 c FROM L3, L3 B),
L5 AS(SELECT 1 c FROM L4, L4 B),
Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
SELECT
S.IntervalStart,
Duration = CASE WHEN Offset = StatusDuration THEN 0 ELSE 900 END
FROM
#AgentActivityLog L
CROSS APPLY (
SELECT N, Offset = (N.N - 1) * 900
FROM Nums N
WHERE N <= @MaxDuration
) N
CROSS APPLY (
SELECT IntervalStart = DateAdd(second, N.Offset, L.StatusSetDateTime)
) S
WHERE
N.Offset <= L.StatusDuration
ORDER BY
L.StatusSetDateTime,
S.IntervalStart;
看起来似乎最后的0 Duration行不正确,因为那时你不能只按IntervalStart排序,因为有重复的IntervalStart值。让行添加0的行有什么好处?