К сожалению, 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 в вашей базе данных, что может быть нежелательно или разрешено;
- Вы не можете назвать его
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 мкс) разница не может быть больше или равна 00:16:40
по величине, поэтому она безопасна для небольших интервалов, но не для всех.
Наконец, если вам удобно потерять всю точность долей секунды, вы можете преобразовать столбцы TIMESTAMP
в DATE
; вычитание DATE
из DATE
вернет количество дней с точностью до секунды (кредит @jimmyorr):
select avg(cast(endtime as date)-cast(starttime as date))*24*60*60
from timings;
person
Community
schedule
15.06.2020