Esta consulta también hace el trabajo. Su rendimiento es muy bueno (aunque el plan de ejecución no parece tan bueno, la CPU y el IO reales superan muchas otras consultas).
Véalo funcionando en 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 índice agrupado en WorkerID, JobStart, JobEnd, JobID
, y con la muestra de 7 filas del violín anterior, una plantilla para datos de nuevos trabajadores/trabajos repetidos suficientes veces para producir una tabla con 14,336 filas, estos son los resultados de rendimiento. He incluido las otras respuestas de trabajo/correctas en la página (hasta ahora):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Hice una prueba más exhaustiva desde un servidor diferente (más lento) (donde cada consulta se ejecutó 25 veces, se descartaron los mejores y peores valores para cada métrica y se promediaron los 23 valores restantes) y obtuve lo siguiente:
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 técnica alternativa pensé para asegurarme de mejorar las cosas. Bueno, ahorró 6 lecturas, pero costó mucho más CPU (lo cual tiene sentido). En lugar de llevar las estadísticas de inicio/fin de cada intervalo de tiempo hasta el final, lo mejor es volver a calcular qué intervalos conservar con EXISTS
contra los datos originales. Puede ser que un perfil diferente de pocos trabajadores con muchos trabajos pueda cambiar las estadísticas de rendimiento para diferentes consultas.
Por si alguien quiere probarlo, use el CREATE TABLE
y INSERT
declaraciones de mi violín y luego ejecutar esto 11 veces:
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
;
Creé otras dos soluciones para esta consulta, pero la mejor con aproximadamente el doble de rendimiento tenía una falla fatal (no manejaba correctamente rangos de tiempo completamente cerrados). El otro tenía estadísticas muy altas/malas (lo sabía pero tenía que intentarlo).
Explicación
Usando todos los tiempos de punto final de cada fila, cree una lista distinta de todos los rangos de tiempo posibles de interés duplicando cada tiempo de punto final y luego agrupándolos de tal manera que emparejen cada tiempo con el siguiente tiempo posible. Sume los minutos transcurridos de estos rangos siempre que coincidan con el tiempo de trabajo real de cualquier trabajador.