Valid date format check Oracle - weird behaviour - sql

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');

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.

Same query works fine in a certain database but throws error in different database

I am trying to fetch the year from 'MON-YY' format and then concatenating the fetched year with '01-JUN' . I used to_date to forcefully convert 'string' ('01-JUN-17') to 'date' type
The below code works fine in a particular database for eg db_1
select to_date('01-JUN-'||(EXTRACT(YEAR FROM to_date('MAY-17','mon-yy'))) ) AS YEAR_START_DATE from dual;
returns :
01-JUN-17
But in db_2 the same code throws the below error :
ORA-01858: a non-numeric character was found where a numeric was expected
01858. 00000 - "a non-numeric character was found where a numeric was expected"
*Cause: The input data to be converted using a date format model was
incorrect. The input data did not contain a number where a number was
required by the format model.
*Action: Fix the input data or the date format model to make sure the
elements match in number and type. Then retry the operation.
Can someone please help ?
'01-JUN-' || EXTRACT(YEAR FROM to_date('MAY-17','mon-yy'))
Will generate a string like '01-JUN-2017'
The problem occurs when you try to use TO_DATE( datestring, format_model ) without specifying a format model. In this case the query will use the NLS_DATE_FORMAT session parameter.
You can find the value for this using:
SELECT VALUE
FROM SYS.NLS_SESSION_PARAMETERS
WHERE PARAMETER - 'NLS_DATE_FORMAT';
If this does not match the format you specify then it will raise an exception.
You should explicitly specify the format model (and the language):
SELECT TO_DATE(
'01-JUN-'||EXTRACT(YEAR FROM to_date('MAY-17','mon-yy','NLS_LANGUAGE="ENGLISH"')),
'dd-MON-YYYY',
'NLS_LANGUAGE="ENGLISH"'
) AS YEAR_START_DATE
FROM DUAL;
Or you could use:
SELECT ADD_MONTHS(
TRUNC(
TO_DATE( 'MAY-17', 'MON-YY', 'NLS_LANGUAGE="ENGLISH"' ),
'YY'
),
5
) AS YEAR_START_DATE
FROM DUAL;
You are missing a date format for your first to_date('01-JUN-'..) function. The date format for one database is probably DD-MON-YYYY but it could be DD-MM-YYYY in the other database.
After adding the date format your query looks like this:
select to_date('01-JUN-' || (extract(year from to_date('MAY-17', 'mon-yy'))),'DD-MON-YYYY') as year_start_date
from dual;
It's good practise to always specify a format when converting dates from and to strings.

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.

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.

Possible Date assignment error ORA-01858: a non-numeric character was found where a numeric was expected

