Ik heb een aantal keer iets soortgelijks gedaan. In wezen groeperen op basis van scheidingen binnen een complexe ordening. De basis van de benadering die ik gebruik, met betrekking tot dit probleem, is als volgt:
- Maak een tabel met alle interessante tijdsbereiken.
- Vind de starttijd voor elke groep van interessegebieden.
- Vind de eindtijd voor elke groep van interessegebieden.
- Voeg de begin- en eindtijden toe aan de lijst met tijdbereiken en groep.
Of, meer in detail:(elk van deze stappen kan deel uitmaken van één grote CTE, maar ik heb het opgesplitst in tijdelijke tabellen om het lezen te vergemakkelijken...)
Stap 1:Zoek de lijst met alle interessegebieden (ik heb een methode gebruikt die vergelijkbaar is met die van @Brad). OPMERKING:zoals @Manfred Sorg al aangaf, veronderstelt dit dat er geen "ontbrekende seconden" zijn in de data van een bus. Als er een onderbreking is in de tijdstempels, interpreteert deze code het enkele bereik als twee (of meer) verschillende bereiken.
;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
Stap 2:Zoek de vroegste tijd voor elke stop
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
Stap 3:Vind de laatste tijd voor elke stop
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
Stap 4:Voeg alles samen
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"
Eindelijk, om compleet te zijn, opruimen:
drop table #ends
drop table #starts
drop table #ranges