How to iterate over a date range in PL/SQL

71,998

Solution 1

You will need some sort of calendar to loop through a range of date. I have built one using the connect by level trick. You can then join the calendar with your data (cross join since you want a row even when there is no option for that day):

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

Solution 2

One solution that I use for this is to convert the date range into an integer range that you can use in a for loop, then convert back to a date to do stuff with it. You can't do any joins or anything this way, but it's a much smaller solution that those already posted:

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;

Solution 3

Just as an addition to the other techniques, one way I iterate over dates is the following:

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

Solution 4

Here is an answer based on an answer above: It uses a start and end date:

It lists all of the days of 07/01/2013 to 07/31/2013. Easily adaptable to any date range.

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;

Solution 5

Using while loop (better)

declare dfrom date; dtill date; day date; begin dfrom := TO_DATE('09.09.1988', 'dd.mm.yyyy'); dtill := TO_DATE('19.09.1988', 'dd.mm.yyyy'); day := dfrom; WHILE day <= dtill LOOP DBMS_OUTPUT.PUT_LINE(day); day := day + 1; END LOOP; end; / //using cursor declare dfrom date; dtill date; begin dfrom := TO_DATE('09.09.1988', 'dd.mm.yyyy'); dtill := TO_DATE('19.09.1988', 'dd.mm.yyyy'); FOR cur IN ( SELECT dfrom + LEVEL - 1 AS today FROM dual CONNECT BY LEVEL <= dtill - dfrom + 1 ) LOOP DBMS_OUTPUT.PUT_LINE(cur.today); END LOOP; end; /
Share:
71,998
Adam Carr
Author by

Adam Carr

Updated on July 09, 2022

Comments

  • Adam Carr
    Adam Carr almost 2 years

    I need to write a report that generates summary totals against a table with date ranges for each record.

    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
    

    What I want out is basically this:

    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
    

    I am having a hard time figuring out how to iterate over a date range. I am sure this is some simple cursor that could be created for this but I am at a loss. Preferably in PL/SQL

    UPDATE:

    I ended up using the example here to accomplish what I wanted to do. This creates a function that generates a table of dates.

  • Adam Carr
    Adam Carr almost 15 years
    This did exactly what I wanted... better even than the article I reference above. Thanks!
  • Steve Broberg
    Steve Broberg almost 15 years
    +1 - your solution is more efficient than mine below with the extra step to create the left-join base table. Not sure how it would be in the case when the table is indexed.
  • HeyMan
    HeyMan almost 9 years
    works fine :) Note it should be: business_date varchar2(10); instead of varchar2(8);
  • Çağdaş Karademir
    Çağdaş Karademir about 5 years
    Business date is wrong. It should be varchar2(10). Please update for other users!
  • Ahmad
    Ahmad over 2 years
    Definitely, this is the most clear, neat, and best answer. Thank you.