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

Cálculo do tempo de duração do SQL


Em várias ocasiões eu fiz algo semelhante. Essencialmente, agrupamento baseado em separações dentro de uma ordenação complexa. Os fundamentos da abordagem que uso, com relação a esse problema, são os seguintes:
  1. Crie uma tabela com todos os intervalos de tempo de interesse.
  2. Encontre a hora de início de cada grupo de intervalos de tempo de interesse.
  3. Encontre o horário de término de cada grupo de intervalos de tempo de interesse.
  4. Junte os horários de início e término à lista de intervalos de tempo e agrupe.

Ou, com mais detalhes:(cada uma dessas etapas pode fazer parte de um grande CTE, mas eu o dividi em tabelas temporárias para facilitar a leitura...)

Etapa 1:encontre a lista de todos os intervalos de tempo de interesse (usei um método semelhante ao vinculado por @Brad). NOTA:como @Manfred Sorg apontou, isso pressupõe que não há "segundos perdidos" nos dados de um ônibus. Se houver uma quebra nos carimbos de data/hora, esse código interpretará o intervalo único como dois (ou mais) intervalos distintos.
;with stopSeconds as (
  select BusID, BusStopID, TimeStamp,
         [date] = cast(datediff(dd,0,TimeStamp) as datetime),
         [grp] = dateadd(ss, -row_number() over(partition by BusID order by TimeStamp), TimeStamp)
  from #test
  where BusStopID is not null
)
select BusID, BusStopID, date,
       [sTime] = dateadd(ss,datediff(ss,date,min(TimeStamp)), 0),
       [eTime] = dateadd(ss,datediff(ss,date,max(TimeStamp)), 0),
       [secondsOfStop] = datediff(ss, min(TimeStamp), max(Timestamp)),
       [sOrd] = row_number() over(partition by BusID, BusStopID order by datediff(ss,date,min(TimeStamp))),
       [eOrd] = row_number() over(partition by BusID, BusStopID order by datediff(ss,date,max(TimeStamp)))
into #ranges
from stopSeconds
group by BusID, BusStopID, date, grp

Etapa 2:encontre o horário mais cedo para cada parada
select this.BusID, this.BusStopID, this.sTime minSTime,
       [stopOrder] = row_number() over(partition by this.BusID, this.BusStopID order by this.sTime)
into #starts
from #ranges this
  left join #ranges prev on this.BusID = prev.BusID
                        and this.BusStopID = prev.BusStopID
                        and this.sOrd = prev.sOrd+1
                        and this.sTime between dateadd(mi,-10,prev.sTime) and dateadd(mi,10,prev.sTime)
where prev.BusID is null

Etapa 3:encontre o último horário para cada parada
select this.BusID, this.BusStopID, this.eTime maxETime,
       [stopOrder] = row_number() over(partition by this.BusID, this.BusStopID order by this.eTime)
into #ends
from #ranges this
  left join #ranges next on this.BusID = next.BusID
                        and this.BusStopID = next.BusStopID
                        and this.eOrd = next.eOrd-1
                        and this.eTime between dateadd(mi,-10,next.eTime) and dateadd(mi,10,next.eTime)
where next.BusID is null

Passo 4:Junte tudo
select r.BusID, r.BusStopID,
       [avgLengthOfStop] = avg(datediff(ss,r.sTime,r.eTime)),
       [earliestStop] = min(r.sTime),
       [latestDepart] = max(r.eTime)
from #starts s
  join #ends e on s.BusID=e.BusID
              and s.BusStopID=e.BusStopID
              and s.stopOrder=e.stopOrder
  join #ranges r on r.BusID=s.BusID
                and r.BusStopID=s.BusStopID
                and r.sTime between s.minSTime and e.maxETime
                and r.eTime between s.minSTime and e.maxETime
group by r.BusID, r.BusStopID, s.stopOrder
having count(distinct r.date) > 1 --filters out the "noise"

Por fim, para ficar completo, arrume:
drop table #ends
drop table #starts
drop table #ranges