Расчет продолжительности SQL - PullRequest
3 голосов
/ 07 декабря 2010

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

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

Я хочу создать расписание автобусных остановок на основе исторических поездок. Автобус находится «на остановке», если BusStopID соответствует остановке, и не «на остановке», если BusStopID равен нулю.

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

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

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

Ответы [ 4 ]

2 голосов
/ 07 декабря 2010

Этот пост Я только что видел, может помочь вам.(SQL Server Central)

2 голосов
/ 07 декабря 2010

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

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

Или, более подробно: (каждый из этих шагов может быть частью одного большого 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
0 голосов
/ 08 декабря 2010

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

-- 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
0 голосов
/ 07 декабря 2010

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

Попробуйте:

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, -@stopWindowMinutes * 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, -@stopWindowMinutes * 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, -@stopWindowMinutes * 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...