Tento dotaz také funguje. Jeho výkon je velmi dobrý (zatímco plán provádění nevypadá tak skvěle, skutečný CPU a IO porazí mnoho dalších dotazů).
Podívejte se, jak to funguje v 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
;
Se seskupeným indexem na WorkerID, JobStart, JobEnd, JobID
a s ukázkou 7 řádků z výše uvedených houslí šablony pro data nového pracovníka/úlohy, která se opakuje dostatečně často, aby se získala tabulka se 14 336 řádky, zde jsou výsledky výkonu. Další funkční/správné odpovědi jsem zahrnul na stránku (zatím):
Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Provedl jsem podrobnější test z jiného (pomalejšího) serveru (kde byl každý dotaz spuštěn 25krát, nejlepší a nejhorší hodnoty pro každou metriku byly vyhozeny a zbývajících 23 hodnot bylo zprůměrováno) a dostal jsem následující:
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
Alternativní technika, o které jsem si myslel, že věci zlepší. No, ušetřilo to 6 čtení, ale stálo to mnohem víc CPU (což dává smysl). Namísto přenášení počáteční/koncové statistiky každého časového řezu až do konce je nejlepší jednoduše přepočítat, které řezy ponechat, pomocí EXISTS
oproti původním údajům. Je možné, že odlišný profil několika málo pracovníků s mnoha zaměstnáními by mohl změnit statistiky výkonu pro různé dotazy.
V případě, že by to chtěl někdo vyzkoušet, použijte CREATE TABLE
a INSERT
výpisy z mých houslí a pak to spusťte 11krát:
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
;
Na tento dotaz jsem postavil dvě další řešení, ale to nejlepší s přibližně dvojnásobným výkonem mělo fatální chybu (nesprávné zpracování plně uzavřených časových rozsahů). Druhý měl velmi vysoké/špatné statistiky (což jsem věděl, ale musel jsem to zkusit).
Vysvětlení
Pomocí všech časů koncových bodů z každého řádku vytvořte zřetelný seznam všech možných časových rozsahů zájmu duplikováním každého koncového času a následným seskupením tak, aby se pokaždé spárovalo s dalším možným časem. Sečtěte uplynulé minuty těchto rozsahů, ať se shodují s jakoukoli skutečnou pracovní dobou pracovníka.