Sql 方法),但从循环到基于集合的逻辑的改变的好处远远大于成本
以下是部分解决方案(无法处理不同的日期):Sql 方法),但从循环到基于集合的逻辑的改变的好处远远大于成本,sql,sql-server,tsql,cursor,Sql,Sql Server,Tsql,Cursor,以下是部分解决方案(无法处理不同的日期): 您可以使用公共表表达式(CTE)或数字表生成日期和小时的列表,然后汇总每个小时消耗的资源。将时段汇总到四分之一小时窗口的相关答案如下。无论如何,事实并非如此。提示:使用软件和版本标记数据库问题,例如,sql-server-2012,因为功能不同。@HABO注意,递归CTE使用隐藏的RBAR。这是一篇由Jeff Moden本人撰写的文章,他是RBAR的创始人,旨在获取更多信息!这对样本数据集起作用,看起来要快得多。 Call_ID StartDate
您可以使用公共表表达式(CTE)或数字表生成日期和小时的列表,然后汇总每个小时消耗的资源。将时段汇总到四分之一小时窗口的相关答案如下。无论如何,事实并非如此。提示:使用软件和版本标记数据库问题,例如,
sql-server-2012
,因为功能不同。@HABO注意,递归CTE使用隐藏的RBAR。这是一篇由Jeff Moden本人撰写的文章,他是RBAR的创始人,旨在获取更多信息!这对样本数据集起作用,看起来要快得多。
Call_ID StartDateTime EndDateTime
1 3/15/2014 11:25 3/15/2014 14:01
2 3/15/2014 11:50 3/15/2014 13:10
3 3/15/2014 12:05 3/15/2014 12:55
4 3/15/2014 13:04 3/15/2014 15:02
5 3/15/2014 13:15 3/15/2014 14:22
6 3/15/2014 14:35 3/15/2014 15:18
7 3/15/2014 15:10 3/15/2014 15:29
Date Hour CallMinutes
3/15/2014 11 45
3/15/2014 12 170
3/15/2014 13 171
3/15/2014 14 108
3/15/2014 15 39
/* create & fill the Inputs table */
CREATE TABLE PhoneCalls(
Call_ID int IDENTITY(1,1) NOT NULL,
StartDateTime datetime NOT NULL,
EndDateTime datetime NOT NULL,
CONSTRAINT PK_PhoneCalls PRIMARY KEY CLUSTERED ( Call_ID ASC) ON [PRIMARY]
) ON [PRIMARY];
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 11:25', '3/15/2014 14:01');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 11:50', '3/15/2014 13:10');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 12:05','3/15/2014 12:55');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 13:04','3/15/2014 15:02');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 13:15','3/15/2014 14:22');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 14:35','3/15/2014 15:18');
INSERT INTO PhoneCalls (StartDateTime, EndDateTime) VALUES ('3/15/2014 15:10','3/15/2014 15:29');
/* Create the Temp table to hold the intermediate results */
Create Table #HourlyCallTimes (
Call_ID int NOT NULL,
CallDate date NOT NULL,
CallHour smallint NOT NULL,
CallTime int NOT NULL);
/* Determine Hourly totals of CallTime using a cursor */
SET NOCOUNT ON;
DECLARE @Call_ID int, @StartDt datetime, @EndDt datetime, @CallLength int, @Hour int, @CallTime int;
declare CallsCursor cursor for
select Call_ID, StartDateTime, EndDateTime, DATEDIFF(minute, StartDateTime, EndDateTime) as CallLength
FROM PhoneCalls
open CallsCursor
fetch next from CallsCursor into @Call_ID, @StartDt, @EndDt, @CallLength
while @@FETCH_STATUS = 0
begin
/* make sure that the call was at least a minute long */
IF (@CallLength > 0.01)
BEGIN
-- Record the call time for the first partial hour
SET @Hour = DATEPART(Hour, @StartDt)
SET @CallTime = 60 - DATEPART(Minute, @StartDt)
if (@CallLength < @CallTime) -- check if this us the last (partial) hour
SET @CallTime = @CallLength
INSERT INTO #HourlyCallTimes (Call_ID, CallDate, CallHour, CallTime)
VALUES (@Call_ID, CAST(@StartDt as DATE), @Hour, @CallTime)
-- move to the beginning of the next hour
SET @StartDt = DATEADD(HOUR,@Hour+1,CAST(CAST(@StartDt as DATE) as DateTime))
SET @CallLength = @CallLength - @CallTime
While @StartDt < @EndDt
BEGIN
SET @Hour = DATEPART(Hour, @StartDt)
SET @CallTime = 60;
if (@CallLength < @CallTime) -- check if this is the last (partial) hour
SET @CallTime = @CallLength
INSERT INTO #HourlyCallTimes (Call_ID, CallDate, CallHour, CallTime)
VALUES (@Call_ID, CAST(@StartDt as DATE), @Hour, @CallTime)
-- move to the beginning of the next hour
SET @StartDt = DATEADD(Hour,1,@StartDt)
SET @CallLength = @CallLength - @CallTime
END
END
fetch next from CallsCursor into @Call_ID, @StartDt, @EndDt, @CallLength
END
close CallsCursor;
deallocate CallsCursor;
/* here's the final results table */
Create Table HourlyCallTotals (
CallDate date NOT NULL,
CallHour smallint NOT NULL,
CallTime int NOT NULL);
Insert into HourlyCallTotals (CallDate, CallHour, CallTime)
select CallDate, CallHour, SUM(CallTime) from #HourlyCallTimes group by CallDate, CallHour;
DROP TABLE #HourlyCallTimes;
DROP TABLE PhoneCalls;
SELECT * FROM HourlyCallTotals;
DROP TABLE HourlyCallTotals;
/* expected Results */
/* CallDate CallHour CallTime
3/15/2014 11 45
3/15/2014 12 170
3/15/2014 13 171
3/15/2014 14 108
3/15/2014 15 39 */
;WITH cte_break AS
(
SELECT
CONVERT(DATE,StartDateTime) AS CallDate,
DATEPART(HOUR,StartDateTime) AS CallHour,
CASE WHEN DATEDIFF(MINUTE,StartDateTime, EndDateTime) < 60 AND DATEPART(HOUR, StartDateTime) = DATEPART(HOUR, EndDateTime) THEN DATEDIFF(MINUTE,StartDateTime, EndDateTime)
ELSE (60 - DATEPART(MINUTE,StartDateTime)) END AS CallTime,
DATEADD(minute, CASE WHEN DATEDIFF(MINUTE,StartDateTime, EndDateTime) < 60 THEN DATEDIFF(MINUTE,StartDateTime, EndDateTime) ELSE (60 - DATEPART(MINUTE,StartDateTime)) END , StartDateTime) AS CalcStartTime,
EndDateTime
FROM dbo.PhoneCalls
UNION ALL
SELECT
CONVERT(DATE,CalcStartTime) AS CallDate,
DATEPART(HOUR,CalcStartTime) AS CallHour,
CASE WHEN DATEDIFF(MINUTE,CalcStartTime, EndDateTime) < 60 AND DATEPART(HOUR, CalcStartTime) = DATEPART(HOUR, cte_break.EndDateTime) THEN DATEDIFF(MINUTE,CalcStartTime, EndDateTime)
ELSE (60 - DATEPART(MINUTE,CalcStartTime)) END AS CallTime,
DATEADD(minute, CASE WHEN DATEDIFF(MINUTE,CalcStartTime, EndDateTime) < 60 THEN DATEDIFF(MINUTE,CalcStartTime, EndDateTime) ELSE (60 - DATEPART(MINUTE,CalcStartTime)) END , CalcStartTime) AS CalcStartTime,
EndDateTime
FROM cte_break
WHERE CalcStartTime < EndDateTime
)
SELECT CallDate, CallHour, SUM(CallTime) AS CallTime FROM cte_break
GROUP BY CallDate, CallHour
ORDER BY CallDate, CallHour
ID Hour0 Hour1 Hour2 Hour3 ...
1 0 0 4 0
2 0 0 3 0
3 0 1 2 0
...
DECLARE @maxHour INT, @minHour INT, @disHours INT
SELECT @minHour = MIN(DATEPART(HOUR, StartDateTime)),
@maxHour = MAX(DATEPART(HOUR, EndDateTime)),
@disHours = @maxHour - @minHour + 1
FROM PhoneCalls
-- Filling out a range from @minHour to @maxHour
SELECT TOP (@disHours) @minHour -1 + ROW_NUMBER() OVER (ORDER BY [object_id]) AS CallHour
INTO #hours
FROM sys.all_objects
ORDER BY CallHour
-- Count the per call per hour call time
SELECT CallHour,
CASE
WHEN DATEPART(HOUR, StartDateTime) < CallHour AND DATEPART(HOUR, EndDateTime) > CallHour THEN 60
WHEN DATEPART(HOUR, StartDateTime) = CallHour AND DATEPART(HOUR, EndDateTime) = CallHour THEN DATEPART(MINUTE, EndDateTime) - DATEPART(MINUTE, StartDateTime)
WHEN DATEPART(HOUR, StartDateTime) < CallHour AND DATEPART(HOUR, EndDateTime) = CallHour THEN DATEPART(MINUTE, EndDateTime)
WHEN DATEPART(HOUR, StartDateTime) = CallHour AND DATEPART(HOUR, EndDateTime) > CallHour THEN 60 - DATEPART(MINUTE, StartDateTime)
ELSE 0
END AS CallTimePerCallPerHour
INTO #cross
FROM PhoneCalls
CROSS JOIN #hours
-- The final result
SELECT CallHour,
SUM(CallTimePerCallPerHour) AS CallTime
FROM #cross
WHERE CallTimePerCallPerHour > 0 -- To left out hours with 0 CallTime in the result
GROUP BY CallHour
DROP TABLE #hours
DROP TABLE #cross
3/15/2016 23:49 - 3/16/2016 00:05 =>
3/15/2016 23:49 - 3/15/2016 23:60 // instead of 24:00 so we don't need to count hour 24
3/16/2016 00:00 - 3/16/2016 00:05