sql >> Base de Datos >  >> RDS >> Sqlserver

Encuentre el tiempo total trabajado con múltiples trabajos/órdenes con superposición/tiempos superpuestos en cada trabajador y trabajo/orden

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.