Diese Abfrage erledigt den Job auch. Seine Leistung ist sehr gut (während der Ausführungsplan nicht so toll aussieht, schlagen die tatsächliche CPU und IO viele andere Abfragen).
Sehen Sie, wie es in einer Sql Fiddle funktioniert .
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
;
Mit einem gruppierten Index auf WorkerID, JobStart, JobEnd, JobID
, und mit den 7 Beispielzeilen aus der obigen Fiddle eine Vorlage für neue Arbeiter/Job-Daten, die oft genug wiederholt wird, um eine Tabelle mit 14.336 Zeilen zu ergeben, hier sind die Leistungsergebnisse. Ich habe die anderen funktionierenden/korrekten Antworten auf der Seite (bisher) eingefügt:
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Ich habe einen umfassenderen Test von einem anderen (langsameren) Server durchgeführt (bei dem jede Abfrage 25 Mal ausgeführt wurde, die besten und schlechtesten Werte für jede Metrik verworfen und die verbleibenden 23 Werte gemittelt wurden) und Folgendes erhalten:
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
Die alternative Technik dachte ich, um sicher zu sein, die Dinge zu verbessern. Nun, es hat 6 Lesevorgänge gespart, aber viel mehr CPU gekostet (was Sinn macht). Anstatt die Start/End-Statistiken jeder Zeitscheibe bis zum Ende durchzuziehen, ist es am besten, einfach neu zu berechnen, welche Scheiben mit EXISTS
beibehalten werden sollen gegen die Originaldaten. Es kann sein, dass ein unterschiedliches Profil von wenigen Arbeitern mit vielen Jobs die Leistungsstatistiken für verschiedene Abfragen ändern könnte.
Falls es jemand ausprobieren möchte, benutze CREATE TABLE
und INSERT
Anweisungen von meiner Geige und führen Sie dann dies 11 Mal aus:
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
;
Ich habe zwei andere Lösungen für diese Abfrage erstellt, aber die beste mit etwa der doppelten Leistung hatte einen fatalen Fehler (nicht korrekt mit vollständig eingeschlossenen Zeitbereichen umgehen). Der andere hatte sehr hohe/schlechte Statistiken (was ich wusste, aber ausprobieren musste).
Erklärung
Erstellen Sie unter Verwendung aller Endpunktzeiten aus jeder Zeile eine eindeutige Liste aller möglichen interessierenden Zeitbereiche, indem Sie jede Endpunktzeit duplizieren und dann so gruppieren, dass jede Zeit mit der nächstmöglichen Zeit gepaart wird. Summieren Sie die verstrichenen Minuten dieser Bereiche, wo immer sie mit der tatsächlichen Arbeitszeit eines Arbeiters zusammenfallen.