Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Calcul du temps de durée SQL

À plusieurs reprises, j'ai fait quelque chose de similaire. Essentiellement, un regroupement basé sur des séparations au sein d'un ordre complexe. Les bases de l'approche que j'utilise, en ce qui concerne ce problème, sont les suivantes :

  1. Créer un tableau de toutes les périodes d'intérêt.
  2. Recherchez l'heure de début pour chaque groupe de périodes d'intérêt.
  3. Recherchez l'heure de fin pour chaque groupe de périodes d'intérêt.
  4. Joignez les heures de début et de fin à la liste des plages horaires et groupez-les.

Ou, plus en détail :(chacune de ces étapes pourrait faire partie d'un grand CTE, mais je l'ai décomposé en tables temporaires pour faciliter la lecture...)

Étape 1 :Trouvez la liste de toutes les plages horaires d'intérêt (j'ai utilisé une méthode similaire à celle liée à par @Brad). REMARQUE :comme l'a souligné @Manfred Sorg, cela suppose qu'il n'y a pas de "secondes manquantes" dans les données d'un bus. S'il y a une rupture dans les horodatages, ce code interprétera la plage unique comme deux plages distinctes (ou plus).

;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

Étape 2 :Trouvez l'heure la plus proche pour chaque arrêt

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

Étape 3 :Trouvez l'heure la plus tardive pour chaque arrêt

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

Étape 4 : Assemblez le tout

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"

Enfin, pour être complet, rangez :

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