Запрос для расчета среднего времени между последовательными событиями - PullRequest
6 голосов
/ 22 декабря 2009

Мой вопрос о том, как написать запрос SQL для вычисления среднего времени между последовательными событиями.

У меня есть маленький столик:

event Name    |    Time

stage 1       |    10:01
stage 2       |    10:03
stage 3       |    10:06
stage 1       |    10:10
stage 2       |    10:15
stage 3       |    10:21
stage 1       |    10:22
stage 2       |    10:23
stage 3       |    10:29

Я хочу построить запрос, который в качестве ответа получает среднее время между этапом (i) и этапом (i + 1).

Например, среднее время между этапом 2 и этапом 3 составляет 5:

(3+6+6)/3 =  5

Ответы [ 8 ]

13 голосов
/ 22 декабря 2009

Дааа и с вкраплениями черной магии:

select a.eventName, b.eventName, AVG(DATEDIFF(MINUTE, a.[Time], b.[Time])) as Average from
     (select *, row_number() over (order by [time]) rn from events) a
join (select *, row_number() over (order by [time]) rn from events) b on (a.rn=b.rn-1)
group by
a.eventName, b.eventName

Это даст вам такие строки, как:

stage3  stage1  2
stage1  stage2  2
stage2  stage3  5

Первый столбец - начальное событие, второй столбец - конечное событие. Если есть событие 3 сразу после события 1, оно также будет указано. В противном случае вы должны указать некоторые критерии относительно того, какой этап следует за каким этапом, поэтому время рассчитывается только между ними.

Добавлено: Это должно работать нормально как на Transact-SQL (MSSQL, Sybase), так и на PL / SQL (Oracle, PostgreSQL). Однако я не проверял это, и все еще могли быть ошибки синтаксиса. Это НЕ будет работать на любой редакции MySQL.

2 голосов
/ 22 декабря 2009
Select Avg(differ) from (
 Select s1.r, s2.r, s2.time - s1.time as differ from (
 Select * From (Select rownum as r, inn.time from table inn order by time) s1
 Join (Select rownum as r, inn.time from table inn order by time) s2
 On mod(s2.r, 3) = 2 and s2.r = s1.r + 1
 Where mod(s1.r, 3) = 1)
);

Параметры могут быть изменены при изменении количества ступеней. В настоящее время он настроен на поиск среднего значения между этапами 1 и 2 из 3-этапного процесса.

РЕДАКТИРОВАТЬ пару опечаток

1 голос
/ 22 декабря 2009

Вы не говорите, для какой разновидности SQL вы хотите получить ответ. Это, вероятно, означает, что вам нужен код в SQL Server (как [sql] обычно = [sql-server] в использовании тега SO).

Но на тот случай, если вы (или какой-то будущий искатель) используете Oracle, этот тип запроса довольно прост с аналитическими функциями, в данном случае LAG(). Проверьте это:

SQL> select stage_range
  2         , avg(time_diff)/60 as average_time_diff_in_min
  3  from
  4      (
  5          select event_name
  6                 , case when event_name = 'stage 2' then  'stage 1 to 2'
  7                      when event_name = 'stage 3' then  'stage 2 to 3'
  8                      else  '!!!' end as stage_range
  9                 , stage_secs - lag(stage_secs)
 10                              over (order by ts, event_name) as time_diff
 11                 from
 12                     ( select event_name
 13                              , ts
 14                              , to_number(to_char(ts, 'sssss')) as stage_secs
 15                       from timings )
 16      )
 17         where event_name in ('stage 2','stage 3')
 18  group by stage_range
 19  /

STAGE_RANGE  AVERAGE_TIME_DIFF_IN_MIN
------------ ------------------------
stage 1 to 2               2.66666667
stage 2 to 3                        5

SQL>

Изменение формата во внутреннем запросе необходимо, потому что я сохранил столбец TIME как тип данных DATE, поэтому я преобразовал его в секунды, чтобы сделать математику более понятной. Альтернативным решением было бы работать с Day to Second Interval типом данных. Но это решение действительно о LAG().

