Как усреднить временные интервалы? - PullRequest
15 голосов
/ 16 января 2009

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

select avg(endtime-starttime) from timings;

Но получите:

Ошибка SQL: ORA-00932: не соответствует типы данных: ожидаемое число получено ИНТЕРВАЛЬНЫЙ ДЕНЬ ВТОРОЙ

Это работает:

select
     avg(extract( second from  endtime - starttime) +
        extract ( minute from  endtime - starttime) * 60 +
        extract ( hour   from  endtime - starttime) * 3600) from timings;

Но очень медленно.

Есть ли лучший способ превратить интервалы в числа секунд или каким-либо другим способом сделать это?

EDIT: То, что действительно замедляло это, было то, что у меня было некоторое время до начала. По некоторым причинам это сделало этот расчет невероятно медленным. Моя основная проблема была решена путем исключения их из набора запросов. Я также определил функцию, чтобы сделать это преобразование проще:

FUNCTION fn_interval_to_sec ( i IN INTERVAL DAY TO SECOND )
RETURN NUMBER
IS
  numSecs NUMBER;
BEGIN
  numSecs := ((extract(day from i) * 24
         + extract(hour from i) )*60
         + extract(minute from i) )*60
         + extract(second from i);
  RETURN numSecs;
END;

Ответы [ 6 ]

21 голосов
/ 07 декабря 2011

Существует более короткий, более быстрый и приятный способ получения разницы DATETIME в секундах в Oracle, чем эта волосатая формула с несколькими выдержками.

Просто попробуйте это, чтобы получить время отклика в секундах:

(sysdate + (endtime - starttime)*24*60*60 - sysdate)

Он также сохраняет дробную часть секунд при вычитании TIMESTAMPs.

Подробнее см. http://kennethxu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html.


Обратите внимание, что пользовательские функции pl / sql имеют значительные издержки производительности , которые могут не подходить для сложных запросов.

9 голосов
/ 16 января 2009

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

select avg(cast(endtime as date)-cast(starttime as date))*24*60*60 
  from timings;
7 голосов
/ 18 января 2009

Самый простой способ - написать собственную агрегатную функцию, чтобы сделать это, так как она будет обрабатывать это наиболее чисто (обрабатывает субсекундное разрешение и т. Д.).

Фактически, этот вопрос был задан (и получен ответ) на asktom.oracle.com некоторое время назад (статья содержит исходный код).

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

SQL Fiddle

Настройка схемы Oracle 11g R2 :

Создание типа для использования при выполнении пользовательского агрегирования:

CREATE TYPE IntervalAverageType AS OBJECT(
  total INTERVAL DAY(9) TO SECOND(9),
  ct    INTEGER,

  STATIC FUNCTION ODCIAggregateInitialize(
    ctx         IN OUT IntervalAverageType
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(
    self        IN OUT IntervalAverageType,
    value       IN     INTERVAL DAY TO SECOND
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(
    self        IN OUT IntervalAverageType,
    returnValue    OUT INTERVAL DAY TO SECOND,
    flags       IN     NUMBER
  ) RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(
    self        IN OUT IntervalAverageType,
    ctx         IN OUT IntervalAverageType
  ) RETURN NUMBER
);
/

CREATE OR REPLACE TYPE BODY IntervalAverageType
IS
  STATIC FUNCTION ODCIAggregateInitialize(
    ctx         IN OUT IntervalAverageType
  ) RETURN NUMBER
  IS
  BEGIN
    ctx := IntervalAverageType( INTERVAL '0' DAY, 0 );
    RETURN ODCIConst.SUCCESS;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(
    self        IN OUT IntervalAverageType,
    value       IN     INTERVAL DAY TO SECOND
  ) RETURN NUMBER
  IS
  BEGIN
    IF value IS NOT NULL THEN
      self.total := self.total + value;
      self.ct    := self.ct + 1;
    END IF;
    RETURN ODCIConst.SUCCESS;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(
    self        IN OUT IntervalAverageType,
    returnValue    OUT INTERVAL DAY TO SECOND,
    flags       IN     NUMBER
  ) RETURN NUMBER
  IS
  BEGIN
    IF self.ct = 0 THEN
      returnValue := NULL;
    ELSE
      returnValue := self.total / self.ct;
    END IF;
    RETURN ODCIConst.SUCCESS;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(
    self        IN OUT IntervalAverageType,
    ctx         IN OUT IntervalAverageType
  ) RETURN NUMBER
  IS
  BEGIN
    self.total := self.total + ctx.total;
    self.ct    := self.ct + ctx.ct;
    RETURN ODCIConst.SUCCESS;
  END;
END;
/

Затем вы можете создать собственную функцию агрегирования:

CREATE FUNCTION AVERAGE( difference INTERVAL DAY TO SECOND )
RETURN INTERVAL DAY TO SECOND
PARALLEL_ENABLE AGGREGATE USING IntervalAverageType;
/

Запрос 1 :

WITH INTERVALS( diff ) AS (
  SELECT INTERVAL '0' DAY FROM DUAL UNION ALL
  SELECT INTERVAL '1' DAY FROM DUAL UNION ALL
  SELECT INTERVAL '-1' DAY FROM DUAL UNION ALL
  SELECT INTERVAL '8' HOUR FROM DUAL UNION ALL
  SELECT NULL FROM DUAL
)
SELECT AVERAGE( diff ) FROM intervals

Результаты

| AVERAGE(DIFF) |
|---------------|
|     0 2:0:0.0 |
2 голосов
/ 16 января 2009

Не похоже, что в Oracle есть какая-либо функция для явного преобразования INTERVAL DAY TO SECOND в NUMBER. См. Таблицу в конце этого документа , которая подразумевает, что такого преобразования нет.

Другие источники, похоже, указывают на то, что используемый вами метод - единственный способ получить число из типа данных INTERVAL DAY TO SECOND.

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

select
     avg(
       (extract( second from endtime)  +
        extract ( minute from endtime) * 60 +
        extract ( hour   from  endtime ) * 3600) - 
       (extract( second from starttime)  +
        extract ( minute from starttime) * 60 +
        extract ( hour   from  starttime ) * 3600)
      ) from timings;
1 голос
/ 16 января 2009

Ну, это действительно быстрый и грязный метод, но как насчет сохранения разницы секунд в отдельном столбце (вам потребуется использовать триггер или обновление вручную, если запись изменяется) и усреднения по этому столбцу?

...