Welcome to EMC Consulting Blogs Sign in | Join | Help

Jim 2.0

Oracle SQL - Generate a list of dates

I have over the last couple of days been working with Jamie Thomson on a sql query to return a daily value inferred from periodic data readings throughout a month. One of the requirements to creating this query was to generate a list of days in the month. Sounds pretty easy except that we couldn't use procedural logic (such as a cursor), so it actually took a little bit of time to figure it out hence I thought it was worthwhile sharing. My thanks go to 'Hampus' for their blog where I found out how to do this.

The database we are querying is in Oracle, so the following sql has been written in TOAD, though I notice Jamie has already posted a corresponding version for SQL Server and he has bullied/cajouled me into posting the Oracle version, which incidentally is much simpler.

    1 select to_date('&date','MM YYYY')-1 + level as DateRange

    2 from    dual

    3 where   (to_date('&date','MM YYYY')-1+level) <= last_day(to_date('&Date','MM YYYY'))

    4 connect by level<=31

    5 ;

Very straightforward once you know how; the range of dates is controled by both the where clause and the connect by (number of levels). In the example above it will only return values for a month up to a maximum of 31 days. You could limit to return only the first half of a month by reducing the connect by level to 15, alternatively you can increase the range to any date range, such as a year by changing the to_date in the where clause. As you can see I'm passing the date variable into the query and using Oracle's date functions to determine the range for me because I feel this is a more user friendly experience for anyone else that has to run my scripts, but you could simply hard code the dates in.

Hope this helps,

James

Published 15 January 2007 16:43 by James.Pipe

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Jim 2.0 said:

In addition to the T-SQL calendar table , which I have posted seperately, I went the whole hog and figured

April 24, 2007 16:17
 

inahurrydba said:

Outstanding! A simple thing, but not so easy. Thank-you for saving me so much effort. Regards.

September 27, 2007 23:13
 

Natesh said:

Great work.. Thanks for knowledge shared

November 6, 2007 08:53
 

Demba said:

Hi

When i execute this request on oracle database

it returns only 25 days

why this???

November 14, 2007 16:06
 

James.Pipe said:

The number of days returned depends on three variables; the start date entered as '&date', the end date in the where clause, which in this case is set to the last day of the month entered, and the maximum number of levels in the 'conect by' clause.

Check what values you have these set too, in particular you may want to change the format of the date variable you have to enter to suite your needs. I have it specified above so that it requires only a 'MM YYYY' and it finds the start of the month from this, so the range returned should be a maximum of 31 days from the start of month to the end of month. If you're not getting this then you may have inadvertantly altered one of these.

November 14, 2007 16:26
 

Rich said:

When I execute the following It only returned 01-OCT-07.  Is this correct?  I was expceting every day of the month.

select to_date('10 2007','MM YYYY')-1 + level as DateRange

from    dual

where   (to_date('10 2007','MM YYYY')-1+level) <= last_day(to_date('10 2007','MM YYYY'))

connect by level<=31

November 26, 2007 19:01
 

Rich said:

I figured it out.  Below works.  Great Job!!

SELECT DateRange

FROM

(

  select to_date('10 2007','MM YYYY')-1 + level as DateRange

  from    dual

  where   (to_date('10 2007','MM YYYY')-1+level) <= last_day(to_date('10 2007','MM YYYY'))

  connect by level<=31

)

ORDER BY DateRange

November 26, 2007 19:09
 

Bill said:

Fantastic stuff James.  I modded it a bit to use a subquery instead of the bind variable - it lets me grab a range of dates from another table dynamically.  Trick is keeping all the subqueries the same and putting the min/max range I'm looking for in the outermost SELECT.

December 10, 2007 15:56
 

paul said:

I am trying to use this query and it is not working for me.  I have a table employee_hours_by_week that contains an employee_id, week_ending_date, and reg_hours which is a total of hours reported for each week in the year (52 records per employee).  when i run the query without the connect by, it works but as soon as I add the connect by, it never returns any values.  here is the query:

SELECT employee_id, week_ending_date, reg_hours

FROM

(

SELECT employee_id, reg_hours, week_ending_date

FROM employee_hours_by_week

WHERE

week_ending_date = to_date('24-Jun-07') - (level * 7)

and employee_id = '14874'

connect by level <= 2

)

Anyone have any ideas why this is not working?

thanks.

December 28, 2007 20:06
 

James.Pipe said:

I think you have 2 problems; the first being that you need the expression to be <your date>-1 + (level*7), since the first level is 1 so you need to account for this otherwise you'll be a day ahead all the way through.