1012 * редактировать *

В моем рассмотрении этого запроса я явно не рассчитал разницу между предыдущим этапом 3 и последующим этапом 1. Это является требованием.

1 голос
/ 22 декабря 2009

Самый простой способ - упорядочить по времени и использовать курсор (tsql) для перебора данных. Поскольку курсоры являются злыми, рекомендуется извлекать упорядоченные по времени данные в код приложения и выполнять там итерации. Возможно, есть и другие способы сделать это в SQL, но они будут очень сложными и будут опираться на нестандартные языковые расширения.

1 голос
/ 22 декабря 2009

Ваш дизайн стола некорректен. Как вы можете сказать, какой этап1 идет с каким этапом2? Без этого я не думаю, что ваш запрос возможен.

0 голосов
/ 22 декабря 2009

Я не могу комментировать, но я должен согласиться с HLGEM. Несмотря на то, что вы можете сказать с помощью предоставленного набора данных, OP должен знать, что полагаться только на один набор этапов, существующих одновременно, может быть слишком оптимистично.


event Name    |    Time

stage 1       |    10:01
stage 2       |    10:03
stage 3       |    10:06
stage 1       |    10:10
stage 2       |    10:15
stage 3       |    10:21
stage 1       |    10:22
stage 2       |    10:23
stage 1       |    10:25     --- new stage 1
stage 2       |    10:28     --- new stage 2
stage 3       |    10:29
stage 3       |    10:34     --- new stage 3

Мы не знаем среду или то, что создает данные. Решение о том, правильно ли построена таблица, решать оператору.

Oracle справится с этим с помощью Analytics. как ответ Вилкса.

0 голосов
/ 22 декабря 2009
WITH    q AS
        (
        SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:01:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:03:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:06:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:10:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:15:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:21:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 1' AS eventname, CAST('2009-01-01 10:22:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 2' AS eventname, CAST('2009-01-01 10:23:00' AS DATETIME) AS eventtime
        UNION ALL
        SELECT  'stage 3' AS eventname, CAST('2009-01-01 10:29:00' AS DATETIME) AS eventtime
        )
SELECT  (
        SELECT  AVG(DATEDIFF(minute, '2009-01-01', eventtime))
        FROM    q
        WHERE   eventname = 'stage 3'
        ) - 
        (
        SELECT  AVG(DATEDIFF(minute, '2009-01-01', eventtime))
        FROM    q
        WHERE   eventname = 'stage 2'
        )

Это зависит от того факта, что у вас всегда есть полные группы этапов, и они всегда идут в одном и том же порядке (то есть stage 1, затем stage 2, затем stage 3)

0 голосов
/ 22 декабря 2009

попробуйте

   Select Avg(e.Time - s.Time)
   From Table s
     Join Table e 
         On e.Time = 
             (Select Min(Time)
              From Table
              Where eventname = s.eventname 
                 And time > s.Time)
         And Not Exists 
             (Select * From Table
              Where eventname = s.eventname 
                 And time < s.Time)

Для каждой записи, представляющей начало этапа, этот sql присоединяет ее к записи, которая представляет конец, принимает разницу между временем окончания и временем начала и усредняет эти различия. Not Exists гарантирует, что промежуточный набор результатов начальных записей, соединенных с конечными записями, включает только начальные записи как s ... и первое условие соединения гарантирует, что только одна конечная запись (та, которая имеет то же имя и следующее значение после время начала) присоединяется к нему ...

Чтобы увидеть промежуточный набор результатов после объединения, но до получения среднего значения, выполните следующее:

   Select s.EventName,
       s.Time Startime, e.Time EndTime, 
       (e.Time - s.Time) Elapsed
   From Table s
     Join Table e 
         On e.Time = 
             (Select Min(Time)
              From Table
              Where eventname = s.eventname 
                 And time > s.Time)
         And Not Exists 
             (Select * From Table
              Where eventname = s.eventname 
                 And time < s.Time)
...