ORA-01843: not a valid month When where condition is changed - sql

I am having problem with the following error
ORA-01843: not a valid month
The following query works well if I use
SELECT mx.work_order_no, mx.work_order_name, mx.comments
FROM max_orders mx
WHERE TO_DATE (wo_dt, 'dd/mm/rr') <= (TO_DATE (SYSDATE, 'dd/mon/rr') - 7)
However if I change where condition clause to
WHERE TO_DATE (wo_dt, 'dd/mm/rr') >= (TO_DATE (SYSDATE, 'dd/mon/rr') - 7)
I am having issue with
ORA-01843: not a valid month
What has caused this and how can I resolve this error?
Update 1
Underlying view
SELECT work_order_no,
work_order_name,
wo_dt,
comments
FROM (SELECT mx_master.work_order_no,
mx_master.work_order_name,
SUBSTR (mx_master.uom, 1, 15) wo_dt,
mx_master.remarks
FROM mx_wo_data mx_master)

SYSDATE is already a date. You should not be passing it into TO_DATE(). When you do that you're doing an implicit conversion to a string, and an explicit conversion back. Gordon Linoff already showed a better way to do that.
Based on the view definition you added, wo_dt is a string. You're expecting that to be in dd/mm/rr format. The error is telling you what you have values in that column which are not actually in that format, so you'll need to examine the data in the view or the underlying table to see which record(s) have incorrect data.
You could use something like this to either exclude the values that are not in the right format; or more usefully identify the bad values so they can be removed or corrected, e.g. with something like:
select * from max_orders
where my_to_date(wo_dt, 'dd/mm/rr') is null;
or from the underlying table:
select * from mx_wo_data
where my_to_date(substr(uom, 1, 8), 'dd/mm/rr') is null;
If you can't create a function then you can use the same logic in an anonymous block.
It's odd that changing the condition causes the error though, as your (implicit and explicit) conversions are applied before the condition is evaluated, and using the function means any index on that column can't be used; so (in the absence of any other filters) you should be doing a full table scan for both queries, the conversion should be applied to all values in the column before it's filtered, and you should get the error either way. So this doesn't really answer that aspect of the question.

I propose you create a stored function to identify the bad rows:
create function invalid_date(p_d in varchar2) return number as
v_d date;
begin
v_d := TO_DATE(p_d, 'dd/mm/rr');
return 0;
exception
when others then
return 1;
end;
/
select * from mx_orders where invalid_date(wo_dt)=1;

Don't convert sysdate to a date! Just use:
WHERE TO_DATE(wo_dt, 'dd/mm/rr') >= trunc(SYSDATE - 7)

Related

ORACLE QUERY to pass date as variable in TOAD

