2010-12-07 6 views
3

У меня есть таблица исторических позиций шины в заданное время, записанное один раз в секунду. Схема выглядит следующим образом:Расчет времени продолжительности SQL

BusID  int   not null, 
BreadcrumbID int   not null identity (1, 1), 
BusStopID int   null, 
Timestamp datetime not null 

Я хочу создать график остановки автобуса, основанный на исторических поездках. Шина находится «на остановке», если ее BusStopID соответствует остановке и не находится «на остановке», если BusStopID имеет значение NULL.

Мне нужно сгенерировать среднее время, в течение которого автобус находится на каждой остановке. Таким образом, в принципе, мне нужно сделать следующее:

  • Определить раз, что автобус на остановке - простое where положение делает трюк
  • Идентифицировать среднее время автобус стоит на месте. Для моих целей я определяю дискретное «время остановки» как окно плюс/минус 10 минут; если автобус останавливается один день с 10:04 до 10:08, другой день в 10:06 - 10:08 и третий день в 10:14 - 10:18, это будет одна и та же остановка, но если она остановится в 10:45 - 10:48, это будет другая остановка.
  • Фильтр из «шум» - т.е. останавливает раз, что только случался несколько раз, но никогда снова

Я полностью в недоумении относительно того, как выполнить вторую и третью пулю. Пожалуйста помоги!

+0