Also, you're multiplying the level by 7, but in the connect by clause you're specifying to end when level exceeds 2, therefore as soon as you start you've already exceeded this as you start with 7 (1*7). I'm assuming you want to stop after 2 weeks, so probably need to enter 14 in your connect by claus (or 2*7) if you prefer.

I must confess I've not tried to use this statement in this way, so my best advice would be to try what I've suggested here and see if that works, otherwise post another comment and we'll see if we can figure it out.

Cheers,

James

December 29, 2007 14:09
 

paul said:

James,

Query is designed to go backwards through time one week at a time for each week ending date (where the week ends on sunday.)  Take this query for example:

SELECT date_range

FROM

(

SELECT to_date('24-jun-07') - (level * 7)date_range

from dual

connect by level <= 6

)

This will return the previous 6 week ending dates from Jun 17 back to May 13.  I do understand that it will skip Jun 24th.

I want this same logic to work with the table in my prior posting, but when I merge the query to return data from my table with the query to go backwards by weeks, even only going back 2 weeks (thus the >= 2), the query never returns an answer, it seems to go into never never land.

Thanks again.

Paul

December 31, 2007 16:17
 

paul said:

I mean only two weeks - (level <= 2)

December 31, 2007 16:18
 

Paul said:

James,

I let the following query run until it returned results and the elapsed time was 1350 seconds:

SELECT employee_id, week_ending_date, reg_hours

FROM

(

SELECT employee_id, reg_hours, week_ending_date

FROM employee_hours_by_week

WHERE

week_ending_date = to_date('24-Jun-07') - (level * 7)

and employee_id = '14874'

connect by level <= 1

)

What is strange is that if I remove the 'connect by' clause, it runs instantaneous because there are only 52 rows in this table for this employee.  I am not sure what this crazy connect by is doing to me.

What I am looking for is a way to do recursion going backwards by week ending date.

Thanks.

Paul

January 4, 2008 13:37
 

James.Pipe said:

i think the reason it works fine if you remove the connect by clause is because then it is just looking for week_ending_date = June 17th.

Apologies for the slow response, but I was on holiday so wasn't able to play around with the actual sql, hence had difficulty understanding exactly what you were doing. I think I can see the problem however; you're returning a range of dates using the connect by expression, yet you're looking for a week_ending_date '=' something, so this will never resolve.

What I think you need to do is put the date range expression in a sub query as you did before e.g.

(

SELECT to_date('24-jun-07') - (level * 7)

from dual

connect by level <= 6

)

and use where week_ending_date IN <result from the sub query>

i.e.

SELECT employee_id, reg_hours, week_ending_date

FROM employee_hours_by_week

WHERE week_ending_date in (

SELECT to_date('24-jun-07') - (level * 7)

from dual

connect by level <= 6

)

Does that make sense? Hopefully that will work.

Thanks,

James

January 7, 2008 19:56
 

Sam Prince said:

If you're using Oracle I think you can use something similar to the approach used in this article:

http://www.orafaq.com/node/1884

Note the bit where he writes:

"

...

RIGHT OUTER JOIN

      (SELECT   Calendar_Dt

       FROM     TABLE (Tfn_date_range (p_dt_Start, p_dt_End))) c

ON (t.Sale_Date = c.Calendar_Dt)

The above query returns all detail data that needs to be analyzed. Note that it uses the table function tfn_date_rnge as one of its data sources, which returns a set of calendar dates within the specified range."

(He has a typo somehwere - it should be either "rnge" or "range" - but I don't have an Oracle instance to test against.)

April 11, 2008 12:51
 

LTSpeed said:

This might work better for some people...

SELECT

       TO_CHAR(TO_DATE('04/01/2006', 'mm/dd/yyyy') + (LEVEL-1),'DD-MON-YYYY') datelist

FROM

       DUAL

CONNECT BY

       TO_NUMBER(TO_CHAR((TO_DATE('04/01/2006','mm/dd/yyyy') + (LEVEL-1)),'YYYYMMDD')) <= TO_NUMBER(TO_CHAR(SYSDATE+365,'YYYYMMDD'))

It will return all dates after 4/1/06 thru 'today+365'.

June 10, 2008 12:43
 

Jill said:

I was having problems with the to_date when I needed to pass in a date, so I found this to work:

SELECT     LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE), -1)) + LEVEL daterange

     FROM DUAL

    WHERE LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE), -1)) + LEVEL <= TRUNC(SYSDATE)

CONNECT BY LEVEL <= 31