I'm getting the error ORA-01858: a non-numeric character was found where a numeric was expected, which I think is because the date assignment in my code isn't correct. Can someone have a look at my code snippet below and let me known the correct way of assigning the date if it's wrong please?
Declare
v_task_start DATE;
v_task_completion DATE;
v_prj_start DATE;
v_prj_completion DATE;
l_start_date DATE;
l_completion_date DATE;
v_task_id NUMBER;
v_prj_id NUMBER;
CURSOR c_tasks_to_update IS
SELECT
pt.TASK_ID,
pt.Project_id AS Task_Prj_Id,
pt.start_date AS Task_start,
pt.Completion_date AS Task_Completion,
ppa.start_date AS Project_start,
ppa.completion_date AS Project_completion
INTO
v_task_id, v_prj_id, v_task_start, v_task_completion, v_prj_start, v_prj_completion
FROM pa_tasks pt, pa_projects_all ppa
WHERE pt.project_id = ppa.project_id
AND pt.created_by = 1623
and to_date(pt.creation_date,'DD-MON-YY') = to_date('26-SEP-2014', 'DD-MON-YY');
BEGIN
FOR ctask_update in c_tasks_to_update
LOOP
v_task_start := ctask_update.Task_start;
v_task_completion := ctask_update.Task_Completion;
v_prj_start := ctask_update.Project_start;
v_prj_completion := ctask_update.Project_completion;
IF ((v_task_start <> v_prj_start) and (v_task_completion <> v_prj_completion))
THEN
DBMS_OUTPUT.put_line( 'Task Start date is not equal to Project start date,
Task completion date is not equal to Project completion date '
||v_task_start||' '||v_prj_start||' '||v_task_completion ||' '||
v_prj_completion);
l_start_date := to_date(trunc(v_prj_start),'DD_MON_YY');
/*SELECT START_DATE FROM PA_PROJECTS_ALL
INTO l_start_date
FROM pa_projects_all WHERE project_id = v_prj_id;*/
l_completion_date := to_date('trunc(v_prj_completion)','DD_MON_YY');
/*SELECT completion_date FROM PA_PROJECTS_ALL
INTO l_completion_date
FROM pa_projects_all WHERE project_id = v_prj_id;*/
DBMS_OUTPUT.put_line( 'Task start date to be updated to '||
to_char(l_start_date));
DBMS_OUTPUT.put_line( 'Task completion date to be updated to '||
to_char(l_completion_date));
End;
I don't get to see the output statements Task start date to be updated to:xxxx. So the exception is occurring here.
There are a number of issues I can see here. Firstly, the following line:
and to_date(pt.creation_date,'DD-MON-YY') = to_date('26-SEP-2014', 'DD-MON-YY');
What is the type of pt.creation_date? CHAR, VARCHAR2 or DATE?
Don't use CHAR or VARCHAR2 columns for dates as otherwise all sorts of other junk could end up in your date column: for example 31-SEP-14, tomorrow, n/a, asdf, all of which will cause problems later on during processing. Your error may be caused by dirty data in this column.
If pt.creation_date is a DATE, there is no need to call to_date on it, as that will force Oracle to convert the date to a string and then back to a date. This is unnecessary.
Also, to_date('26-SEP-2014', 'DD-MON-YY') isn't entirely correct as you are using a four-digit year 2014 in your date string and a two-digit year YY in your date format picture. However, although this looks odd, Oracle doesn't seem to have a problem with it.
The next problem is here:
l_start_date := to_date(trunc(v_prj_start),'DD_MON_YY');
v_prj_start is already a DATE, so there's no need to convert it to a date. You are doing an unnecessary conversion from date to string and back again. It is probably sufficient to just write
l_start_date := trunc(v_prj_start);
Also I'm not sure why you are using underscores in your date format picture ('DD_MON_YY').
The next problem I see is this line:
l_completion_date := to_date('trunc(v_prj_completion)','DD_MON_YY');
This could well be the line that is generating your error. In fact, it is guaranteed to fail with exactly the error message you report. This is because the string 'trunc(v_prj_completion)' isn't a valid date in the format you specified. Oracle is expecting a number at the start of your date string and you have given it the letter t instead. I imagine you intended to write the following line instead:
l_completion_date := to_date(trunc(v_prj_completion),'DD_MON_YY');
However, once again, don't use to_date on a DATE value, so
l_completion_date := trunc(v_prj_completion);
may well be enough.
Further on down there are two lines similar to the following:
DBMS_OUTPUT.put_line( 'Task start date to be updated to '||
to_char(l_start_date));
Always use to_char with a date format picture, e.g. to_char(l_start_date, 'DD-MON-YY').
Finally, as a general point, use four-digit years (YYYY) instead of two-digit years where possible. A 2-digit year 14 will be interpreted as 2014, and 15 as 2015, but how much further can you go before 2-digit years start being interpreted as in the last century? Perhaps 50 will be interpreted as 1950?
Just a wild guess:
As I've noticed you use TO_DATE in your query to convert pt.creation_date to a proper date value, I could assume that maybe other date are stored as string in your table as well (which is a bad idea™).
So I would try something like that:
v_task_start := TO_DATE(ctask_update.Task_start,'DD-MON-YY');
v_task_completion := TO_DATE(ctask_update.Task_Completion,'DD-MON-YY');
v_prj_start := TO_DATE(ctask_update.Project_start,'DD-MON-YY');
v_prj_completion := TO_DATE(ctask_update.Project_completion,'DD-MON-YY');