I'm new to oracle and trying to run a simple query to pass date dynamically
DEFINE startdate = TO_DATE(select TRUNC(LAST_DAY(ADD_MONTHS( max(nav_last_calc_dt) ,-1))+1) from tb);
DEFINE enddate = TO_DATE(select TRUNC(LAST_DAY(ADD_MONTHS(max(nav_last_calc_dt),0))) from tb);
begin
select Count(1)
FROM tb
WHERE DATE BETWEEN &startdate AND &enddate;
end;
I received the below error when executing using script (F5), TOAD script runner.
ORA-06550: line 4, column 78:
PL/SQL: ORA-00936: missing expression
ORA-06550: line 2, column 1:
PL/SQL: SQL Statement ignored
But when using SQL PLUS , it returned 7 as result. So I'm confused.
SQL*Plus isn't returning 7 as the result; you haven't completed the anonymous PL/SQL block, so it is showing you 7 as the next line number (as 'begin' is line 1 and 'end;' is line 6, and is waiting for input. If you enter a slash and hit return it will then execute the block; which will throw the same ORA-00936 error.
In both clients the problem is the DATE keyword - it's expecting that to be the start of a date literal, and doesn't see the rest of the literal value as it expects. That should be a column name, presumably:
WHERE nav_last_calc_dt DATE BETWEEN &startdate AND &enddate;
But the block will still fail, possibly for different reasons in the two clients; in SQL*Plus it will still get ORA-00936 because the defined value ends at the first space (which you can fix by enclosing in double quotes), and Toad may throw that error or complain that your select has no 'into' clause. (Or it might do something else; SQL Developer against 12cR1 is throwing an internal error.) The outer TO_DATE in your defined expressions is also not ideal - it will implicitly convert the date you have to a string and then convert that string back again to a real date, both using your session NLS settings; which might work, but isn't needed.
It's not clear why you are using PL/SQL there, or really why you are using a substitution variable - it's not really dynamic, it just makes the final statement a bit more obscure.
The date calculation also seems a bit complicated. It looks like you're trying to count rows from the last month with any data; and you're finding the first day of that month using add_months and last_day - which could be done more simply just by truncating the maximum date using the 'MM' date component:
select count(*)
from tb
where nav_last_calc_dt >= (select trunc(max(nav_last_calc_dt), 'MM') from tb)
Assuming the column used for the filter is `nav_last_calc_dt, and not some other column, you don't need an upper bound - you know the maximum date is in that month, so it has to be within the month.
If if was another column, with later dates, and you used between then you would exclude any values after midnight on the last day of that month. It's safer to use a full month range:
select count(*)
from tb
where some_date >= (select trunc(max(nav_last_calc_dt), 'MM') from tb)
and some_date < (select add_months(trunc(max(nav_last_calc_dt), 'MM'), 1) from tb)
which would find all values at or after midnight on the first day of the target month, and before midnight on the first day of the following month.
It might then be worth getting the maximum date once, but you could do that in a CTE or an inline view rather than via define, which wouldn't actually save you anything the way you are trying to use it - since both defined queries would be substituted into the query/block before it's executed.

Why ORACLE TO_DATE function does not gives an ORA-01841 error in OR condition when spaces gets passed?

SQL> select to_date(' ','YYYYMMDD') from dual;
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, not be 0
SQL> select * from dual where to_date(' ','YYYYMMDD') = '19960512' or 1 = 2;
ERROR at line 1:
ORA-01841: (full) year must be between -4713 and +9999, not be 0
SQL> select * from dual where to_date(' ','YYYYMMDD') = '19960512' or 1 = 1;
Success.
In first two statements we are passing spaces to TO_DATE function which gives an error which is expected.
But
Now in third statement we are passing spaces to TO_DATE oracle function which is not allowed. But still it executes successfully.
Why?
In actual code I might pass spaces from variable and OR part may or may not be true.
Can anyone explain me this behaviour and how to handle to get an error?
P.S - I am on Oracle 12g EE
Oracle uses short-circuit logic here. Since 1=1 is known to be true, Oracle optimizes out the evaluation of the other condition. Since it's never evaluated, the fact that you're passing invalid arguments to it is inconsequential.
The other answer didn't deal with the part of your question where you still wanted to get an error
I recommend you do the date parsing in isolation. If this is a stored procedure, don't put the query as you have done here, create a date variable, use to_date to assign a date value to it (it will crash if spaces are given by the user) then use the date variable in the query. In this way if bad data is given oracle still has a chance to choke on it.
Alternatively make your store procedure take a date type parameter rather than a string, to force the calling program to provide something reasonable and consistently a date
You could repeat the conversion within the or branch, which is obviously a bit messy:
select * from dual
where to_date(' ','YYYYMMDD') = '19960512'
or (1 = 1 and to_date(' ','YYYYMMDD') != '19960512');
Error report -
ORA-01841: (full) year must be between -4713 and +9999, and not be 0
Or if the only invalid-date scenario you have to worry about is spaces, you could do something else to make that branch evaluate to false in that case:
select * from dual
where to_date(' ','YYYYMMDD') = '19960512'
or (1 = 1 and trim(' ') is not null);
string from database getting fetched and passed to TO_DATE in the query
implies you're storing dates as strings in your database, which is not a good idea; so if you can have spaces in there you could have anything, and will have to deal with other potential errors too, which the is not null approach won't catch.
And neither of those queries will error with an empty string (i.e. null) passed in - but then neither will your first two original queries; they'll all just get no-data-found.
Of course, '19960512' is not a date so you're doing additional implicit conversions here; it would be safer to pass that through to_date() too, or use a date literal if it's really a fixed value:
where to_date(' ','YYYYMMDD') = date '1996-05-12'

Not a valid month

I cannot figure out why I am getting a not valid month error. I can return each field with using a to_date without error but when I add the between filter it bombs out. I am using Oracle. The weird thing is not every record bombs out
SELECT *
FROM timedetail_dates
WHERE empnum = '501331134'
AND (TO_DATE ('10/14/2016 04:00', 'mm/dd/yyyy hh24:mi')
BETWEEN TO_DATE (timein_date, 'mm/dd/yyyy hh24:mi')
AND TO_DATE (timeout_date, 'mm/dd/yyyy hh24:mi')
)
AND tsdate BETWEEN '09-oct-2016' AND '22-oct-2016'
AND timedetail_dates.DURATION > 0;
Wrap your dates in your BETWEEN clause in TO_DATE as well like
AND tsdate BETWEEN TO_DATE('09-oct-2016', 'dd-MON-yyyy') AND TO_DATE('22-oct-2016', 'dd-MON-yyyy')
Also, if your tsdate column is not a date type, you'll need to wrap it, too, within the format of how you're storing the data in the column.
Cause of the error:
It sounds like you have an invalid date in your table. You receive this error when the string being parsed is incorrect. Here's an example query that produces it.
SELECT TO_DATE('22-10-2016 05:31', 'mm/dd/yyyy hh24:mi') FROM DUAL;
Prevent this from happening again:
As a_horse_with_no_name implies, using VARCHAR (or VARCHAR2 or any text type) is usually a poor data type for temporal data. This is part of the reason why: it's difficult to prevent invalid data from getting into the database.
You need to prevent bad data from getting into the database to start with:
The ideal solution is to convert these columns to an actual temporal type (after you repair the existing data), such as TIMESTAMP (possibly WITH TIME ZONE) or DATE.
If that isn't possible, then add a constraint or trigger that will throw an error on the wrong format. Note that you need to figure out what the "right" format is. Part of the problem here might be that you have non-American users (or maybe computer savvy people who like ISO 8601), and applications will need to either enforce a particular format or automatically parse to the standard you decide on
If you really can't prevent different formats from coming in and can't convert to an actual date, then you could store an extra column with the format. You would still need to validate that the date actually matches whatever format is contained in the same row.
You should also look at any applications that are adding data to this database. They likely lack validation of the input or contain bugs that allow invalid data to be created.
Fix the existing data:
To find the offending row, try this. First create a function:
CREATE OR REPLACE FUNCTION DATETIME_IS_VALID_FOR_FORMAT(
TEXT_DATETIME VARCHAR2,
DATETIME_FORMAT VARCHAR2
)
RETURN VARCHAR2
IS
DUMMYVAR DATE;
BEGIN
-- The assignment is only here to get the function to compile
DUMMYVAR := TO_DATE(TEXT_DATETIME, DATETIME_FORMAT);
RETURN 'TRUE';
EXCEPTION
WHEN OTHERS THEN
RETURN 'FALSE';
END;
/
Now SELECT the rows where this is 'FALSE':
SELECT *
FROM timedetail_dates
WHERE
DATETIME_IS_VALID_FOR_FORMAT(timein_date, 'mm/dd/yyyy hh24:mi') != 'TRUE' OR
DATETIME_IS_VALID_FOR_FORMAT(timeout_date, 'mm/dd/yyyy hh24:mi') != 'TRUE'
If you can't create functions because of low privileges on the database, you'll have to leverage DBMS_OUTPUT instead. Replace ID_COLUMN in the anonymous block below, and you can use it to find the bad rows:
DECLARE
FUNCTION DATETIME_IS_VALID_FOR_FORMAT(
TEXT_DATETIME VARCHAR2,
DATETIME_FORMAT VARCHAR2
)
RETURN VARCHAR2
IS
DUMMYVAR DATE;
BEGIN
-- The assignment is only here to get the function to compile
DUMMYVAR := TO_DATE(TEXT_DATETIME, DATETIME_FORMAT);
RETURN 'TRUE';
EXCEPTION
WHEN OTHERS THEN
RETURN 'FALSE';
END;
BEGIN
FOR T_ROW IN (SELECT * FROM timedetail_dates) LOOP
IF (
DATETIME_IS_VALID_FOR_FORMAT(T_ROW.TIMEIN_DATE, 'mm/dd/yyyy hh24:mi') != 'TRUE' OR
DATETIME_IS_VALID_FOR_FORMAT(T_ROW.TIMEOUT_DATE, 'mm/dd/yyyy hh24:mi') != 'TRUE'
) THEN
-- Replace ID_COLUMN with your actual primary key
DBMS_OUTPUT.PUT_LINE('Bad row: '||T_ROW.ID_COLUMN);
END IF;
END LOOP;
END;
/
Note that you'll probably have to do some preparation to make your client start capturing the output from DBMS_OUTPUT.PUT_LINE. (This is client dependent, but you have to turn it on in both SQL*Plus and Oracle SQL Developer.) Also note that none of the output will show up until the block completes.

Oracle: between dates statement gives error 'not valid month'

I'm not sure what I'm doing wrong here. I'm trying to build an oracle query that I will be executing from my php project via oci. I need to select all records between a specific date range.
In trying to get the syntax down, I wrote out this test query:
SELECT * FROM SHIPPED
WHERE user_seq_id = 381 AND
LOT_DATE BETWEEN TO_DATE('05/27/2014', 'MM/DD/YYYY')
AND TO_DATE('06/03/2014','MM/DD/YYYY');
This syntax seems like it should work but it's not. I'm definitely not an oracle developer so I'm positive I"m misunderstanding something. When I've looked at similar posts I haven't found anything that would point to what I'm doing wrong.
This is a rather tricky error. The problem would occur if LOT_DATE were stored as a character string rather than a date -- and the string contained invalid data.
By explicitly converting the right hand side of the comparison to dates, the comparison is attempted by converting the field to a date. And there is an error.
The fix is to fix the data in the field. if something is in a field called "date", then it should probably have a date data type.
For identifying unsupported string value for date - You can create a PL/SQL function which accepts string and validate for correct date format, sample of function:
CREATE OR REPLACE FUNCTION VERIFY_DATE(v_date IN VARCHAR2) RETURN NUMBER IS
v_date1 DATE;
BEGIN
select to_date(v_date) into v_date1 from dual;
RETURN 1;
Exception WHEN Others THEN
RETURN 0;
END;
Now, identify rows which are having invalid string value for which you should correct to run your query:
select * from
(
select VERIFY_DATE(LOT_DATE) DateVerified,s.* from SHIPPED s
)
where
DateVerified=0

How to identify invalid (corrupted) values stored in Oracle DATE columns

Oracle 10.2.0.5
What is the easiest way to identify rows in a table that have "invalid" values in DATE columns. By "invalid" here what I mean is a binary representation that violates Oracle rules for date values.
I recently had an issue with an invalid date stored in a column.
I was able to use a query predicate to find a particular problematic row:
WHERE TO_CHAR(date_expr,'YYYYMMDDHH24MISS') = '00000000000000'
In the case I had, the century byte was invalid...
select dump(h.bid_close_date) from mytable h where h.id = 54321
Typ=12 Len=7: 220,111,11,2,1,1,1
The century byte should be 100 + two digit century. In this case, there was an extra 100 added, as if the century value was "120", making the year "12011". (The only way I know to get invalid DATE values into the database is using OCI, using native 7-byte DATE representation.)
In this case, the TO_CHAR function returned an identifiable string, which I could use for identifying the wonky DATE value.
My question: is there an more general or easier approach (preferably using a SQL SELECT statement) to identify rows with "invalid" values in DATE columns.
This is a pretty unusual scenario (although I have come across something similar once before). The more common problem is finding invalid dates which are held as strings in a date column. You could adapt the solution for that to your situation, by building your own date validator.
Something like this:
create or replace function is_a_date
( p_date in date )
return varchar2
is
d date;
begin
d := to_date(to_char(p_date, 'SYYYYMMDDHH24MISS'), 'SYYYYMMDDHH24MISS') ;
if d != p_date then
return 'not a proper date';
else
return 'good date';
end if;
exception
when others then
return 'not a date';
end;
/
This converts a date into a string and back again. It catches exceptions thrown by date casting. If the end product is not the same as the input date then presumably something got lost in translation; to be honest I'm not sure whether the 12011 date would cast successfully to a string, so this is a belt'n'braces approach. It's a bit tricky writing this utility without some test data!
This query would identify all the non-valid dates:
select h.id, dump(h.bid_close_date)
from mytable h
where h.bid_close_date is not null
and is_a_date(h.bid_close_date) != 'good date';
Without adding a function, a simple predicate
TO_CHAR(date_col,'YYYYMMDDHH24MISS') = '000000000000'
appears to be satisfactory to identify corrupted values stored in an Oracle DATE column. The addition of a function appears to be unnecessary. Checking for corrupted dates should be able to be done in a SQL SELECT statement, and not require a user to have CREATE FUNCTION privilege on the database.
This identifies invalid months
SELECT rowid,
pk_column,
DUMP(date_column, 1010) AS dump1
FROM table
WHERE TO_NUMBER(SUBSTR(DUMP(date_column, 1010), INSTR(DUMP( date_column, 1010),
',', 1, 2
) + 1,
INSTR(DUMP(date_column, 1010), ',', 1, 3) - (
INSTR(DUMP( date_column, 1010), ',', 1, 2) + 1
))) = 0;
Update using the same where clause, I found the month number was zero in these cases.
I'd go for a method that can be implemented as a check constraint, probably through checking that the date is in the range of legal values.
However, ask yourself also whether it is valid to have a date of 1066-10-14? It is a legal value, but you probably do not have invoices printed on that day, for example. So you might like to roll the invalid date check into a larger issue of what you really consider to be valid in the context of your application.
I had an SQL Error: ORA-01841: (full) year must be between -4713 and +9999, and not be 0
01841. 00000 - "(full) year must be between -4713 and +9999, and not be 0".
So to identify the rows that had a bad date I did the following.
declare
cursor mydates is select table_pk, your_date_col from table;
c_date table.your_date_col%type;
c_pk table.table_pk%type;
testrow table.your_date_col%type;
begin
open mydates;
loop
begin
fetch mydates into c_pk, c_date;
exit when mydates%notfound;
testrow := TO_TIMESTAMP(c_date,'YYYY-MM-DD HH24:MI:SS');
exception when others then
dbms_output.put_line('bad file: ' || c_pk);
end;
end loop;
close mydates;
end;
So all I did was create a cursor, loop through the elements and tested each one and display the identifier so I could easily find the bad rows.