Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Encontre o tempo total trabalhado com vários trabalhos/pedidos com sobreposição/tempos sobrepostos em cada trabalhador e trabalho/pedido


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.