2010-02-03 11 views
12

Ho una colonna DATE che desidero arrotondare all'intervallo successivo inferiore di 10 minuti in una query (vedere l'esempio seguente).Intervallo di arrotondamento a intervalli di 10 minuti

Sono riuscito a farlo troncando i secondi e quindi sottraendo l'ultima cifra dei minuti.

WITH test_data AS (
     SELECT TO_DATE('2010-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:05:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:09:59', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:10:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2099-01-01 10:00:33', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
) 
-- #end of test-data 
SELECT 
    d, TRUNC(d, 'MI') - MOD(TO_CHAR(d, 'MI'), 10)/(24 * 60) 
FROM test_data 

Ed ecco il risultato:

01.01.2010 10: 00:00     01.01.2010 10: 00:00
01.01.2010 10: 05 : 00     01.01.2010 10: 00:00
01.01.2010 10: 09:59     01.01.2010 10: 00:00
01.01.2010 10: 10:00     01.01.2010 10: 10:00
01.01.2099 10: 00 : 33     2099/01/01 10: 00:00

funziona come previsto, ma c'è un modo migliore?

EDIT:

mi incuriosiva prestazioni, così ho fatto la seguente prova con 500.000 righe e (non proprio) date casuali. Ho intenzione di aggiungere i risultati come commenti alle soluzioni fornite.

DECLARE 
    t  TIMESTAMP := SYSTIMESTAMP; 
BEGIN 
    FOR i IN (
    WITH test_data AS (
     SELECT SYSDATE + ROWNUM/5000 d FROM dual 
     CONNECT BY ROWNUM <= 500000 
    ) 
    SELECT TRUNC(d, 'MI') - MOD(TO_CHAR(d, 'MI'), 10)/(24 * 60) 
    FROM test_data 
) 
    LOOP 
    NULL; 
    END LOOP; 
    dbms_output.put_line(SYSTIMESTAMP - t); 
END; 

Questo approccio ha richiesto 03.24 s.

+0

Che dire 'SELECT CASE WHEN TO_CHAR (date_col, 'MI') tra 0 e 10 POI TO_DATE (TO_CHAR (date_col, 'YYYY') || '-' || TO_CHAR (date_col, 'MM') || '-' || TO_CHAR (date_col, 'DD') || '' || TO_CHAR (date_col, 'HH') || ': 00' , 'AAAA-MM-GG HH: MI') END'? –

+0

Ponticini @OMG: ci dispiace, ma restituisce l'ora completa quando '0 <= MI <= 10', altrimenti' NULL'. –

+0

Non volevo ingombrare con il resto del QUARTO –

risposta

10
select 
    trunc(sysdate, 'mi') 
    - numtodsinterval(mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10), 'minute') 
from dual; 

o anche

select 
    trunc(sysdate, 'mi') 
    - mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10)/(24 * 60) 
from dual; 
+0

@Tom: Welcome to StackOverflow! Entrambi gli approcci sembrano funzionare, il primo ha preso "04.18 s", il secondo solo "03.33 s"! Le prestazioni della seconda query sono quasi le stesse della mia query originale, ma evitano 'TO_CHAR', quindi accetterò la risposta. Grazie! –

1

È possibile prendere il valore restituito come stringa e sottostringa nella parte sinistra fino alla cifra dell'ultimo minuto e sostituirlo con 0. Non direi esattamente che è meglio se non fornisci qualche tipo di metrica.

+1

SELECT SYSDATE, TO_DATE (SUBSTR (TO_CHAR (SYSDATE,'YYYYMMDD HH24MI '), 1, 12) ||' 0 ',' YYYYMMDD HH24MI ') DUAL Cosa ha detto nel codice SQL e anche cosa detto per le prestazioni, non sono sicuro che sarebbe meglio, ma il tuo sarebbe più semplice. – Craig

+0

Vedere il test delle prestazioni nella domanda modificata. Questo approccio ha richiesto "04,32 s". –

1
Non

necessariamente migliore, ma un altro metodo:

