Den här frågan gör jobbet också. Dess prestanda är mycket bra (medan exekveringsplanen inte ser så bra ut, slår den faktiska CPU:n och IO många andra frågor).
Se hur det fungerar i en Sql-fiol .
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
;
Med ett klustrat index på WorkerID, JobStart, JobEnd, JobID
, och med provet 7 rader från ovanstående fiol en mall för nya arbetar-/jobbdata som upprepas tillräckligt många gånger för att ge en tabell med 14 336 rader, här är resultatresultaten. Jag har inkluderat de andra fungerande/rätta svaren på sidan (hittills):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Jag gjorde ett mer uttömmande test från en annan (långsammare) server (där varje fråga kördes 25 gånger, de bästa och sämsta värdena för varje mätvärde kastades ut och de återstående 23 värdena beräknades) och fick följande:
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
Den alternativa tekniken tänkte jag vara säker på att förbättra saker och ting. Tja, det sparade 6 läsningar, men kostade mycket mer CPU (vilket är vettigt). Istället för att föra igenom start-/slutstatistiken för varje tidssegment till slutet, är det bäst att bara räkna om vilka segment som ska behållas med EXISTS
mot de ursprungliga uppgifterna. Det kan vara så att en annan profil av få arbetare med många jobb kan förändra prestationsstatistiken för olika frågor.
Om någon vill prova, använd CREATE TABLE
och INSERT
uttalanden från min fiol och kör sedan detta 11 gånger:
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
;
Jag byggde två andra lösningar för den här frågan, men den bästa med ungefär dubbelt så hög prestanda hade ett fatalt fel (hanterade inte helt slutna tidsintervall korrekt). Den andra hade väldigt hög/dålig statistik (vilket jag visste men var tvungen att prova).
Förklaring
Använd alla slutpunktstider från varje rad, bygg upp en distinkt lista över alla möjliga tidsintervall av intresse genom att duplicera varje slutpunktstid och sedan gruppera på ett sådant sätt att varje gång paras ihop med nästa möjliga tid. Summera de förflutna minuterna av dessa intervall var de än sammanfaller med en faktisk arbetares arbetstid.