如何在PL / SQL中迭代日期范围

时间:2009-06-12 16:21:39

标签: sql database oracle plsql

我需要编写一份报告,针对每个记录的日期范围的表生成摘要总计。

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

更新:

我最终使用示例here来完成我想要做的事情。这将创建一个生成日期表的函数。

7 个答案:

答案 0 :(得分:16)

您需要某种日历来循环一系列日期。我使用connect by level技巧构建了一个。然后,您可以将日历与您的数据一起加入(即使在当天没有选项的情况下您想要一行,也可以交叉加入):

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

答案 1 :(得分:14)

我使用的一个解决方案是将日期范围转换为可以在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;

答案 2 :(得分:4)

作为对其他技术的补充,我迭代日期的一种方法如下:

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

答案 3 :(得分:3)

以下是基于以上答案的答案: 它使用开始和结束日期:

列出了2013年7月1日至2013年7月31日的所有日期。可轻松适应任何日期范围。

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;

答案 4 :(得分:0)

如果您有第二个“实用程序”表,则可以最好地处理此类型的查询,您可以将其用于需要将范围转换为特定存储桶的任何查询。实用程序表只不过是一个数字列表:

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;

这个想法是你在迭代器表和你的表之间用范围创建一个笛卡尔积,然后过滤掉你的范围条件不满足的所有情况。您可以在很多地方使用它,并且是最好的例子之一,为什么使用范围而不是离散间隔来更好地建模数据 - 因为您总是可以使用此技术轻松转换为离散间隔。

编辑:我真的不应该使用BETWEEN进行日期范围查询 - 我将其更改为&gt; =&lt;

答案 5 :(得分:0)

declare 
v_curr_date  date;
for i in to_number(to_char(p_date_from ,'j')) .. to_number(to_char(p_date_to 
,'j')) loop

 v_curr_date = to_date(to_char(i),'j'); 
 --make any operation on v_curr_date (like insert into table)

end loop;    

end;

答案 6 :(得分:0)

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