2009-06-12 13 views
6

Devo scrivere un rapporto che generi totali di riepilogo su una tabella con intervalli di date per ogni record.Come eseguire l'iterazione su un intervallo di date in PL/SQL

table data: 
option start_date end_date 
opt1  6/12/2009 6/19/2009 
opt1  6/3/2009  6/13/2009 
opt2  6/5/2009  6/6/2009 

Quello che voglio fuori è fondamentalmente questo:

date  option count 
6/1/2009 opt1  0 
6/1/2009 opt2  0 
6/2/2009 opt1  0 
6/2/2009 opt2  0 
6/3/2009 opt1  0 
6/3/2009 opt2  1 

Sto avendo difficoltà a capire come un'iterazione su un intervallo di date. Sono sicuro che questo è un semplice cursore che potrebbe essere creato per questo, ma io sono in perdita. Preferibilmente in PL/SQL

UPDATE:

ho finito per usare l'esempio here per realizzare quello che volevo fare. Questo crea una funzione che genera una tabella di date.

risposta

14

Avrete bisogno di una sorta di calendario per scorrere un intervallo di date. Ne ho costruito uno usando il trucco connect by level. È quindi possibile unire il calendario con i tuoi dati (cross join poiché si desidera una riga, anche se non esiste alcuna opzione per quel giorno):

SQL> WITH calendar AS (
    2  SELECT to_date(:begin_date, 'mm/dd/yyyy') + ROWNUM - 1 c_date 
    3  FROM dual 
    4  CONNECT BY LEVEL <= to_date(:end_date, 'mm/dd/yyyy') 
          - to_date(:begin_date, 'mm/dd/yyyy') + 1 
    5 ) 
    6 SELECT c_date "date", d_option "option", COUNT(one_day) 
    7 FROM (SELECT c.c_date, d.d_option, 
    8     CASE 
    9      WHEN c.c_date BETWEEN d.start_date AND d.end_date THEN 
10      1 
11     END one_day 
12    FROM DATA d, calendar c) 
13 GROUP BY c_date, d_option 
14 ORDER BY 1,2; 

date  option COUNT(ONE_DAY) 
----------- ------ -------------- 
01/06/2009 opt1    0 
01/06/2009 opt2    0 
02/06/2009 opt1    0 
02/06/2009 opt2    0 
03/06/2009 opt1    1 
03/06/2009 opt2    0 
04/06/2009 opt1    1 
04/06/2009 opt2    0 
05/06/2009 opt1    1 
05/06/2009 opt2    1 
06/06/2009 opt1    1 
06/06/2009 opt2    1 

12 rows selected 
+0

Questo ha fatto esattamente quello che volevo ... meglio anche l'articolo di cui sopra. Grazie! –

+0

+1: la soluzione è più efficiente della mia in basso con il passaggio in più per creare la tabella di base di join sinistro. Non sei sicuro di come sarebbe nel caso in cui la tabella è indicizzata. –

0

Questo tipo di query è meglio gestito se si dispone di un secondo "utility" tabella, che è possibile utilizzare per qualsiasi query in cui è necessario convertire intervalli in specifici bucket. La tabella di utilità non è altro che un elenco di numeri:

CREATE TABLE Iterator (Counter NUMBER); 

COUNTER 
------- 
     0 
     1 
     2 
     3 
... 
    100 (or however many rows you want to include) 

se si assume che si desidera visualizzare 30 giorni, per esempio

SELECT TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter thedate 
     , i.My_option 
     , count(y.My_option) 
    FROM (SELECT DISTINCT 
        i2.Counter 
       , y.My_option 
      FROM iterator i2 
       , YourTable y 
      WHERE i2.Counter < 5 
     ) i 
      LEFT OUTER JOIN yourtable y 
          ON TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
           >= y.start_date 
          AND TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
           < y.end_date 
          AND y.My_option = i.My_option 
GROUP BY TO_DATE('6/1/2009', 'MM/DD/YYYY') + i.counter 
     , i.My_option 
ORDER BY 1 
     , 2; 

L'idea è che si crea un prodotto cartesiano tra il tavolo iteratore e il vostro tavolo con la gamma, poi filtrare tutti i casi in cui non siano soddisfatte le vostre condizioni di gamma. Puoi usarlo in molti posti, ed è uno dei migliori esempi perché è meglio modellare i tuoi dati con intervalli diversi da intervalli discreti - perché puoi sempre convertire facilmente a intervalli discreti usando questa tecnica.

edit: io in realtà non dovrebbe usare TRA per la gamma di data query - ho cambiato in> = <

4

Proprio in aggiunta alle altre tecniche, in un modo che iterare date è la seguente:

/* List of days for the past year, starting with today at midnight */ 
SELECT TRUNC(SYSDATE) + 1 - LEVEL AS today, 
     TRUNC(SYSDATE) + 2 - LEVEL AS tomorrow 
FROM DUAL 
CONNECT BY LEVEL <= 365 
8

Una soluzione che uso per questo è convertire l'intervallo di date in un intervallo intero che è possibile utilizzare in un ciclo for, quindi riconvertire in una data per fare cose con esso. Non si può fare alcun join o qualsiasi cosa in questo modo, ma è una soluzione molto più piccolo che quelli già postato:

declare 
    start_date number; 
    end_date number; 
    business_date varchar2(8); 
begin 
    start_date := to_number(to_char(to_date('2013-04-25', 'yyyy-MM-dd'), 'j')); 
    end_date := to_number(to_char(to_date('2013-05-31', 'yyyy-MM-dd'), 'j')); 
    for cur_r in start_date..end_date loop 
    business_date := to_char(to_date(cur_r, 'j'), 'yyyy-MM-dd'); 
    dbms_output.put_line(business_date); 
    end loop; 
end; 
+0

funziona bene :) Nota che dovrebbe essere: business_date varchar2 (10); invece di varchar2 (8); – HeyMan

3

Ecco una risposta basata su una risposta di cui sopra: Esso utilizza una data di inizio e di fine:

Elenca tutti i giorni dal 07/01/2013 al 31/07/2013. Facilmente adattabile a qualsiasi intervallo di date.

SELECT to_date('07/01/2013', 'mm/dd/yyyy') + LEVEL - 1 AS today 
FROM dual 
CONNECT BY LEVEL <= to_date('07/31/2013', 'mm/dd/yyyy') - to_date('07/01/2013', 'mm/dd/yyyy') + 1; 
Problemi correlati