2009-06-12 43 views
6

Necesito escribir un informe que genere totales resumidos en una tabla con intervalos de fechas para cada registro.Cómo iterar en un rango de fechas en 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 

lo que quiero es básicamente el siguiente:

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 

Estoy teniendo un tiempo difícil encontrar la manera de iterar sobre un intervalo de fechas. Estoy seguro de que este es un cursor simple que podría crearse para esto, pero estoy perdido. Preferentemente en PL/SQL

ACTUALIZACIÓN:

Terminé usando el ejemplo here para lograr lo que quería hacer. Esto crea una función que genera una tabla de fechas.

Respuesta

14

Necesitará algún tipo de calendario para recorrer un rango de fecha. He creado uno usando el truco connect by level. A continuación, puede unirse al calendario con sus datos (combinación cruzada, ya que desea una fila, incluso cuando no hay ninguna opción para ese día):

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

Esto hizo exactamente lo que yo quería ... mejor incluso que el artículo que menciono arriba. ¡Gracias! –

+0

+1: su solución es más eficiente que la mía a continuación con el paso adicional para crear la tabla base de unión izquierda. No estoy seguro de cómo sería en el caso cuando la tabla esté indexada. –

0

Este tipo de consulta se maneja mejor si tiene un segundo "utilidad" tabla, que puede usar para cualquier consulta en la que necesite convertir rangos en grupos específicos. La tabla de utilidad no es más que una lista de números:

CREATE TABLE Iterator (Counter NUMBER); 

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

si asumimos que desea mostrar los 30 días, por ejemplo,

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; 

La idea es que se crea un producto cartesiano entre su mesa de iterador y su mesa con la gama, a continuación, filtrar todos los casos en que no se cumplen sus condiciones de alcance. Puede usar esto en muchos lugares, y es uno de los mejores ejemplos de por qué es mejor modelar sus datos con rangos en lugar de intervalos discretos, porque siempre puede convertir fácilmente a intervalos discretos usando esta técnica.

edición: Realmente no se debe utilizar para consultas ENTRE período - lo cambié a> = <

4

Así como una adición a las otras técnicas, de una manera que iterar sobre las fechas es la siguiente:

/* 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 solución que uso para esto es convertir el rango de fechas en un rango entero que puede usar en un ciclo for, luego convertir de nuevo a una fecha para hacer cosas con él. No se puede hacer ninguna une o algo así, pero es una solución mucho más pequeños que los que ya colocado:

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

funciona bien :) Tenga en cuenta que debería ser: business_date varchar2 (10); en lugar de varchar2 (8); – HeyMan

3

Aquí es una respuesta basada en una respuesta anterior: Utiliza una fecha de inicio y fin:

Lista todos los días del 01/07/2013 al 31/07/2013. Fácilmente adaptable a cualquier rango de fechas.

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