Как выполнить итерацию по диапазону дат в 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

то, что я хочу, в основном это:

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

мне трудно понять, как перебирать диапазон дат. Я уверен, что это какой-то простой курсор, который может быть создан для этого, но я в недоумении. Предпочтительно в PL / SQL

обновление:

Я закончил использование примера здесь выполнить что я хотел сделать. Это создает функцию, которая генерирует таблицу дат.

5 ответов


вам понадобится какой-то календарь для цикла через диапазон дат. Я построил один, используя подключение на уровне - трик. Затем вы можете присоединиться к календарю с вашими данными (перекрестное присоединение, так как вы хотите строку, даже если нет опции для этого дня):

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

одно из решений, которое я использую для этого, - преобразовать диапазон дат в целочисленный диапазон, который вы можете использовать в цикле for, а затем преобразовать обратно в дату, чтобы сделать с ней что-то. Вы не можете делать какие-либо соединения или что-то в этом роде, но это гораздо меньшее решение, которое уже опубликовано:

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;

как дополнение к другим методам, один из способов, которым я повторяю даты, следующий:

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

вот ответ, основанный на ответ выше : Он использует дату начала и окончания:

в нем перечислены все дни с 07/01/2013 по 07/31/2013. Легко адаптируется к любому диапазону дат.

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;

этот тип запроса лучше всего обрабатывать, если у вас есть вторая таблица "утилита", которую вы можете использовать практически для любого запроса, где вам нужно преобразовать диапазоны в определенные ведра. Служебная таблица представляет собой не что иное, как список чисел:

CREATE TABLE Iterator (Counter NUMBER);

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

если мы предположим, что вы хотите отобразить 30 дней, например

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;

идея заключается в том, что вы создаете Декартовое произведение между таблицей итератора и таблицей с диапазоном, а затем отфильтровываете все случаи, когда условия диапазона не выполняются. Вы можете использовать это во многих местах, и это один из лучших примеров, почему лучше моделировать ваши данные с диапазонами, а не дискретными интервалами - потому что вы всегда можете легко конвертировать в дискретные интервалы с помощью этой техники.

edit: я действительно не должен использовать между запросами диапазона дат - я изменил его на >=