不幸的是,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 天或超过 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 聚合对象,这可能是不希望或不允许的;
- 您无法命名它
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
从 a 中减去 aDATE
将返回具有第二精度的天数(归功于@jimmyorr):
select avg(cast(endtime as date)-cast(starttime as date))*24*60*60
from timings;