如何平均时间间隔?

时间:2009-01-16 14:41:56

标签: sql oracle timestamp ora-00932

在Oracle 10g中,我有一个表,其中包含时间戳,显示某些操作需要多长时间。它有两个时间戳字段:starttime和endtime。我想找到这些时间戳给出的持续时间的平均值。我试试:

select avg(endtime-starttime) from timings;

但是得到:

  

SQL错误:ORA-00932:不一致   数据类型:预期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;

但是真的很慢。

任何更好的方法将间隔转换为秒数,或其他方式做到这一点?

编辑:  真正放慢速度的是我在开始时间之前有一些终结时间。由于某些原因,这个计算非常缓慢。我的基本问题是通过从查询集中删除它们来解决的。我还定义了一个函数来更容易地进行这种转换:

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;

7 个答案:

答案 0 :(得分:21)

在Oracle中,有一种更短,更快,更好的方法可以在几秒钟内获得DATETIME差异,而不是多次提取的毛茸茸公式。

试试这个以秒为单位获得响应时间:

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

当减去TIMESTAMP时,它也会保留小数部分秒。

有关详细信息,请参阅http://kennethxu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html


请注意,custom pl/sql functions have significant performace overhead可能不适合重度查询。

答案 1 :(得分:9)

如果您的结束时间和开始时间不在彼此的秒内,您可以将时间戳作为日期投射并进行日期算术:

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

答案 2 :(得分:7)

最干净的方法是编写自己的聚合函数来执行此操作,因为它将以最干净的方式处理它(处理亚秒级分辨率等)。

事实上,这个问题在asktom.oracle.com前一段时间被提出(并回答)(文章包含源代码)。

答案 3 :(得分:2)

在Oracle中,似乎没有任何功能可以将INTERVAL DAY TO SECOND显式转换为NUMBER。请参阅this document末尾的表格,其中表示没有此类转化。

其他来源似乎表明您使用的方法是从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;

答案 4 :(得分:2)

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

<强> Results

| AVERAGE(DIFF) |
|---------------|
|     0 2:0:0.0 |

答案 5 :(得分:1)

嗯,这是一个非常快速和肮脏的方法,但是如何将秒差异存储在一个单独的列中(如果记录发生变化,您需要使用触发器或手动更新)并对该列进行平均? / p>

答案 6 :(得分:0)

不幸的是,Oracle不支持大多数功能。有很多解决方法,但是它们都有一些缺点(特别是,它们都不符合ANSI-SQL)。

最好的答案(如@justsalt稍后发现)是编写一个自定义函数,将间隔转换为数字,对数字求平均,然后(可选)转换回间隔。 Oracle 12.1和更高版本支持使用WITH块来声明函数:

with
    function fn_interval_to_sec(i in dsinterval_unconstrained)
        return number is
    begin
        return ((extract(day from i) * 24
               + extract(hour from i) )*60
               + extract(minute from i) )*60
               + extract(second from i);
    end;
select numtodsinterval(avg(fn_interval_to_sec(endtime-starttime)), 'SECOND') 
  from timings;

如果您使用的是11.2或更早版本,或者不想在SQL语句中包含函数,则可以将其声明为存储函数:

create or replace function fn_interval_to_sec(i in dsinterval_unconstrained)
    return number is
begin
    return ((extract(day from i) * 24
           + extract(hour from i) )*60
           + extract(minute from i) )*60
           + extract(second from i);
end;

然后可以按预期在SQL中使用它:

select numtodsinterval(avg(fn_interval_to_sec(endtime-starttime)), 'SECOND') 
  from timings;

使用dsinterval_unconstrained

对函数参数使用PL / SQL类型别名dsinterval_unconstrained可确保您具有最大的精度/比例; INTERVAL DAY TO SECOND默认将DAY的精度设置为2位数字(表示±100天或以上的任何内容都会溢出,并引发异常),SECOND则将精度设置为6位数字。

此外,如果您尝试在参数中指定任何精度/小数位数,Oracle 12.1将引发PL / SQL错误:

with
    function fn_interval_to_sec(i in interval day(9) to second(9))
        return number is
        ...

ORA-06553: PLS-103: Encountered the symbol "(" when expecting one of the following: to

替代品

自定义聚合函数

Oracle支持用PL / SQL编写的自定义聚合函数,这将使您对语句进行最小的更改:

select ds_avg(endtime-starttime) from timings;

但是,这种方法有几个主要缺点:

  • 您必须在数据库中创建PL/SQL aggregate objects,这可能是不希望的或不允许的;
  • 您无法将其命名为avg,因为Oracle将始终使用内置的avg函数而不是您自己的函数。 (从技术上讲,您可以,但是您必须使用模式来限定它,这会破坏目的。)
  • 正如@vadzim所指出的,聚合PL / SQL函数具有显着的性能开销。

日期算术

如果您的价值观相差不远,那么@vadzim的方法同样适用:

select avg((sysdate + (endtime-starttime)*24*60*60*1000000 - sysdate)/1000000.0) 
  from timings;

但是请注意,如果间隔太大,则(endtime-starttime)*24*60*60*1000000表达式将溢出并抛出ORA-01873: the leading precision of the interval is too small。以这种精度(1μs),差异的大小不能大于或等于00:16:40,因此对于较小的间隔(并非全部)是安全的。

最后,如果您愿意放弃所有亚秒精度,则可以将TIMESTAMP列强制转换为DATE;从DATE中减去DATE将返回以秒为单位的天数(贷记为@jimmyorr):

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