WITH test_data AS (
     SELECT TO_DATE('2010-01-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:05:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:09:59', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2010-01-01 10:10:00', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
    UNION SELECT TO_DATE('2099-01-01 10:00:33', 'YYYY-MM-DD HH24:MI:SS') d FROM dual 
) 
-- #end of test-data 
SELECT 
    d, TRUNC(d) + FLOOR((d-TRUNC(d))*24*6)/(24*6) 
FROM test_data 
+0

Vedere il test delle prestazioni nella domanda modificata. Questo approccio ha richiesto '04,16 s'. –

+0

Accettato perché è corto e abbastanza leggibile. Grazie! Rimarrà comunque con la mia soluzione, poiché richiede meno tempo. –

+0

@Tony Andrews: accetta la risposta di Tom al posto tuo, dal momento che la trovo più intuitiva e le prestazioni sono migliori. Spero non ti dispiaccia ... –

3

io in genere Odio fare data -> carattere -> Data conversioni quando non è necessario. Preferisco usare i numeri.

select trunc((sysdate - trunc(sysdate))*60*24,-1)/(60*24)+trunc(sysdate) from dual;  

Questo estrae i minuti dal giorno corrente, li tronca verso il basso per l'intervallo di 10 minuti, e poi li aggiunge nuovamente a renderlo un appuntamento di nuovo. Ovviamente, puoi sostituire sysdate con qualsiasi data tu voglia. Si fida di conversioni implicite molto più di quanto voglio ma almeno funzionerà con qualsiasi formato di data NLS.

+0

Vedere il test delle prestazioni nella domanda modificata. Questo approccio ha richiesto "04,39 s". –

1

Un altro metodo,

select my_date - mod((my_date-trunc(my_date))*24*60, 10)/24/60 
from (
    select sysdate my_date from dual 
); 

Un'alternativa che potrebbe essere più veloce in quanto elimina la chiamata a trunc.

select my_date - mod((my_date-to_date('1970', 'yyyy'))*24*60, 10)/24/60 
from (
    select sysdate my_date from dual 
); 
+0

+1: un altro approccio interessante che funziona. Presi '04.60 s' nel mio test, credo che il' mod' renda più lento del mio approccio. –

+0

Ho pubblicato un'alternativa che rimuove una chiamata a trunc. Sarà interessante vedere se c'è qualche differenza nel tuo test. –

+0

@ar: Siamo spiacenti, non è stato notificato il tuo aggiornamento. La tua query aggiornata funziona meglio: '04.22 s' –

0

Per riportare il successivo superiore intervallo di 10 minuti, ho usato la seguente domanda. Spero che sarà utile perché non ho potuto fare semplicemente un

trunc(sysdate, 'mi') + mod(EXTRACT(minute FROM cast(sysdate as timestamp)), 10)/(24 * 60)

ho fatto questo e ha funzionato per me.

select 
 
case when mod(EXTRACT(minute FROM cast(sysdate as timestamp)),5) between 1 and 4 
 
     then trunc(sysdate,'mi')+((5*TRUNC(EXTRACT(minute FROM cast(sysdate as timestamp))/5, 
 
0)+5)-EXTRACT(minute FROM cast(sysdate as timestamp)))/1440 
 
     else trunc(sysdate,'mi') 
 
end 
 
from dual

Questo si basa su this palo.

0

Penso che per risolvere questo problema ci sia un modo molto più semplice e veloce per arrotondare a un intervallo inferiore di 10 secondi, 1 minuto, 10 minuti ecc. Cercare di manipolare il vostro timestamp come una stringa utilizzando SUBSTR() in questo modo:

SELECT 
SUBSTR(datetime(d),1,18)||'0' AS Slot10sec, 
SUBSTR(datetime(d),1,17)||'00' AS Slot1min, 
SUBSTR(datetime(d),1,15)||'0:00' AS Slot10min, 
SUBSTR(datetime(d),1,14)||'00:00' AS Slot1h, 
MY_VALUE 
FROM MY_TABLE; 
Problemi correlati