À un certain nombre d'occasions, j'ai fait quelque chose de similaire. Essentiellement, regroupement basé sur des séparations dans un ordre complexe. Les bases de l'approche que j'utilise, en ce qui concerne ce problème, sont les suivantes:
- Construire un tableau de toutes les plages de temps d'intérêt.
- Trouvez l'heure de début pour chaque groupe de plages d'intérêt.
- Trouvez l'heure de fin pour chaque groupe de plages d'intérêt.
- Joignez les heures de début et de fin à la liste des périodes et du groupe.
Ou, plus en détail: (chacune de ces étapes pourrait être une partie d'un grand CTE, mais je me suis cassé vers le bas dans les tables temporaires pour faciliter la lecture ...)
Étape 1 : Trouver la liste de toutes les plages d'intérêt (j'ai utilisé une méthode similaire à celle liée à @Brad). NOTE: 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 interruption 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 le temps plus tôt 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: Trouver la dernière fois 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: Joignez-vous tout ensemble
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, ranger:
drop table #ends
drop table #starts
drop table #ranges
est 'Bus ID' vraiment censé incrémenter pour tous les horodateurs? Comme 'Timestamp' est en fait un type de données en SQL, je déconseille de l'utiliser comme un nom de colonne, mais je comprends que vous avez choisi un nom qui a du sens (contrairement au nom du type de données lui-même). – Brad
Oups, j'ai fait une erreur en tapant le schéma dans StackOverflow. Vous avez raison, l'identifiant du fil d'Ariane augmente et le BusID est un FK. –
Question intéressante, beaucoup plus délicate qu'il n'y paraît – smirkingman