2009-01-16 9 views
14

En Oracle 10g tengo una tabla que contiene las marcas de tiempo que muestran cuánto tiempo tomaron ciertas operaciones. Tiene dos campos de marca de tiempo: hora de inicio y hora de finalización. Quiero encontrar promedios de las duraciones dadas por estas marcas de tiempo. Trato:¿Cómo se promedian los intervalos de tiempo?

select avg(endtime-starttime) from timings; 

Pero sale:

SQL Error: ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL DAY TO SECOND

esto funciona:

select 
    avg(extract(second from endtime - starttime) + 
     extract (minute from endtime - starttime) * 60 + 
     extract (hour from endtime - starttime) * 3600) from timings; 

Pero es muy lento.

Cualquier forma mejor para convertir intervalos en número de segundos, o de alguna otra manera de hacer esto?

EDITAR: Lo que realmente estaba frenando esto era el hecho de que tenía algunos tiempos de finalización antes de la hora de inicio. Por alguna razón que hizo que este cálculo sea increíblemente lento. Mi problema subyacente se resolvió eliminándolos del conjunto de consultas. También se acaba de definir una función para hacer esto más fácil la conversión:

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; 

Respuesta

4

La manera más limpia es escribir su propia función agregada para hacer esto , ya que manejará esto de la manera más limpia (maneja la resolución por debajo del segundo, etc.).

De hecho, se pidió a esta pregunta (y sus respuestas) sobre asktom.oracle.com un tiempo atrás (artículo incluye código fuente).

+1

Tenga en cuenta que [las funciones pl/sql personalizadas tienen una sobrecarga de rendimiento significativa] (https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:60122715103602) que pueden no ser adecuado para consultas pesadas. – Vadzim

2

No parece que haya ninguna función es hacer una conversión explícita de INTERVAL DAY TO SECOND a NUMBER en Oracle. Consulte la tabla al final de this document que implica que no hay tal conversión.

Otras fuentes parecen indicar que el método que está utilizando es la única forma de obtener un número del tipo de datos INTERVAL DAY TO SECOND.

La única otra cosa que podría intentar en este caso particular sería convertir al número antes de restar ellos, pero desde que va a hacer el doble de extract iones, es probable que sea aún más lento:

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; 
1

Bueno, este es un método realmente rápido y sucio, pero ¿qué pasa con el almacenamiento de la diferencia de segundos en una columna separada (necesitará usar un activador o actualizarlo manualmente si el registro cambia) y promediar sobre esa columna?

+1

Si usted quiere hacer esto, puedes usar un índice basado en la función (FBI) que le ahorra un disparador o actualización manual de una columna . Un fbi se puede usar en la cláusula where, pero también en la cláusula select. – tuinstoel

9

Si el tiempo final y starttime no están dentro de un segundo entre si, puede convertir sus marcas de tiempo como fechas y hacer aritmética de fechas:

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

Esto perderá cualquier fracción de segundo en las marcas de tiempo (independientemente de si están dentro de un segundo el uno del otro). – MT0

16

Hay una, más rápida y más corta mejor que esa fórmula peludo con múltiples extractos.

Simplemente tratar de conseguir este tiempo de respuesta en segundos:

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

También se conserva parte fraccionaria de segundos cuando restando TIMESTAMP.

Ver http://kennethxu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html por algunos detalles.


Tenga en cuenta que custom pl/sql functions have significant performace overhead que puede ser no apto para consultas pesadas.

+1

parece ser la solución más simple hasta ahora. Sería bueno si Oracle pudiera crear una función normal para eso. –

+1

Esto multiplicará la diferencia de intervalo por '24 * 60 * 60 = 86400' y luego lo agregará a la fecha que dará el resultado como una fecha y perderá segundos fraccionarios, por lo que si las marcas de tiempo son precisas a un microsegundo (o cualquier más pequeño que 1/86400 segundos), entonces perderá precisión. – MT0

+0

@ MT0, tienes razón. La precisión de nanosegundos para [TIMESTAMP (9)] (https://stackoverflow.com/questions/8987975/oracle-timestamp-data-type) se puede lograr con '(sysdate + (end_ts - start_ts) * 24 * 60 * 60 * 1000000 - sysdate)/1000000.0'. – Vadzim

0

SQL Fiddle

Oracle 11g R2 Configuración de esquema:

crear un tipo a utilizar cuando se realiza una agregación personalizada:

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; 
/

continuación, puede crear una función de agregación personalizada:

CREATE FUNCTION AVERAGE(difference INTERVAL DAY TO SECOND) 
RETURN INTERVAL DAY TO SECOND 
PARALLEL_ENABLE AGGREGATE USING IntervalAverageType; 
/

consulta 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 | 
Cuestiones relacionadas