我搜索日夜回来时,我第一次开始了在SQL世界的回答这个问题。 我的需求找不到类似这样的东西,所以我决定提出和回答的情况下,其他人我自己的问题需要帮助像我一样。
这里是我拥有的数据的一个例子。 为简单起见,它是一切从工作表。 每个作业ID都有它自己的开始和结束时间基本上是随机的,可以重叠,有差距,启动并在同一时间,其他工作等结束
--Available--
JobID WorkerID JobStart JobEnd
1 25 '2012-11-17 16:00' '2012-11-17 17:00'
2 25 '2012-11-18 16:00' '2012-11-18 16:50'
3 25 '2012-11-19 18:00' '2012-11-19 18:30'
4 25 '2012-11-19 17:30' '2012-11-19 18:10'
5 26 '2012-11-18 16:00' '2012-11-18 17:10'
6 26 '2012-11-19 16:00' '2012-11-19 16:50'
我想查询显示会的结果:
WorkerID TotalTime(in Mins)
25 170
26 120
编辑:忘了提及的是,重叠需要被忽略。 基本上,这是应该把这些工人和他们的工作,就像您的小时工,而不是承包商。 就像如果我工作两年jobIDs并开始和完成他们俩从下午12:00至下午12:30,作为员工,我只会得到报酬30分钟,而承包商可能会得到报酬60分钟,因为他们的工作被单独处理,并每工作得到报酬。 此查询的一点是要分析那些绑在工人数据库工作,需要找出如果这名工人作为员工,你会在一个给定的时间出来是工作他总小时处理。
EDIT2:不会让我回答我的问题7小时,将它那里以后移动。
好了,现在答题。 基本上,我使用临时表来构建分钟,我期待了工作的最大日期时间之间的每一分钟。
IF OBJECT_ID('tempdb..#time') IS NOT NULL
BEGIN
drop table #time
END
DECLARE @FromDate AS DATETIME,
@ToDate AS DATETIME,
@Current AS DATETIME
SET @FromDate = '2012-11-17 16:00'
SET @ToDate = '2012-11-19 18:30'
create table #time (cte_start_date datetime)
set @current = @FromDate
while (@current < @ToDate)
begin
insert into #time (cte_start_date)
values (@current)
set @current = DATEADD(n, 1, @current)
end
现在我有一个临时表中的所有分钟。 现在我需要参加所有的工作表信息到它,选择了什么,我需要一气呵成。
SELECT J.WorkerID
,COUNT(DISTINCT t.cte_start_date) AS TotalTime
FROM #time AS t
INNER JOIN Job AS J ON t.cte_start_date >= J.JobStart AND t.cte_start_date < J.JobEnd --Thanks ErikE
GROUP BY J.WorkerID --Thanks Martin Parkin
drop table #time
这是非常简单的答案,好让别人开始。
这个查询做这项工作为好。 它的性能非常好(当执行计划看上去没有那么大,实际的CPU和IO击败众多其他查询)。
看到它在SQL拨弄工作 。
WITH Times AS (
SELECT DISTINCT
H.WorkerID,
T.Boundary
FROM
dbo.JobHistory H
CROSS APPLY (VALUES (H.JobStart), (H.JobEnd)) T (Boundary)
), Groups AS (
SELECT
WorkerID,
T.Boundary,
Grp = Row_Number() OVER (PARTITION BY T.WorkerID ORDER BY T.Boundary) / 2
FROM
Times T
CROSS JOIN (VALUES (1), (1)) X (Dup)
), Boundaries AS (
SELECT
G.WorkerID,
TimeStart = Min(Boundary),
TimeEnd = Max(Boundary)
FROM
Groups G
GROUP BY
G.WorkerID,
G.Grp
HAVING
Count(*) = 2
)
SELECT
B.WorkerID,
WorkedMinutes = Sum(DateDiff(minute, 0, B.TimeEnd - B.TimeStart))
FROM
Boundaries B
WHERE
EXISTS (
SELECT *
FROM dbo.JobHistory H
WHERE
B.WorkerID = H.WorkerID
AND B.TimeStart < H.JobEnd
AND B.TimeEnd > H.JobStart
)
GROUP BY
WorkerID
;
随着聚集索引WorkerID, JobStart, JobEnd, JobID
,并与样品从上面的小提琴重复足够的时间,以产生一个表行14,336新工人/就业数据的模板7行,这里的性能结果。 我已经包含了网页(到目前为止)对其他工作/正确答案:
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
我也从不同的(慢)服务器(其中每个查询已运行25次,每个指标的最佳和最差值被抛出,而剩余的23个值的平均值)更详尽的测试,得到了以下几点:
Query CPU Duration Reads Notes
-------- ---- -------- ------ ----------------------------------
Erik 1 215 231 122 query as above
Erik 2 326 379 116 alternate technique with no EXISTS
Gordon 1 578 682 106847 from j
Gordon 2 584 673 106847 from dbo.JobHistory
备用技术,我认为是一定要提高的东西。 那么,它保存6读取,但耗费了大量的更多的CPU(这是有道理的)。 相反,通过每个时间片到结束的开始/结束统计携带,最好是刚刚重新计算其切片,以保持与EXISTS
对原始数据。 这可能是几个工人与许多就业机会不同的配置文件可以更改不同的查询性能统计数据。
如果有人想尝试一下,使用CREATE TABLE
和INSERT
从我的小提琴语句,然后运行该11次:
INSERT dbo.JobHistory
SELECT
H.JobID + A.MaxJobID,
H.WorkerID + A.WorkerCount,
DateAdd(minute, Elapsed + 45, JobStart),
DateAdd(minute, Elapsed + 45, JobEnd)
FROM
dbo.JobHistory H
CROSS JOIN (
SELECT
MaxJobID = Max(JobID),
WorkerCount = Max(WorkerID) - Min(WorkerID) + 1,
Elapsed = DateDiff(minute, Min(JobStart), Min(JobEnd))
FROM dbo.JobHistory
) A
;
我内置的其他两个解决办法查询,但最好的一个约两倍的性能有一个致命的缺陷(没有正确处理全封闭的时间范围)。 其他具有很高的/糟糕的统计数据(这我知道,但不得不尝试)。
说明
使用的所有端点时间从每行,每一个端点时复制,然后以这样的方式为每次配对的下一个可能的时间分组建立相关的所有时间段的不同列表。 总结这些范围,无论他们与任何实际工作者的工作时间重合的流逝的时间。
查询如下面应该提供您正在寻找的答案:
SELECT WorkerID,
SUM(DATEDIFF(minute, JobStart, JobEnd)) AS TotalTime
FROM Job
GROUP BY WorkerID
道歉,这是未经测试(我没有SQL Server来这里测试),但它应该做的伎俩。
这是一个复杂的查询。 说明如下。
with j as (
select j.*,
(select 1
from jobs j2
where j2.workerid = j.workerid and
j2.starttime < j.endtime and
j2.starttime > j.starttime
) as HasOverlap
from jobs j
)
select workerId,
sum(datediff(minute, periodStart, PeriodEnd)) as NumMinutes
from (select workerId, min(startTime) as periodStart, max(endTime) as PeriodEnd
from (select j.*,
(select min(starttime)
from j j2
where j2.workerid = j.workerid and
j2.starttime >= j.starttime and
j2.HasOverlap is null
) as thegroup
from j
) j
group by workerId, thegroup
) j
group by workerId;
理解这种方法的关键是要了解“重叠”的逻辑。 一个时间段与下一个重叠时,下次启动时间是以前的结束时间之前。 通过指定重叠标志每条记录,我们知道,如果它的“下一个”记录重叠。 上述逻辑是使用的开始时间这一点。 这可能是最好使用的JobId,特别是如果两个作业相同的工人可以在同一时间开始。
重叠标志的计算使用相关子查询(这是j
在with
子句)。
然后,对于每个记录,我们回去,事后发现的第一条记录,其中overlap
值为NULL。 这提供了在给定的重叠集合中的所有记录的分组密钥。
其余的,那么,就是聚合的结果,首先在workerId
/组的水平,然后在workerId
水平得到最终结果。
我还没有运行该SQL,所以它可能有语法错误。
文章来源: Find total time worked with multiple jobs / orders with overlap / overlapping times on each worker and job / order