To zapytanie również spełnia swoje zadanie. Jego wydajność jest bardzo dobra (podczas gdy plan wykonania nie wygląda tak dobrze, rzeczywisty procesor i IO pokonają wiele innych zapytań).
Zobacz, jak działa na skrzypcach Sql .
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
;
Z indeksem klastrowym na WorkerID, JobStart, JobEnd, JobID
, a przy próbie 7 wierszy z powyższego szablonu dla danych nowego pracownika/zadania powtórzonych wystarczająco wiele razy, aby uzyskać tabelę z 14 336 wierszami, oto wyniki dotyczące wydajności. Na stronie zamieściłem inne działające/poprawne odpowiedzi (jak dotąd):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Przeprowadziłem bardziej wyczerpujący test z innego (wolniejszego) serwera (gdzie każde zapytanie zostało uruchomione 25 razy, najlepsze i najgorsze wartości dla każdej metryki zostały odrzucone, a pozostałe 23 wartości zostały uśrednione) i uzyskałem następujące wyniki:
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
Pomyślałem, że alternatywna technika na pewno poprawi sytuację. Cóż, zaoszczędził 6 odczytów, ale kosztował znacznie więcej procesora (co ma sens). Zamiast przenosić statystyki początku/końca każdego kawałka czasu do końca, najlepiej jest po prostu ponownie obliczyć, które wycinki należy zachować z EXISTS
w stosunku do oryginalnych danych. Może się zdarzyć, że inny profil kilku pracowników z wieloma zadaniami może zmienić statystyki wydajności dla różnych zapytań.
Jeśli ktoś chce spróbować, użyj CREATE TABLE
i INSERT
wypowiedzi z moich skrzypiec, a następnie uruchom to 11 razy:
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
;
Zbudowałem dwa inne rozwiązania dla tego zapytania, ale najlepsze z około dwukrotnie większą wydajnością miało fatalną wadę (nie poprawnie obsługuje w pełni zamknięte zakresy czasowe). Drugi miał bardzo wysokie/złe statystyki (które wiedziałem, ale musiałem spróbować).
Wyjaśnienie
Korzystając ze wszystkich czasów punktów końcowych z każdego wiersza, utwórz odrębną listę wszystkich możliwych interesujących zakresów czasowych, powielając każdy czas punktu końcowego, a następnie grupując w taki sposób, aby za każdym razem sparować z następnym możliwym czasem. Zsumuj minuty, które upłynęły z tych zakresów, gdziekolwiek pokrywają się one z rzeczywistym czasem pracy pracownika.