Not a valid month - sql

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.

Related

ORA-01861 literal does not match format string error in stored procedure

Hello Experts,
We have a requirement to find due date based on emp_id,
create table emp_test (emp_id number, customer_due_date date)
/
create table dept_test (due_days number)
/
insert into emp_test values (1,sysdate)
/
insert into emp_test values (2,sysdate-1)
/
insert into emp_test values (3,sysdate-2)
/
insert into emp_test values (4,sysdate+1)
/
insert into emp_test values (5,sysdate+2)
/
insert into dept_test values(2)
/
commit
/
My stored procedure:
create or replace procedure test_proc(p_emp_id in emp_test.emp_id%type,p_due_date out date)
as
begin
select
(case
when trim(to_char(trunc(o.customer_due_date) - r,due_days, 'Day')) = 'Sunday' then trunc(o.customer_due_date)-r.due_days-2
when trim(to_char(trunc(o.customer_due_date) - r.due_days,
'Day')) = 'Saturday' then trunc(o.customer_due_date)-r.due_days-1
when trunc(o.customer_due_date) is null then trunc(sysdate) + 60
else trunc(o.customer_due_date) - r.due_days
end
) Due_date
into p_due_date
from
dept_test r,
emp_test o
where o.emp_id = p_emp_id;
p_due_date := to_char(p_due_date,'yyyy-mm-dd');
end;
I am getting error as "ORA-01861 literal does not match format string".
Please suggest,how can we resolve the error?
Thanks.
You are playing fast and loose with data types. Perhaps your programming experience is in a language (or languages) that does/do not enforce data types.
In the function declaration, you say that p_due_date will be of DATE data type. But when you first assign it (through SELECT ... INTO) you assign a string to it. Since you are not wrapping it in a proper TO_DATE function, Oracle (which enforces data types sometimes but not always, and many of its guesses are plain wrong) attempts to convert the string to a date before assigning to p_due_date, using your session's NLS_DATE_FORMAT parameter. If that is not (by some accident) exactly 'yyyy-mm-dd', you will get this error.
The correct way to do this is to wrap the CASE expression, right in the SELECT ... INTO statement, within TO_DATE().
By the way, later in the code you have TO_CHAR(.....) - why you have that, is completely unclear. TO_CHAR returns a string, but you are assigning back to p_due_date which must be a DATE, so you have even more implicit conversions (with the wrong format model). Of course, if you fix the SELECT ... INTO statement as I suggested, you don't need to do anything to p_due_date after it is selected into; you can return it as is.
Because of this row :
p_due_date := to_char(p_due_date,'yyyy-mm-dd');
you get this error.
Since p_due_date is a date, whereas assignment yields a char.
By the way, there's also a problem on 6th row of procedure, because of , in r,due_days
As others have said
p_due_date := to_char(p_due_date,'yyyy-mm-dd');
seems to be the problem. If what you're trying to do is to remove the time portion of the date value, I suggest you use
p_due_date := TRUNC(p_due_date);
instead.
Best of luck.
As mathguy said in his answer, this issue could happen when some connection parameters such as NLS_DATE_FORMAT, NLS_TIME_FORMAT, etc are different from the DB server. In my case, I'm using the Symfony framework and its parameters were different from my Oracle server. So I have to edit my code to alter the session with raw SQL before and after my query execution:
$conn = $this->getEntityManager()->getConnection();
// Same parameters as DB server
$sqlAlterSession = "ALTER SESSION SET NLS_TIME_FORMAT = 'HH24:MI:SSXFF' NLS_DATE_FORMAT = 'DD/MM/RR' NLS_TIMESTAMP_FORMAT = 'DD/MM/RR HH24:MI:SSXFF' NLS_TIMESTAMP_TZ_FORMAT = 'DD/MM/RR HH24:MI:SSXFF TZR'";
$conn->executeUpdate($sqlAlterSession);
// Execute your query
$sql = '...';
$conn->execute($sql);
// Restore original Symfony parameters
$sqlAlterSession = "ALTER SESSION SET NLS_TIME_FORMAT = 'HH24:MI:SS' NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'";
$conn->executeUpdate($sqlAlterSession);
All the DB server's parameters were retrieved following this answer with every parameter in the ALTER SESSION ... sentence.

