Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Tonny.

Asked: April 12, 2004 - 11:54 am UTC

Last updated: June 16, 2006 - 7:07 pm UTC

Version: 9.0.1

Viewed 1000+ times

You Asked

I am working on a report where quarter start date is arbitary lets say if user enter
Jan 01, 2001 then 4 quarters will be
Jan 2001 to Mar 2001
Apr 2001 to Jun 2001
Jul 2001 to Sep 2001
Oct 2001 to Nov 2001

Where as if he/she enters

Feb 01 2001 then
Feb 2001 to Apr 2001
May 2001 to Jul 2001
Aug 2001 to Oct 2001
Nov 2001 to Jan 2002

Similarly a little more complicated will be including start date
Feb 10, 2001 then
Feb 10, 2001 to May 9, 2001
May 10, 2001 to Aug 9, 2001
Aug 10, 2001 to Nov 9, 2001
Nov 10, 2001 to Feb 9, 2002

I can't think of any way other then writing a PL/SQL function for it. Is there a efficient way to do it. I have been amazed by some of the queries from you on this forum.
thanks for such a great technical forum.
-tonny
- Sorry I corrected the last case in question. I didn't calculate well. What I am asking is how too do quarters when they start and end on particular dates with 3 months difference in between.
thanks,
-tonny

and Tom said...

well, that is easy -- user gives you input in the form of :input and you

ops$tkyte@ORA9IR2> variable input varchar2(25)
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :input := '01-jan-2001'

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select 'q1', add_months(to_date(:input,'dd-mon-yyyy'),0), add_months(to_date(:input,'dd-mon-yyyy'),3)-0.00001
2 from dual union all
3 select 'q2', add_months(to_date(:input,'dd-mon-yyyy'),3), add_months(to_date(:input,'dd-mon-yyyy'),6)-0.00001
4 from dual union all
5 select 'q3', add_months(to_date(:input,'dd-mon-yyyy'),6), add_months(to_date(:input,'dd-mon-yyyy'),9)-0.00001
6 from dual union all
7 select 'q4', add_months(to_date(:input,'dd-mon-yyyy'),9), add_months(to_date(:input,'dd-mon-yyyy'),12)-0.00001
8 from dual
9 /

'Q ADD_MONTHS(TO_DATE(: ADD_MONTHS(TO_DATE(:
-- -------------------- --------------------
q1 01-jan-2001 00:00:00 31-mar-2001 23:59:59
q2 01-apr-2001 00:00:00 30-jun-2001 23:59:59
q3 01-jul-2001 00:00:00 30-sep-2001 23:59:59
q4 01-oct-2001 00:00:00 31-dec-2001 23:59:59

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :input := '01-feb-2001'

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> /

'Q ADD_MONTHS(TO_DATE(: ADD_MONTHS(TO_DATE(:
-- -------------------- --------------------
q1 01-feb-2001 00:00:00 30-apr-2001 23:59:59
q2 01-may-2001 00:00:00 31-jul-2001 23:59:59
q3 01-aug-2001 00:00:00 31-oct-2001 23:59:59
q4 01-nov-2001 00:00:00 31-jan-2002 23:59:59

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec :input := '10-feb-2001'

PL/SQL procedure successfully completed.

ops$tkyte@ORA9IR2> /

'Q ADD_MONTHS(TO_DATE(: ADD_MONTHS(TO_DATE(:
-- -------------------- --------------------
q1 10-feb-2001 00:00:00 09-may-2001 23:59:59
q2 10-may-2001 00:00:00 09-aug-2001 23:59:59
q3 10-aug-2001 00:00:00 09-nov-2001 23:59:59
q4 10-nov-2001 00:00:00 09-feb-2002 23:59:59




