I need to find some records created in a range of quarters. For example, I'm looking for all records created between the 4th quarter of 2008 and the 1st quarter of 2010. I have this in my WHERE-clause:
...and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
but Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened. Is this even a valid way to find values in between calender quarters, or is there a better way?
Also interesting, and possibly related, if I execute this:
select to_date('2009','YYYY') from dual;
The value displayed in my IDE is 2009-08-01. I would have expected 2009-08-04, since today is 2010-08-04.
This:
select to_date('2009 1','YYYY Q') from dual;
of course, fails.
(Oracle 10g)
Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened.
See the second column of table 2.15 at http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34948. Not all format elements are allowed when converting to dates, timestamps, etc.
I recommend against using between for date range checks. People often will miss values within the ending day that the expect to be included. So I would translate:
and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
To
and to_date('2008-10-01', 'YYYY-MM-DD') <= r.record_create_date
and record_create_date < to_date('2010-04-01', 'YYYY-MM-DD') -- < beginning of 2Q2010.
Someone asked the same question on OTN: http://forums.oracle.com/forums/thread.jspa?threadID=1081398&tstart=255
The crux of the issue is that you can not specify "Q" in the TO_DATE function.
Given that you're already specifying a portion of the date, why not provide the entire date? Mind too that to_date('2010 1','YYYY Q') would give you Jan 1st, 2010 when you really want March 31st, 2010... at a second to midnight.
Since the relationship between quarters to months is one-to-many, it doesn't make sense to do TO_DATE('2008 1', 'yyyy q'); what date should be returned? The first of the quarter, the end of the quarter, ...? (On the other hand, converting a date to a quarter - like TO_CHAR(SYSDATE, 'yyyy q') makes sense because a specific date only exists in one quarter.)
So, if you do want a query that looks for a date that falls between two quarters, you will have to "rolll your own" (explicitly stating the dates of the start/end of a quarter.)
As a side note, in case anyone is considering not using TO_DATE please do not use things like: WHERE date_value BETWEEN 'date string1' and 'date string2' without the TO_DATE function. It assumes a default date format and under certain situations can avoid potentially useful indexes altogether.
Below is one example where the same query can have a different result.
select sysdate from dual where sysdate between '1-Jan-10' and '31-Dec-10';
SYSDATE
---------
04-AUG-10
SQL> alter session set nls_date_format = 'YYYY-MM-DD';
Session altered.
SQL> select * from dual where sysdate between '1-Jan-10' and '31-Dec-10';
no rows selected
(Notice that in the second instance no error is returned. It just assumes Jan 10, 0001 and Dec. 10th, 0031.)
I think the best way is to just input the quarter start date and quarter end dates without even bothering with to_date. I think if you use
between '1-Jan-10' and '31-Dec-10'
for example, then you don't (in Oracle I believe) need to_date and it isn't much more difficult than typing in the quarter number
To calculate in Oracle the first day of a quarter and the last day of a quarter from the year and quarter:
I Use the fact
start_month= -2 + 3 * quarter
last_month = 3 * quarter
variable v_year number
variable v_quarter number
exec :v_year :=2017
exec :v_quarter:=4
select :v_year as year,
:v_quarter as quarter,
to_date(:v_year||to_char(-2+3*:v_quarter,'fm00'),'yyyymm') as quarter_start,
last_day(to_date(:v_year||to_char(3*:v_quarter,'fm00')||'01 23:59:59','yyyymmdd hh24:mi:ss')) as quarter_end
from dual a;
YEAR|QUARTER|QUARTER_START |QUARTER_END
2017| 4|2017-10-01 00:00:00|2017-12-31 23:59:59
Related
I am trying to write a query using SQL in the Maximo 7.5 UI advanced search function which will return data from the month before the month in which the query was run. I want to save this query and make it available to users who will run the query without editing it. For example, if a user ran the saved query on 1/25/2019, the query would return all records for which the date was any day in December 2018. I have previously used "where actfinish >= sysdate-30" but the length on months varies and I cannot rely on the users (who do not write SQL) to always run the query on the first day of each month, so I need the query to filter by the previous month. The field I am filtering on is a DATE field, but in the DB it looks like DD-MMM-YY.
Your current where actfinish >= sysdate-30 will return data from the current month, as well as the issues you mentioned.
You can do something like:
where actfinish >= add_months(trunc(sysdate, 'MM'), -1)
and actfinish < trunc(sysdate, 'MM')
The trunc(sysdate, 'MM') gives you midnight on the first day of the current month. The first clausesubtracts a month from that, so gives you midnight on the first day of the previous month; the second clause prevent any record from this month being included.
You can check what those evaluate to by querying the terms outside your real query:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
select sysdate,
add_months(trunc(sysdate, 'MM'), -1) as month_from,
trunc(sysdate, 'MM') as month_to
from dual;
SYSDATE MONTH_FROM MONTH_TO
------------------- ------------------- -------------------
2019-01-25 12:59:53 2018-12-01 00:00:00 2019-01-01 00:00:00
The alter session is just to make the client format the results in a particular way, instead of explicitly doing to_char().
When you said "in the DB it looks like DD-MMM-YY", it doesn't actually look like that in the database; when you query the date values your client is formatting the dates like that, so your NLS_DATE_FORMAT is probably set to the still-default DD-MON-RR model. (And it's MON in Oracle, not MMM - see the format model elements in the docs.)
Your solution will depend on the type of database that is being used.
For SQL Server, we have used logic along the following lines:
actfinish between dateadd(day,1,eomonth(dateadd(month,-2,getdate()))) and eomonth(dateadd(month,-1,getdate()))
(identify the last day of the previous month and the last day of the month before that + 1 day)
For Oracle, the equivalent would be:
trunc(actfinish,'MONTH') = add_months(trunc(sysdate,'MONTH'),-1)
(identify the year and month associated with last month and compare that against the target date components - trunc month function removes the time and day of month components so making comparisons easier)
I have previously used "where actfinish >= sysdate-30" but the length
on months varies.
By "length of month", do you mean the number of days for each month (e.g. 30, 31, 28, 29) or just a formatting issue with the month? If I understood your question correctly, you can convert the date to character then convert that to date and Oracle will take care of the change in the number of days. For example, something like:
where to_date(to_char(actfinish, 'mm/dd/yyyy')) >= to_date(to_char(sysdate, 'mm/dd/yyyy'))-30
or
where to_date(to_char(actfinish), 'mm/dd/yyyy') >= to_date(to_char(sysdate), 'mm/dd/yyyy')-30
I don't have Maximo installed on my personal laptop so you can try the two I listed above.
How to get previous month of last day without using date function in oracle?
SQL> ed
Wrote file afiedt.buf
1 SELECT LAST_DAY(ADD_MONTHS(sysdate,-1))
2* from dual
SQL> /
LAST_DAY(
---------
30-JUN-14
But I want without using date function
Create a calendar table.
Example here:
http://social.technet.microsoft.com/wiki/contents/articles/22776.t-sql-calendar-table.aspx
Also this question:
Calendar table for Data Warehouse
You'll have to adjust the date functions to work in oracle. But this will let you do date logic without the database specific functions after the one-time load.
You haven't specified which functions you can and can't use, or why, so I don't know if this is allowed by whatever constraints you're trying to do this under:
select trunc(sysdate, 'MM') - 1
from dual;
TRUNC(SYSDATE,'MM')-1
---------------------
30-JUN-14
This form of the trunc() function gives you your date "with the time portion of the day truncated to the unit specified by the format model", although that's slightly misleading as you are not restricted to time format elements. Here I've used format model 'MM', so it truncates to the first of the current month. One day before that is the last day of the previous month.
But trunc() is still a date function, depending on your definition; and maybe more importantly it's still Oracle-specific syntax, which you might be trying to avoid for some reason. but then using sysdate wouldn't be allowed either.
So I am doing a homework project where I have to display the current date, and then the date for three months from now, which I have done with this:
SELECT CURRENT_DATE AS "Today's Date", LOWER(ADD_MONTHS(CURRENT_DATE, 3)) AS "Today, Three Months Hence" FROM dual;
and again with this:
SELECT CURRENT_DATE AS "Today's Date", LOWER(CURRENT_DATE + INTERVAL '3' MONTH)AS "Today, Three Months Hence" FROM dual;
results:
Today's Date
05-FEB-14
Today, Three Months Hence
05-may-14
What I am wondering is if there is a way to display the results of the future date where it only shows the month and nothing else, (i.e. no day or year). Is this possible in Oracle, or am I just looking to try the impossible?
You just need to specify the display format, which you should really do anyway instead of relying on your session defaults:
SELECT TO_CHAR(CURRENT_DATE, 'DD/MM/YYYY') AS "Today's Date",
TO_CHAR(ADD_MONTHS(CURRENT_DATE, 3), 'YYYY-MM-DD')
AS "Today, Three Months Hence",
TO_CHAR(ADD_MONTHS(CURRENT_DATE, 3), 'Month') AS "Three Months Hence"
FROM dual;
| TODAY'S DATE | TODAY, THREE MONTHS HENCE | THREE MONTHS HENCE |
|--------------|---------------------------|--------------------|
| 06/02/2014 | 2014-05-06 | May |
Simple SQL Fiddle.
The available date format model elements are shown in the documentation.
A date value is actually stored as a number in database, not a character string. What you see in your result is the default date format interpretation of your session.
To change the way your date fields are displayed, you need to use to_char function.
select to_char(sysdate, 'MM') from dual;
'MM' parameter here stands for month, which is what you asked. It is a format string to convert a date into a character string. For a list of your options with format string, a simple google search will help you.
OK, I was looking at an example about to_char and finally sorted it out:
SELECT CURRENT_DATE AS "Today's Date", TO_CHAR(ADD_MONTHS(CURRENT_DATE, 3), 'month') AS "Today, Three Months Hence" FROM dual;
This is similar to the answer provided by Alex. I did notice that when I type in month in lower case the result is also in lower case, and when it type it in as MONTH, in upper case, the result is then in upper case, which is good to know. This wasn't required, but I was curious.
I have below data format
10/29/2003
10/21/2003 7:26:00 AM in a table
and I want to compare dates in between '07-14-2013' and '09-15-2013'. I have written code as
to_char(to_date(a.TEXT_VALUE, 'DD-MM-YYYY HH:MI:SS AM'),'dd-mm-YYYY') between '07-14-2013 00:00:00 AM' and '09-15-2013 00:00:00 AM'
this is not working. Can anyone suggest what should I do to get dates between these 2 dates?
You have your days and months reversed.
Americans (and possibly other countries too) use a notation of MM-DD-YYYY:
to_char(to_date(a.TEXT_VALUE, 'MM-DD-YYYY HH:MI:SS AM'),'mm-dd-YYYY')
between '07-14-2013 00:00:00 AM' and '09-15-2013 00:00:00 AM'
As others have also said, you really don't know whats in that varchar field, and dates should be stored as dates (so you can do all the wonderful things with dates, like compare them, subtract them, get date ranges, etc...).
So, if you have even 1 record that has an invalid date, the to_date will break. But, you say that you only want to grab records within a date range, you might ignore the time portion of the date using substr (and still hope the days are valid):
with date_strings as
(
select 1 as id, '01/31/2013' as dte_str from dual
union
select 2 as id, '02/01/2013 13:55:01' as dte_str from dual
union
select 3 as id, '02/28/2013 10:30:01 AM' as dte_str from dual
union
select 4 as id, '03/01/2013 11:15:01 AM' as dte_str from dual
)
select
id, dte_str, to_date(substr(dte_str, 1, 10), 'MM/DD/YYYY') as dte
from date_strings
where to_date(substr(dte_str, 1, 10), 'MM/DD/YYYY') between
to_date('02/01/2013', 'MM/DD/YYYY') and to_date('03/01/2013', 'MM/DD/YYYY')-1;
This example grabs rows that have a date that falls somewhere in Feb of 2013 (or fails if you have even 1 row where the MM/DD/YYYY part of the string is invalid, like 02/29/2013 for example). But at least you can probably ignore the variations in the time formats.
You've said that you have dates with format MM/DD/YYYY and MM/DD/YYYY HH:MI:SS AM, and none with MM/DD/YYYY HH24:MI:SS. The error messages you're getting indicate that you are mistaken; the 'hour must be between 1 and 12' means that you have at least one row with the time in 24-hour format. Or, potentially, with something that isn't a recognisable time at all.
The problem with storing structured data - a date in this case - in a free-text field as a varchar2 instead of as a proper data type is that you can get any old rubbish in there, and you are relying on your application validate data as it is entered - which is doesn't seem to be doing based on what you're seeing now. Well, one of the major problems, there are others, including performance implications.
The only way to try to salvage your data is to write a function that tries multiple conversions and only returns when it has something valid - or runs out of options. Something like this perhaps:
create or replace function clean_date(text_value varchar2) return date is
begin
begin
return to_date(text_value, 'MM/DD/YYYY HH24:MI:SS');
exception
when others then
null;
end;
begin
return to_date(text_value, 'MM/DD/YYYY HH:MI:SS AM');
exception
when others then
null;
end;
return null;
end clean_date;
/
This is only trying two formats but you can add more as your data needs - any row that gets a null back couldn't be converted by any of the formats it tried. You need to be a bit careful about the order you test them though, to avoid the potential for incorrect matches. Each begin/exception/end sub-block is testing one format; catching other isn't ideal but the alternative is to declare all possible date format exceptions which would be painful and error-prone. If there is no exception then that date value is returned; if there is any exception then it does nothing but moves on to the next block to try the next format.
This also won't help you if you have something really unexpected, and won't always error if you have a date in UK format as DD/MM/YYYY for example - if both the day and month are less than 13 it's impossible to tell which is which.
Anyway, with this your filter could become:
where trunc(clean_date(a.text_value))
between date '2013-07-14' and date '2013-09-15'
You could convert it back to a string if you prefer, but still use a sensible date format for the comparison:
where to_char(clean_date(a.text_value), 'YYYY-MM-DD')
between '2013-07-14' and '2013-09-15'
In Oracle SQL Developer, how does one determine the calendar date of the next occurrence of Wednesday, based on the current date. I have searched and seen many examples for Ruby, PHP, MYSQL, but not for oracle sql. I read this article about DATETIME calculations, but these we have not gone over in class. We are working with date conversion, case, when, timestamps, and intervals but I'm not sure how to go about this problem.
Nex_day function will return the date of the first weekday, specified as the second parameter of the function, later than a date specified as the first parameter of the function. For example:
SQL> select next_day(sysdate, 'WEDNESDAY') next_day
2 from dual
3 ;
NEXT_DAY
-----------
05-DEC-2012
You can use DATE function called NEXT_DAY
Try this
SELECT NEXT_DAY(SYSDATE,'WEDNESDAY') "Next WED"
FROM DUAL ;