Anche questa query fa il lavoro. Le sue prestazioni sono molto buone (sebbene il piano di esecuzione non sembri così eccezionale, la CPU e l'IO effettivi superano molte altre query).
Guardalo mentre funziona in un 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
;
Con un indice cluster su WorkerID, JobStart, JobEnd, JobID
e con l'esempio di 7 righe di cui sopra, un modello per i dati del nuovo lavoratore/lavoro ripetuto abbastanza volte da produrre una tabella con 14.336 righe, ecco i risultati delle prestazioni. Ho incluso le altre risposte funzionanti/corrette nella pagina (finora):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Ho eseguito un test più completo da un server diverso (più lento) (in cui ogni query è stata eseguita 25 volte, i valori migliori e peggiori per ciascuna metrica sono stati eliminati e la media dei 23 valori rimanenti) e ho ottenuto quanto segue:
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
La tecnica alternativa pensavo per essere sicuro di migliorare le cose. Bene, ha salvato 6 letture, ma è costato molta più CPU (il che ha senso). Invece di eseguire le statistiche di inizio/fine di ogni intervallo di tempo fino alla fine, è meglio semplicemente ricalcolare quali strati mantenere con EXISTS
contro i dati originali. È possibile che un profilo diverso di pochi lavoratori con molti lavori possa modificare le statistiche sulle prestazioni per query diverse.
Nel caso qualcuno volesse provarlo, usa il CREATE TABLE
e INSERT
dichiarazioni del mio violino e poi esegui questo 11 volte:
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
;
Ho creato altre due soluzioni a questa query, ma la migliore con circa il doppio delle prestazioni aveva un difetto fatale (non gestendo correttamente intervalli di tempo completamente chiusi). L'altro aveva statistiche molto alte/cattive (che sapevo ma dovevo provare).
Spiegazione
Utilizzando tutti i tempi di fine corsa di ciascuna riga, creare un elenco distinto di tutti i possibili intervalli di tempo di interesse duplicando ciascun tempo di fine corsa e quindi raggruppandoli in modo tale da accoppiarsi ogni volta con l'ora successiva possibile. Somma i minuti trascorsi di questi intervalli ovunque coincidano con l'orario di lavoro effettivo di un lavoratore.