Как средние интервалы времени?

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

select avg(endtime-starttime) from timings;

Но получим:

Ошибка SQL: ORA-00932: непоследовательный datatypes: ожидается NUMBER INTERVAL DAY TO SECOND

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

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;

Ответы

Ответ 1

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

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

Ответ 2

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

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

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

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

Для получения дополнительной информации см. Http://kenneспасибоu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html.


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

Ответ 3

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

select avg(cast(endtime as date)-cast(starttime as date))*24*60*60 
  from timings;

Ответ 4

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

Другие источники, похоже, указывают, что метод, который вы используете, является единственным способом получить число из типа данных 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;

Ответ 5

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 |

Ответ 6

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