Is `Bus Идентификатор действительно должен увеличиваться для всех временных меток? Кроме того, поскольку `Timestamp` на самом деле является типом данных в SQL, я бы посоветовал использовать его в качестве имени столбца, но я понимаю, что вы выбрали имя, которое имеет смысл (в отличие от самого имени самого типа данных). – Brad 2010-12-07 13:26:20

+0

Упс, я ошибся при вводе схемы в StackOverflow. Вы правы, увеличивается идентификатор палитры, а BusID - это FK. – 2010-12-07 13:27:20

+1

Интересный вопрос, намного сложнее, чем кажется – smirkingman 2010-12-08 13:05:20

ответ

2

В ряде случаев я сделал нечто подобное. По существу, группировка основана на разделении в сложном порядке. Основы подхода, который я использую в отношении этой проблемы, следующие:

  1. Построить таблицу всех диапазонов времени.
  2. Найти время начала для каждой группы интересующих периодов времени.
  3. Найдите конечное время для каждой группы интересующих периодов времени.
  4. Соедините начальное и конечное время со списком диапазонов времени и группы.

Или, более подробно: (каждый из этих шагов может быть частью одной большой КТР, но я разбил его на временные таблицы для удобства чтения ...)

Шаг 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: Join все вместе

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 
0

Свежий ответ ...

Попробуйте это:

DECLARE @stopWindowMinutes INT 
SET @stopWindowMinutes = 10 

-- 
; 
WITH test_data 
      AS (SELECT 1 [BusStopId] 
         ,'2010-01-01 10:00:04' [Timestamp] 
       UNION SELECT 1,'2010-01-01 10:00:05' 
       UNION SELECT 1,'2010-01-01 10:00:06' 
       UNION SELECT 1,'2010-01-01 10:00:07' 
       UNION SELECT 1,'2010-01-01 10:00:08' 
       UNION SELECT 1,'2010-01-02 10:00:06' 
       UNION SELECT 1,'2010-01-02 10:00:07' 
       UNION SELECT 1,'2010-01-02 10:00:08' 
       UNION SELECT 2,'2010-01-01 10:00:06' 
       UNION SELECT 2,'2010-01-01 10:00:07' 
       UNION SELECT 2,'2010-01-01 10:00:08' 
       UNION SELECT 2,'2010-01-01 10:00:09' 
       UNION SELECT 2,'2010-01-01 10:00:10' 
       UNION SELECT 2,'2010-01-01 10:00:09' 
       UNION SELECT 2,'2010-01-01 10:00:10' 
       UNION SELECT 2,'2010-01-01 10:00:11' 
       UNION SELECT 1,'2010-01-02 10:33:43' 
       UNION SELECT 1,'2010-01-02 10:33:44' 
       UNION SELECT 1,'2010-01-02 10:33:45' 
       UNION SELECT 1,'2010-01-02 10:33:46' 
      ) 
    SELECT DISTINCT 
      [BusStopId] 
      ,[AvgStop] 
    FROM (SELECT [a].[BusStopId] 
         ,(SELECT MIN([b].[Timestamp]) 
          FROM  [test_data] b 
          WHERE  [a].[BusStopId] = [b].[BusStopId] 
            AND CONVERT(VARCHAR(10), [a].[Timestamp], 120) = CONVERT(VARCHAR(10), [b].[Timestamp], 120) 
            AND [b].[Timestamp] BETWEEN DATEADD(SECOND, [email protected] * 60, 
                     [a].[Timestamp]) 
                 AND  DATEADD(SECOND, @stopWindowMinutes * 60, [a].[Timestamp]) -- w/i X minutes 

         ) [MinStop] 
         ,(SELECT MAX([b].[Timestamp]) 
          FROM  [test_data] b 
          WHERE  [a].[BusStopId] = [b].[BusStopId] 
            AND CONVERT(VARCHAR(10), [a].[Timestamp], 120) = CONVERT(VARCHAR(10), [b].[Timestamp], 120) 
            AND [b].[Timestamp] BETWEEN DATEADD(SECOND, [email protected] * 60, 
                     [a].[Timestamp]) 
                 AND  DATEADD(SECOND, @stopWindowMinutes * 60, [a].[Timestamp]) -- w/i X minutes 

         ) [MaxStop] 
         ,(SELECT DATEADD(second, 
              AVG(DATEDIFF(second, CONVERT(VARCHAR(10), [b].[Timestamp], 120), 
                 [b].[Timestamp])), 
              CONVERT(VARCHAR(10), MIN([b].[Timestamp]), 120)) 
          FROM  [test_data] b 
          WHERE  [a].[BusStopId] = [b].[BusStopId] 
            AND CONVERT(VARCHAR(10), [a].[Timestamp], 120) = CONVERT(VARCHAR(10), [b].[Timestamp], 120) 
            AND [b].[Timestamp] BETWEEN DATEADD(SECOND, [email protected] * 60, 
                     [a].[Timestamp]) 
                 AND  DATEADD(SECOND, @stopWindowMinutes * 60, [a].[Timestamp]) -- w/i X minutes 

         ) [AvgStop] 
       FROM  [test_data] a 
       WHERE  CONVERT(VARCHAR(10), [Timestamp], 120) = CONVERT(VARCHAR(10), [Timestamp], 120) 
       GROUP BY [a].[BusStopId] 
         ,[a].[Timestamp] 
      ) subset1 
0

Как это часто бывает, такого рода проблемы легче решать и поддерживать разбивая их на части размера укуса:

-- Split into Date and minutes-since-midnight 
WITH observed(dates,arrival,busstop,bus) AS (
    SELECT 
     CONVERT(CHAR(8), TimeStamp, 112), 
     DATEPART(HOUR,TimeStamp) * 60 + DATEPART(MINUTE,TimeStamp), 
     busstopid, 
     busid 
    FROM 
     History 
), 
-- Identify times at stop subsequent to arrival at that stop 
atstop(dates,stoptime,busstop,bus) AS (
    SELECT 
     a.dates, 
     a.arrival, 
     a.busstop, 
     a.bus 
    FROM 
     observed a 
    WHERE 
     EXISTS (
      SELECT 
       * 
      FROM 
       observed b 
      WHERE 
       a.dates = b.dates AND 
       a.busstop = b.busstop AND 
       a.bus = b.bus AND 
       a.arrival - b.arrival BETWEEN 1 AND 10 
     ) 
), 
-- Isolate actual arrivals at stops, excluding waiting at stops 
dailyhalts(dates,arrival,busstop,bus) AS (
    SELECT 
     a.dates,a.arrival,a.busstop,a.bus 
    FROM 
     observed a 
    WHERE 
     arrival NOT IN (
      SELECT 
       stoptime 
      FROM 
       atstop b 
      WHERE 
       a.dates = b.dates AND 
       a.busstop = b.busstop AND 
       a.bus = b.bus 
    ) 
), 
-- Merge arrivals across all dates 
timetable(busstop,bus,arrival) AS (
    SELECT 
     a.busstop, a.bus, a.arrival 
    FROM 
     dailyhalts a 
    WHERE 
     NOT EXISTS (
      SELECT 
       * 
      FROM 
       dailyhalts h 
      WHERE 
       a.busstop = h.busstop AND 
       a.bus = h.bus AND 
       a.arrival - h.arrival BETWEEN 1 AND 10 
     ) 
    GROUP BY 
     a.busstop, a.bus, a.arrival 
) 
-- Print timetable for a given day 
SELECT 
    a.busstop, a.bus, a.arrival, DATEADD(minute,AVG(b.arrival),'2010/01/01') 
FROM 
    timetable a INNER JOIN 
    observed b ON 
     a.busstop = b.busstop AND 
     a.bus = b.bus AND 
     b.arrival BETWEEN a.arrival AND a.arrival + 10 
GROUP BY 
    a.busstop, a.bus, a.arrival 

ввода :

ID BusID BusStopID TimeStamp 
1 1 1 2010-01-01 10:00:00.000 
2 1 1 2010-01-01 10:01:00.000 
3 1 1 2010-01-01 10:02:00.000 
4 1 2 2010-01-01 11:00:00.000 
5 1 3 2010-01-01 12:00:00.000 
6 1 3 2010-01-01 12:01:00.000 
7 1 3 2010-01-01 12:02:00.000 
8 1 3 2010-01-01 12:03:00.000 
9 1 1 2010-01-02 11:00:00.000 
10 1 1 2010-01-02 11:03:00.000 
11 1 1 2010-01-02 11:07:00.000 
12 1 2 2010-01-02 12:00:00.000 
13 1 3 2010-01-02 13:00:00.000 
14 1 3 2010-01-02 13:01:00.000 
15 1 1 2010-01-03 10:03:00.000 
16 1 1 2010-01-03 10:05:00.000 

Выход:

busstop bus arrival (No column name) 
1 1 600 2010-01-01 10:02:00.000 
1 1 660 2010-01-01 11:03:00.000 
2 1 660 2010-01-01 11:00:00.000 
2 1 720 2010-01-01 12:00:00.000 
3 1 720 2010-01-01 12:01:00.000 
3 1 780 2010-01-01 13:00:00.000 
Смежные вопросы