在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;
答案 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)
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;
但是,这种方法有几个主要缺点:
avg
,因为Oracle将始终使用内置的avg
函数而不是您自己的函数。 (从技术上讲,您可以,但是您必须使用模式来限定它,这会破坏目的。)如果您的价值观相差不远,那么@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;