Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Calcolo del tempo di durata SQL

In diverse occasioni ho fatto qualcosa di simile. In sostanza, raggruppamento basato su separazioni all'interno di un ordinamento complesso. Le basi dell'approccio che utilizzo, riguardo a questo problema, sono le seguenti:

  1. Costruisci una tabella di tutti gli intervalli di tempo di interesse.
  2. Trova l'ora di inizio per ciascun gruppo di intervalli di tempo di interesse.
  3. Trova l'ora di fine per ciascun gruppo di intervalli di tempo di interesse.
  4. Unisci gli orari di inizio e fine all'elenco degli intervalli di tempo e raggruppa.

O, più in dettaglio:(ciascuno di questi passaggi potrebbe far parte di un grande CTE, ma l'ho suddiviso in tabelle temporanee per facilitare la lettura...)

Passaggio 1:trova l'elenco di tutti gli intervalli di tempo di interesse (ho usato un metodo simile a quello collegato da @Brad). NOTA:come ha sottolineato @Manfred Sorg, questo presuppone che non ci siano "secondi mancanti" nei dati di un autobus. Se si verifica un'interruzione nei timestamp, questo codice interpreterà il singolo intervallo come due (o più) intervalli distinti.

;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

Passaggio 2:trova l'orario più presto per ogni fermata

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

Passaggio 3:trova l'ultima ora per ogni fermata

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

Passaggio 4:unisci tutto insieme

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"

Infine, per completare, riordina:

drop table #ends
drop table #starts
drop table #ranges