不幸的是,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;