June 17, 2008 06:13
 

Robert said:

Thanks very much for everyone who contributed to this. I was googling for a query like this and your work has really helped us.

Thanks once again.

September 17, 2008 21:20
 

Phani said:

Very useful SQLs. Great job guys.

Thanks.

October 15, 2008 00:49
 

Vasily said:

You don't need where clause and end date. Start date and level that's what matters:

SELECT TO_DATE ('2008-01', 'yyyy-mm') - 1 + LEVEL AS daterange

FROM DUAL

CONNECT BY LEVEL <= 366

February 19, 2009 04:55
 

gln sarma said:

Excellent solved my problem

March 13, 2009 12:50
 

gurdipe said:

great piece of sql how could you modify it to take a start and end date?

April 23, 2009 12:43
 

Vinoir said:

Just what I was looking for - many thanks!

April 23, 2009 18:28
 

Joe said:

Not what I was looking for, but thanks anyways.

August 3, 2009 04:51
 

Rasmatazz said:

Thanks a lot for that query. It has save me a lot of time

September 11, 2009 17:52
 

Jean said:

This query works great if I need only one row per date.  However, my requirement was to get a date entry for every 5 minutes.   I ended up writing a custom function.  I have listed the function at http://meslie.blogspot.com/2009/10/oracle-generate-list-of-dates-with-time.html

October 20, 2009 03:44
 

Tey Brady said:

Someone asked how to return all days in a date range. I used a formula to set level to the number of days you want returned.  End date - beg date + 1

select to_date('1-Oct-2007') - 1 + level as DateRange

  from dual

connect by level <= (to_date('15-Nov-2007') - to_date('01-Oct-07') + 1);

I needed to know the last day of the month in a range of dates so I did this.  It returns the last day of the month every level of the connect and takes the distinct values.  

select distinct last_day(to_date('1-Oct-2007') - 1 + level) as DateRange

  from dual

connect by level <= (to_date('1-Jan-2008') - to_date('01-Oct-07') + 1);

November 20, 2009 16:42
 

garvin said:

great !

February 10, 2010 21:18
 

cheap nike shox said:

I totally agree the standpoint of upstairs, and I believe this will be a trend. I often come this forum , rom here I learn much and know the newest tide! the content here  constantly update and I love it! Another I know some websites which often update their contents, you guys should browse if you are free.

May 31, 2010 02:36
 

Awais said:

Hi Jim,

Nice post. Keep up the good work.

Also, there exists a little modification to your code at http://aspdotnetcode.source-of-humor.com/CodeSnippets/Oracle/HowToGenerateDatesSequenceColumn.aspx which can be used to auto generate all dates present in the specified date range.

Thank you

July 2, 2010 06:01
 

Cornell said:

Vasily's comment:

"You don't need where clause and end date. Start date and level that's what matters:

SELECT TO_DATE ('2008-01', 'yyyy-mm') - 1 + LEVEL AS daterange

FROM DUAL

CONNECT BY LEVEL <= 366

"

assumes that there are 366 days in a year.  This isn't always true and one would need to change the hardcoded value on a regular basis.

Also, I wanted it to return the days in the month for any arbitrary date (instead of giving it the first of the month).  Jill's attempt was more elegant than mine, though here's end's with the date in question instead of the end of the month.

So I ended up with this:

SELECT LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE), -1)) + LEVEL daterange

 FROM DUAL

WHERE LAST_DAY(ADD_MONTHS(TRUNC(SYSDATE), -1)) + LEVEL <= last_day(TRUNC(SYSDATE))

CONNECT BY LEVEL <= 31

(note that the 31 in the connect by, can be any number >= the maximum days in any month, the actual termination occurs from the where clause)

July 8, 2010 14:54
 

Pam said:

Very nice !

Anyone can help me with this ?

Why the query takes forever to run if I use the column name for ' Connect by level < Duration ' ( Duration is the number column for how many days in my table )

If I put the constant like this 'Connect by level <10  and query returns instantaneous.

SELECT customer,current_date-level,qty

from factor

connect by level < duration

July 26, 2010 15:15
 

Piyush said:

Thanks a lot James for sharing this. This is what I was looking for. I have modified it further to get hourly rows just be using "level/24".

Also, do you know any such shortcut way of handling Day Light Savings? Basically Handling long and short days...?

Thanks

Piyush

July 26, 2010 18:07
 

Gabriel said:

AWESOME!!! This was exactly what I was looking for!! Thanks a lot for sharing!!

August 13, 2010 20:34

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server (Personal Edition), by Telligent Systems