2009-01-16 15 views
14

In Oracle 10g habe ich eine Tabelle, die Zeitstempel enthält, die zeigen, wie lange bestimmte Operationen dauerte. Es hat zwei Zeitstempelfelder: Startzeit und Endzeit. Ich möchte Durchschnittswerte der von diesen Zeitstempeln angegebenen Dauer finden. Ich versuche:Wie durchschnittliche Zeitintervalle?

select avg(endtime-starttime) from timings; 

aber erhalten:

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

Dies funktioniert:

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

Aber ist wirklich langsam.

Gibt es eine bessere Möglichkeit, Intervalle in Sekunden zu verwandeln, oder auf eine andere Art und Weise?

EDIT: Was wirklich verlangsamte war die Tatsache, dass ich einige Endzeit vor der Startzeit hatte. Aus irgendeinem Grund wurde diese Berechnung unglaublich langsam. Mein zugrunde liegendes Problem wurde gelöst, indem man sie aus dem Abfragesatz eliminierte. I definierte auch nur eine Funktion diese Umwandlung leichter zu tun:

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; 

Antwort

4

Der sauberste Weg ist Ihre eigene Aggregatfunktion zu schreiben, dies zu tun , da es das am saubersten handhabt (behandelt unter einer Sekunde Auflösung, etc.).

In der Tat wurde diese Frage (asktom.oracle.com) vor einer Weile (Artikel enthält Quellcode) gefragt (und beantwortet).

+1

Beachten Sie, dass [custom PL/SQL-Funktionen signifikante Performace Overhead] (https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:60122715103602), die möglicherweise nicht geeignet für schwere Abfragen. – Vadzim

2

Es sieht nicht wie es eine Funktion ist eine explizite Umwandlung von INTERVAL DAY TO SECOND zu NUMBER in Oracle zu tun. Siehe die Tabelle am Ende von this document, die besagt, dass es keine solche Umwandlung gibt.

Andere Quellen scheinen anzuzeigen, dass die von Ihnen verwendete Methode die einzige Möglichkeit ist, eine Zahl aus dem INTERVAL DAY TO SECOND-Datentyp zu erhalten.

Die einzige andere Sache, die Sie in diesem speziellen Fall könnten versuchen, sich zu Zahl zu konvertieren wären vor Abzug, aber da das doppelt so viele extract Ionen tun würde, wird es wahrscheinlich sogar noch langsamer:

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

Nun, das ist eine sehr schnelle und dreckige Methode, aber was ist mit dem Speichern der Sekundendifferenz in einer separaten Spalte (Sie müssen einen Trigger verwenden oder diese manuell aktualisieren, wenn sich der Datensatz ändert) und über diese Spalte gemittelt wird?

+1

Wenn Sie dies tun möchten, können Sie eine Funktion basierende Index (fbi) verwenden, die Sie einen Trigger oder manuelle Aktualisierung einer Spalte speichert . Ein fbi kann im where-clause, aber auch im select-clause verwendet werden. – tuinstoel

9

Wenn Ihr endtime und Startzeit nicht innerhalb einer Sekunde von ihnen, können Sie Ihre Zeitstempel als Daten werfen können und tun Datum Arithmetik:

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

Dadurch werden alle Sekundenbruchteile in den Zeitstempeln verloren (unabhängig davon, ob sie innerhalb einer Sekunde liegen). – MT0

16

Es gibt einen kürzeren, schnelleren und schöneren Weg als diese haarige Formel mit mehreren Extrakten.

Probieren Sie diese Antwortzeit in Sekunden zu erhalten:

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

Es bewahrt auch Bruchteil von Sekunden, wenn Timestamps subtrahiert wird.

Weitere Details finden Sie unter http://kennethxu.blogspot.com/2009/04/converting-oracle-interval-data-type-to.html.


Beachten Sie, dass custom pl/sql functions have significant performace overhead, die für schwere Abfragen nicht geeignet sein können.

+1

scheint die einfachste Lösung bisher. Wäre gut, wenn Oracle dafür eine normale Funktion erstellen könnte. –

+1

Dies multipliziert die Intervalldifferenz mit '24 * 60 * 60 = 86400' und fügt sie dann zu dem Datum hinzu, das das Ergebnis als Datum ergibt und alle Sekundenbruchteile verliert - wenn also die Zeitstempel auf eine Mikrosekunde genau sind kleiner als 1/86400 Sekunden), dann wird es die Genauigkeit verlieren. – MT0

+0

@ MT0, Sie haben Recht. Die Nanosekunden-Genauigkeit für [TIMESTAMP (9)] (https://stackoverflow.com/questions/8987975/oracle-timestamp-data-type) kann mit '(sysdate + (end_ts - start_ts) * 24 * 60 * 60 * erreicht werden. 1000000 - sysdate)/1000000.0'. – Vadzim

0

SQL Fiddle

Oracle 11g R2 Schema-Setup:

eine Art erstellen zu verwenden, wenn Sie eine benutzerdefinierte Aggregation ausführen:

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

Dann können Sie eine benutzerdefinierte Aggregationsfunktion erstellen:

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

Abfrage 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 | 
Verwandte Themen