Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Trouver le temps total travaillé avec plusieurs travaux/commandes avec des temps de chevauchement/chevauchement sur chaque travailleur et travail/commande

Cette requête fait également le travail. Ses performances sont très bonnes (alors que le plan d'exécution n'a pas l'air si bon, le processeur et les E/S réels battent de nombreuses autres requêtes).

Regardez-le fonctionner dans 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
;

Avec un index clusterisé sur WorkerID, JobStart, JobEnd, JobID , et avec les 7 lignes de l'échantillon ci-dessus, reproduisez un modèle pour les nouvelles données de travailleur/d'emploi répété suffisamment de fois pour produire un tableau de 14 336 lignes, voici les résultats de performance. J'ai inclus les autres réponses correctes/de travail sur la page (jusqu'à présent) :

Author  CPU  Elapsed  Reads   Scans
------  ---  -------  ------  -----
  Erik  157    166      122       2
Gordon  375    378    106964  53251

J'ai fait un test plus exhaustif à partir d'un serveur différent (plus lent) (où chaque requête a été exécutée 25 fois, les meilleures et les pires valeurs pour chaque métrique ont été rejetées et les 23 valeurs restantes ont été moyennées) et j'ai obtenu ce qui suit :

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 technique alternative je pensais être sûr d'améliorer les choses. Eh bien, cela a permis d'économiser 6 lectures, mais coûte beaucoup plus de CPU (ce qui est logique). Au lieu de poursuivre les statistiques de début/fin de chaque tranche de temps jusqu'à la fin, il est préférable de simplement recalculer les tranches à conserver avec le EXISTS contre les données d'origine. Il se peut qu'un profil différent de quelques travailleurs avec de nombreux emplois puisse modifier les statistiques de performances pour différentes requêtes.

Au cas où quelqu'un voudrait l'essayer, utilisez le CREATE TABLE et INSERT déclarations de mon violon, puis exécutez ceci 11 fois :

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
;

J'ai construit deux autres solutions à cette requête, mais la meilleure avec environ le double des performances avait un défaut fatal (ne gérant pas correctement les plages de temps entièrement fermées). L'autre avait des statistiques très élevées/mauvaises (que je connaissais mais que je devais essayer).

Explication

En utilisant toutes les heures de point final de chaque ligne, créez une liste distincte de toutes les plages de temps possibles d'intérêt en dupliquant chaque heure de point final, puis en les regroupant de manière à associer chaque fois à la prochaine heure possible. Additionnez les minutes écoulées de ces plages chaque fois qu'elles coïncident avec le temps de travail réel d'un travailleur.