Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Trova il tempo totale lavorato con più lavori/ordini con tempi di sovrapposizione/sovrapposizione su ciascun lavoratore e lavoro/ordine

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.