Rating

  (9 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Try this

Ashok K Rathi, April 13, 2004 - 10:25 am UTC

select add_months(add_months(to_date('01-feb-03') ,(rownum * 3)), -3) ,
(add_months(to_date('01-feb-03') ,(rownum * 3)) -1) QRT_END from all_objects where rownum < 100


Tom Kyte
April 13, 2004 - 10:54 am UTC

why -- that doesn't answer the question

question wanted four rows for a user input date.



A reader, April 13, 2004 - 3:33 pm UTC

Why -> Query looks more compact and may come to use when you need more then 4 rows .

One can get 4 rows just by changing the query rownum < 5

why it is not counting 30/31 days and if leap year 29 elase 28 ?

A reader, August 20, 2004 - 1:02 pm UTC

I just want to add 1 month to existing date.

but,..
I ran this query and it looks like it is not counting 30 or 31 days in the month ?


SELECT SYSDATE,
ADD_MONTHS (SYSDATE, 1),
ADD_MONTHS (SYSDATE, 2),
ADD_MONTHS (SYSDATE, 3),
ADD_MONTHS (SYSDATE, 4),
ADD_MONTHS (SYSDATE, 5),
ADD_MONTHS (SYSDATE, 6)
FROM DUAL;

output:

SYSDATE ADD_MONTHS(SYSDATE,1) ADD_MONTHS(SYSDATE,2) ADD_MONTHS(SYSDATE,3) ADD_MONTHS(SYSDATE,4) ADD_MONTHS(SYSDATE,5) ADD_MONTHS(SYSDATE,6)

8/20/2004 12:45:33 PM 9/20/2004 12:45:33 PM 10/20/2004 12:45:33 PM 11/20/2004 12:45:33 PM 12/20/2004 12:45:33 PM 1/20/2005 12:45:33 PM 2/20/2005 12:45:33 PM


all added dates are 20 . It don't count the moths30/31 days ?



Tom Kyte
August 21, 2004 - 11:02 am UTC

the documented output of add months is:

if you add N months to a date, and the date is not the last day of the month, you get the same day in the month that is N months from them

that is, if you add 1 month to august 20th, you will get the 20th of september -- that is the same day in the next month.

if you add 1 month to the last day of a month, you get the last day of the NEXT month.

that is what is it documented to do. that is what most people would expect.



Thanks,

A reader, August 23, 2004 - 5:48 pm UTC

so how do I get what I want ? can you guide me ?

Tom Kyte
August 23, 2004 - 7:44 pm UTC

describe "what you want" and we'll see.

put it into english, spec it out - no idea what you "want"

if you add a month to a date in august (with 31 days) should it be 30 or 31 days into the future. and if you added N months (n > 1) what then?????

How to get No. of Quarters between two Quarters?

A reader, June 14, 2006 - 11:22 am UTC

Lets say i have two quarters
Variable 1 : year=2005 and Quarter=4
Variable 2 : year=2004 and Quarter=2

I want to find how many quarters are there between these 2 quarters?

One solution i came up with [looks like a weird one..]

Convert yearqtr to a valid date ..
eg: 20054 -> to_date('20051231','mmddyyyy') --Last day of the 4th qtr
20042 -> to_date('20040630','mmddyyyy') --Last day of the 2nd qtr

select (to_date('20051231','mmddyyyy')-to_date('20040630','mmddyyyy'))/90 from dual

gives me correct qtrs between them.

But does it work with all months [ Leap year, etc..]?
Can you provide me with a better solution?

Thanks,

Tom Kyte
June 14, 2006 - 12:42 pm UTC

convert yyyy qtr into a date (first day of the month of the quarter)

months_between() the two resulting dates

divide by 3.

don't use days, use months_between.

Can I covert using a function or ..

A reader, June 14, 2006 - 12:46 pm UTC

do i have to hard code first day of the month for each quarter?
like if qtr=1 then '0101'||year
elseif qtr=2 then '0104'||year ..etc

can i use to_date('20054','yyyyQ') from dual..
something like that.

Thanks,

Tom Kyte
June 15, 2006 - 8:05 am UTC

'math' works well - multiply :)

q cannot be in an input format



ops$tkyte@ORA10GR2> select q, 3*q-2 month
  2  from (select level q from dual connect by level <= 4)
  3  /

         Q      MONTH
---------- ----------
         1          1
         2          4
         3          7
         4         10

 

Neat trick to get first Month from Quarter, But.

A reader, June 15, 2006 - 9:56 am UTC

It doesn't help if i have to get first day of the month. Does it?

I can use last_day() function to get last day of that month.but, I do not know if there is first_day() function. So I still have to hardcode day.

Sorry If I did not understand your response properly..

Tom Kyte
June 15, 2006 - 4:44 pm UTC

eh? first day is always "1"

hard code "1"



First day of month

Thijs, June 16, 2006 - 7:49 am UTC

Given an arbitrary date in a month, you can get the first day of the month with TRUNC(datevar, 'MON').


Tom Kyte
June 16, 2006 - 7:07 pm UTC

not needed here, just need the number "1"

Here is the example in full I guess:

ops$tkyte@ORA10GR2> variable d1 varchar2(10);
ops$tkyte@ORA10GR2> variable d2 varchar2(10);
ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> exec :d1 := '20054'; :d2 := '20042'

PL/SQL procedure successfully completed.

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> select to_date( substr(:d1,1,4) || to_char( substr(:d1,5)*3-2, 'fm00' ) || '01', 'yyyymmdd' ) d1,
  2         to_date( substr(:d2,1,4) || to_char( substr(:d2,5)*3-2, 'fm00' ) || '01', 'yyyymmdd' ) d2
  3    from dual;

D1        D2
--------- ---------
01-OCT-05 01-APR-04

ops$tkyte@ORA10GR2>
ops$tkyte@ORA10GR2> select months_between( d1, d2 )/3
  2    from (
  3  select to_date( substr(:d1,1,4) || to_char( substr(:d1,5)*3-2, 'fm00' ) || '01', 'yyyymmdd' ) d1,
  4         to_date( substr(:d2,1,4) || to_char( substr(:d2,5)*3-2, 'fm00' ) || '01', 'yyyymmdd' ) d2
  5    from dual
  6         );

MONTHS_BETWEEN(D1,D2)/3
-----------------------
                      6
 

That's what I was looking for :-)

A reader, June 19, 2006 - 9:39 am UTC

Thanks Tom, You nailed it. That exactly what i was trying to do. Thanks a bunch.
Also, I should thank "Thijs" from harleem, for mentioning 'MON' to get first day of the month. I did not know it :-).

Thank you both.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library