Getting ORA-01861 - Literal does not match format string on SQLPlus Only

I have a PL/SQL procedure that takes the following arguments/parameters
Procedure create_test(mode_in in number, value1_in in number,
value2_in in number, value3_in in varchar2);
I am using the following pl/sql block to call and execute the procedure
DECLARE
lv_mode NUMBER;
lv_value1_in NUMBER;
lv_value2_in NUMBER;
lv_value3 VARCHAR2(3);
BEGIN
lv_mode := 1;
lv_value1_in := 1;
lv_value2_in := 1;
lv_value3_in := 'ES';
CREATE_TEST(
mode_in => lv_mode ,
value1_in => lv_value1_in,
value2_in => lv_value2_in,
value3_in => lv_value3_in
);
--rollback;
END;
/
If i paste the above sql block into SQLDeveloper and execute it, it runs with no problems.
If i put it in a file and execute it through SQL plus, i get the following error (same problem if run it directly in SQLPLus):
ORA-01861: literal does not match format string
Usually when i get this error, the issue is usually related to Dates. I am not sure what is wrong with the above as there are no dates involved - Especially given the fact that the same SQL block works in an IDE but not SQLPLus.
Is SQLPlus handling the literals slightly differently than the IDE?
My guess is that some parameter in SQLPlus is handling it differently - but which one?
Funny mistake: TO_DATE(SYSDATE, 'DD-MON-YYYY HH:MI:SS')
Just replace this expression with SYSDATE in the procedure..
The returned datatype of SYSDATE function is DATE:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions172.htm
TO_DATE function expects either CHAR, VARCHAR2, NCHAR, or NVARCHAR2 type as it's first parameter:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions183.htm
The procedure passes SYSDATE (of type DATE) as a first parameter, which is expected to be a string (VARCHAR, CHAR etc.). In this case Oracle performs an implicit conversion of DATE to CHHAR/VARCHAR2, using TO_CHAR function internally.
One can say that the above expression is converted internally to:
TO_DATE( TO_CHAR( sysdate ) , 'DD-MON-YYYY HH:MI:SS' )
More on imlicit conversion rules can be found here (scroll to section: "Data Conversion - Implicit and Explicit Data Conversion):
https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements002.htm
An essential fragment from the above link:
Implicit conversion depends on the context in which it occurs and may
not work the same way in every case. For example, implicit conversion
from a datetime value to a VARCHAR2 value may return an unexpected
year depending on the value of the NLS_DATE_FORMAT parameter.
In other words - TO_CHAR( some-date ) without a second parameter (format) uses a value of NLS_DATE_FOMRAT variable taken from the session.
If you check this parameter on SQL-Developer, it will be probably: 'DD-MON-YYYY HH:MI:SS' (in SQL-Developer a value of this parameter is configured in option: Tools/Preferences/Database/NLS
But if you check NLS_DATE_FORMAT value in SQL-Plus, it will differ from 'DD-MON-YYYY HH:MI:SS'. SQL-Plus determines a value of NLS setting from language settings of your environment (Windows, Linux etc.) and from NLS_LANG enironment varable (if present).
You can change this parameter in your session using:
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH:MI:SS';
and the (old) procedure will work.
Your use of the statement " TO_DATE(SYSDATE, 'DD-MON-YYYY HH:MI:SS')" is fundamentally flawed. The to_date function takes a character string as its first arguement, but you are passing SYSDATE, which is a date. This forces oracle to first do an implicit conversion of SYSDATE to a character string, using the session level setting of NLS_DATE_FORMAT. Having gotten that character string, it then converts it back to a DATE, and has to assume that the derived character string matches your supplied format of 'DD-MON-YYYY HH:MI:SS'. It works in SQL Dev because you have it configured to set NLS_DATE_FORAMT to 'DD-MON-YYYY HH:MI:SS', but this is not the database default format, so it fails under sqlplus. Rather than monkey around with the NLS settings, you need to address the fundamental flaw up passing a date (sysdate) to the to_date function.

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

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)

Valid date format check Oracle - weird behaviour

We have been looking into some legacy code and found function that checks if date(VARCHAR2) is passed in the proper format 'YYYY-MM-DD HH24:MI:SS'. It uses simple construct
function IS_CORRECT_TIMESTAMP(P_EVENT_TIMESTAMP varchar2)
return number is
V_TIMESTAMP timestamp;
begin
select TO_TIMESTAMP(NVL(P_EVENT_TIMESTAMP, '1'),
'YYYY-MM-DD HH24:MI:SS')
into V_TIMESTAMP
from DUAL;
return 1;
exception
when others then
return - 1;
end;
Problem is, when i pass value for example '22-AUG-13', it does not throw any exception.
What can be reason of this?
Thanks for ideas.
The FX format model modifier can be used to require exact matching between a character string and the format model:
select TO_TIMESTAMP(P_EVENT_TIMESTAMP, 'FxYYYY-MM-DD HH24:MI:SS')
into V_TIMESTAMP
from DUAL;
Moreover, you can prevent context switching by not querying DUAL table in order to convert character string to a timestamp data type:
V_TIMESTAMP := TO_TIMESTAMP(P_EVENT_TIMESTAMP, 'FxYYYY-MM-DD HH24:MI:SS');

How can I convert a complex sysdate + something else with a to_date function?

Bit stuck here. Say I run the following SQL query:
select to_char(sysdate, 'DD/MON/YY') || ' 15:32:00' from dual;
The result gives me this:
08/NOV/12 15:32:00
Now I want to take that result and use it in a to_date function (long story as to why, but I'll go into detail in a sec):
select to_date((to_char(sysdate, 'DD/MON/YY') || ' 15:32:00'), 'DD/MON/YY HH24:MI:SS') from dual;
I was expecting the result to be the same as the previous one, except it isn't. Instead it gives me this:
08/NOV/12
Question is this: Is there a way I can use a concatenation of a to_char(sysdate, 'DD/MON/YY') and any combo of HH24:MI:SS in a to_date?
Additional detail that may or may not be of any additional use:
Reason I need to do this is because I was provided a query that has a table with an SLA time. The format is just a time in HH24:MI, however it's not a DATETIME type (I suspect it's a VARCHAR, but can't tell for sure as it's likely a view or function of some sort that I cannot dissect. Partially due to me not knowing how to and partially to me not having the necessary access due to my DB user.
EDIT: On reading the above again I realized I left out some additional detail (though this does not relate to my question at all I think):
I want to take today's date (DD/MON/YY), combine that with the SLA time of HH:MI, combine that with ':00' for :SS, convert the whole thing to a date, then do a comparison between the resultant abomination and the finish time for each row returned. If the finish time is bigger/ newer/ later than my combination-and-conversion-deluxe, then return something specific, in not, return something like "All OK". Not sure if that makes sense or not, but in summary, I want to get something that I can use to compare to another DATETIME type, so I think I need to somehow get the SLA time converted to a DATETIME type as well.
Any help on this will be greatly appreciated.
the output you see is converted to a char format. this is dictated by the NLS_DATE_FORMAT setting. internally, the date will still have the time format associated. so do this to see it:
SQL> select sysdate from dual;
SYSDATE
---------
08-NOV-12
SQL> alter session set nls_date_format='dd/MON/yy hh24:mi:ss';
Session altered.
SQL> select sysdate from dual;
SYSDATE
------------------
08/NOV/12 11:41:46
You're default date format is 'DD/MM/YY' If you want to display the time component you'll have to use the right date format:
select to_char(to_date((to_char(sysdate, 'DD/MON/YY') || ' 15:32:00'), 'DD/MON/YY HH24:MI:SS'),'DD/MM/YY HH24:MI:SS') from dual