В редица случаи съм правил нещо подобно. По същество групиране въз основа на разделяне в рамките на комплексно подреждане. Основите на подхода, който използвам по отношение на този проблем, са следните:
- Създайте таблица с всички интересни времеви диапазони.
- Намерете началния час за всяка група от периоди от време, които представляват интерес.
- Намерете крайния час за всяка група от интересни времеви диапазони.
- Присъединете началния и крайния час към списъка с периоди от време и групирайте.
Или, по-подробно:(всяка от тези стъпки може да бъде част от един голям CTE, но аз съм го разделил на временни таблици за по-лесно четене...)
Стъпка 1:Намерете списъка с всички интересни времеви диапазони (използвах метод, подобен на този, към който е свързана от @Brad). ЗАБЕЛЕЖКА:както посочи @Manfred Sorg, това предполага, че няма „липсващи секунди“ в данните на автобуса. Ако има прекъсване във времевите клейма, този код ще интерпретира единичния диапазон като два (или повече) различни диапазона.
;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
Стъпка 2:Намерете най-ранния час за всяко спиране
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
Стъпка 3:Намерете най-новия час за всяко спиране
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
Стъпка 4:Свържете всичко заедно
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"
И накрая, за да бъде пълно, подредете:
drop table #ends
drop table #starts
drop table #ranges