Deze query doet het werk ook. De prestaties zijn erg goed (hoewel het uitvoeringsplan er niet zo geweldig uitziet, verslaan de eigenlijke CPU en IO vele andere vragen).
Zie hoe het werkt in een Sql Fiddle .
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
;
Met een geclusterde index op WorkerID, JobStart, JobEnd, JobID
, en met het voorbeeld van 7 rijen uit de bovenstaande viool, een sjabloon voor nieuwe werknemer-/taakgegevens die vaak genoeg worden herhaald om een tabel met 14.336 rijen op te leveren, zijn hier de prestatieresultaten. Ik heb de andere werkende/goede antwoorden op de pagina toegevoegd (tot nu toe):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Ik deed een uitgebreidere test vanaf een andere (langzamere) server (waar elke query 25 keer werd uitgevoerd, de beste en slechtste waarden voor elke statistiek werden weggegooid en de resterende 23 waarden werden gemiddeld) en kreeg het volgende:
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
De alternatieve techniek dacht ik zeker om dingen te verbeteren. Nou, het heeft 6 keer gelezen, maar kost veel meer CPU (wat logisch is). In plaats van de begin-/eindstatistieken van elke tijdslice tot het einde door te voeren, kunt u het beste gewoon opnieuw berekenen welke segmenten u wilt behouden met de EXISTS
tegen de originele gegevens. Het kan zijn dat een ander profiel van enkele werknemers met veel banen de prestatiestatistieken voor verschillende zoekopdrachten kan veranderen.
Als iemand het wil proberen, gebruik dan de CREATE TABLE
en INSERT
uitspraken van mijn viool en voer dit dan 11 keer uit:
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
;
Ik heb twee andere oplossingen voor deze vraag gebouwd, maar de beste met ongeveer het dubbele van de prestaties had een fatale fout (niet correct omgaan met volledig ingesloten tijdbereiken). De andere had zeer hoge/slechte statistieken (wat ik wist maar moest proberen).
Uitleg
Gebruik alle eindpunttijden van elke rij en bouw een duidelijke lijst op van alle mogelijke tijdbereiken van belang door elke eindpunttijd te dupliceren en vervolgens zo te groeperen dat elke keer wordt gekoppeld aan de volgende mogelijke tijd. Tel de verstreken minuten van deze bereiken bij elkaar op, waar ze ook samenvallen met de werkelijke werktijd van een werknemer.