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.