Essa consulta também funciona. Seu desempenho é muito bom (enquanto o plano de execução não parece tão bom, a CPU e o IO reais superam muitas outras consultas).
Veja-o funcionando em um 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
;
Com um índice clusterizado em
WorkerID, JobStart, JobEnd, JobID
, e com as 7 linhas da amostra acima, um modelo para novos dados de trabalho/trabalho repetidos o suficiente para gerar uma tabela com 14.336 linhas, aqui estão os resultados de desempenho. Incluí as outras respostas funcionais/corretas na página (até agora):Author CPU Elapsed Reads Scans
------ --- ------- ------ -----
Erik 157 166 122 2
Gordon 375 378 106964 53251
Fiz um teste mais exaustivo de um servidor diferente (mais lento) (onde cada consulta foi executada 25 vezes, os melhores e piores valores de cada métrica foram descartados e os 23 valores restantes foram calculados) e obtive o seguinte:
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
A técnica alternativa que eu pensei para ter certeza de melhorar as coisas. Bem, economizou 6 leituras, mas custou muito mais CPU (o que faz sentido). Em vez de levar as estatísticas de início/fim de cada fatia de tempo até o final, é melhor apenas recalcular quais fatias devem ser mantidas com o
EXISTS
contra os dados originais. Pode ser que um perfil diferente de poucos trabalhadores com muitos empregos possa alterar as estatísticas de desempenho para consultas diferentes. Caso alguém queira experimentar, use o
CREATE TABLE
e INSERT
declarações do meu violino e, em seguida, execute isso 11 vezes: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
;
Eu construí duas outras soluções para esta consulta, mas a melhor com cerca do dobro do desempenho teve uma falha fatal (não lidando corretamente com intervalos de tempo totalmente fechados). O outro tinha estatísticas muito altas/ruins (que eu sabia, mas tinha que tentar).
Explicação
Usando todos os tempos de terminal de cada linha, crie uma lista distinta de todos os intervalos de tempo possíveis de interesse duplicando cada tempo de terminal e, em seguida, agrupando de forma a emparelhar cada tempo com o próximo tempo possível. Some os minutos decorridos desses intervalos sempre que coincidirem com o tempo de trabalho real de qualquer trabalhador.