Error while receiving date as parameter input - oracle

I am getting an error when I plug in a date to test a parameter I am working on. The error is:
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.
Here is my test script:
set serveroutput on
declare
type tempcursor is ref cursor;
v_cur_result tempcursor;
errcode number;
errmesg varchar2(1000);
p_statusmnemonic_in acts.ct_cu_act_medrecon_pg.varchararrayplstype;
p_processtypemnemonic_in transactionprocesslog.processtypemnemonic%type;
p_primarymemberplanid_in membermedicalreconcilationhdr.primarymemberplanid%type;
p_assigneduserid_in membermedicalreconcilationhdr.assigneduserid%type;
p_accountorgid_in membermedicalreconcilationhdr.accountorgid%type;
p_reconstatusmnemonic_in membermedicalreconcilationhdr.reconciliationstatusmnemonic%type;
p_estimatedenddt_in membermedicalreconcilationhdr.estimatedenddt%type;
p_actualenddt_in membermedicalreconcilationhdr.actualenddt%type;
p_inserteddate_in membermedicalreconcilationhdr.inserteddt%type;
p_insertedby_in membermedicalreconcilationhdr.insertedby%type;
p_updateddate_in membermedicalreconcilationhdr.updateddt%type;
p_updatedby_in membermedicalreconcilationhdr.updatedby%type;
begin
p_statusmnemonic_in(1) := ('OPEN');
p_statusmnemonic_in(2) := ('SUSPENDED_PRIOR_TO_COMPARE');
ct_cu_act_medrecon_pg.sps_get_patientmedrecs_hdr
(p_statusmnemonic_in,'NO','26-JAN-14',v_cur_result, errcode, errmesg);
loop
fetch v_cur_result into p_primarymemberplanid_in,p_assigneduserid_in,p_accountorgid_in,p_reconstatusmnemonic_in,
p_estimatedenddt_in,p_actualenddt_in,p_inserteddate_in,p_insertedby_in,
p_updateddate_in,p_updatedby_in,p_processtypemnemonic_in;
dbms_output.put_line(' planid '||p_primarymemberplanid_in||' userid '||p_assigneduserid_in);
exit when v_cur_result%notfound;
end loop;
dbms_output.put_line(' error code '||errcode||' message '||errmesg);
end;
I dont get an error when the date is set to '24-JAN-13' but the second I change anything I get that error. Here are the two estimated date fields in the table I am looking at:
24-JAN-13 04.29.19.989847000 PM
28-JAN-13 08.52.27.187015000 PM
Here is my proc:
procedure sps_get_patientmedrecs_hdr (
p_statusmnemonic_in in varchararrayplstype,
p_processtypemnemonic_in in transactionprocesslog.processtypemnemonic%type,
p_estimatedenddt_in in membermedicalreconcilationhdr.estimatedenddt%type,
p_return_cur_out out sys_refcursor,
p_err_code_out out number,
p_err_mesg_out out varchar2)
is
lv_varchararray varchararray := varchararray();
begin
if p_statusmnemonic_in.count > 0
then
for rec1 in 1..p_statusmnemonic_in.count
loop
lv_varchararray.extend(1);
lv_varchararray(rec1) := p_statusmnemonic_in(rec1);
end loop;
open p_return_cur_out for
select h.membermedreconciliationhdrskey,
h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h
where h.reconciliationstatusmnemonic in (select *
from table (cast(lv_varchararray as varchararray)))
and h.estimatedenddt <= nvl(p_estimatedenddt_in, h.estimatedenddt)
and not exists (select *
from transactionprocesslog tpl
where tpl.transactiontypemnemonic = 'MEDREC'
and tpl.transactionid = h.primarymemberplanid
and nvl(p_processtypemnemonic_in, tpl.processtypemnemonic) = tpl.processtypemnemonic);
else
open p_return_cur_out for
select h.membermedreconciliationhdrskey,
h.primarymemberplanid,
h.assigneduserid,
h.accountorgid,
h.reconciliationstatusmnemonic,
h.estimatedenddt,
h.actualenddt,
h.inserteddt,
h.insertedby,
h.updateddt,
h.updatedby
from membermedicalreconcilationhdr h;
end if;
p_err_code_out := 0;
exception
when others then
p_err_code_out := -1;
p_err_mesg_out := 'error in ct_cu_act_medrecon_pg.sps_get_patientmedrecs_hdr => ' || sqlerrm;
end sps_get_patientmedrecs_hdr;
I've tried the to_date function but received the same error. Any help would be appreciated, thanks in advance.

Looks like you're treating a string like it's a date, which may or may not work depending on your NLS settings. Whenever Oracle sees a string where it expects a date it tries to do an implicit date conversion based whether that string happens to match your particular session's date formatting parameter. This is a very common source of errors.
To use a proper date you can use the to_date function like so:
to_date('26-JAN-14','DD-MON-RR')
or use the native syntax for specifying date literals, which is what I would recommend:
date'2014-1-26'

Related

How to set value in out parameter in oracle

I want to create a stored procedure on oracle which return the value in a out parameter. I was able to create a sample query but I am able to set the value to output variable. And the output should be converted into JSON
Here is my code
CREATE OR REPLACE PROCEDURE GET_TABLE_NAMES(JSON_DATA OUT CLOB )
-- OUT OUT_IS_SUCCESS BOOLEAN,
-- OUT OUT_ERROR_MESSAGE VARCHAR(4000)
AS
l_cursor_1 SYS_REFCURSOR;
BEGIN
--JSON_DATA :=
OPEN l_cursor_1 FOR SELECT JSON_ARRAYAGG(
JSON_OBJECT('TABLE_NAME' VALUE T.TABLE_NAME)
) INTO JSON_DATA
FROM
(
select TABLE_NAME FROM all_tables
) T
;
JSON_DATA := TO_CLOB(l_cursor_1);
--dbms_sql.return_result(l_cursor_1);
END GET_TABLE_NAMES;
I want to get output in 3 variables
JSON_DATA contain out in JSON format
OUT_IS_SUCCESS Boolean flag if no error occurred
OUT_ERROR_MESSAGE if any error message
Any help will be appreciated. Thanks in advance
You can tell json_arrayagg to return a CLOB, and select that directly into your OUT parameter. The other two commented-out parameters are malformed; the OUT is in the wrong place, and the string should not have a size.
You can do something like:
CREATE OR REPLACE PROCEDURE GET_TABLE_NAMES(
P_JSON_DATA OUT CLOB,
P_IS_SUCCESS OUT BOOLEAN,
P_ERROR_MESSAGE OUT VARCHAR2
)
AS
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT('TABLE_NAME' VALUE TABLE_NAME)
FORMAT JSON
RETURNING CLOB
)
INTO P_JSON_DATA
FROM all_tables;
P_IS_SUCCESS := TRUE;
EXCEPTION
WHEN OTHERS THEN
P_IS_SUCCESS := FALSE;
P_ERROR_MESSAGE := SQLERRM;
END GET_TABLE_NAMES;
/
db<>fiddle, with an extra bit of dummy code to force an exception on the second call.
But catching exceptions, particularly with when others, just to turn them into basic strings with no context such as the position the error occurred, is rarely a good idea - it would be simpler to just let any exception flow back to the caller:
CREATE OR REPLACE PROCEDURE GET_TABLE_NAMES(
P_JSON_DATA OUT CLOB
)
AS
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT('TABLE_NAME' VALUE TABLE_NAME)
FORMAT JSON
RETURNING CLOB
)
INTO P_JSON_DATA
FROM all_tables;
END GET_TABLE_NAMES;
/
db<>fiddle
... unless you have a good reason to hide useful debugging information.

How can I test my package with one function?

I made a package that compiles fine but when I try to test it it gives me "invalid data type".
I've tried two different ways, first one like this
select pkg_contabilidad.f_totalizar_Detalle(100) FROM DUAL;
It gives me the ORA-00902 'invalid data type'
Also I've tried this
DECLARE
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
resultao r_registro;
numero NUMBER :=100;
BEGIN
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
END;
It gives me another error PLS-00382 'expression is of wrong type'
I don't know what am I doing wrong, cause my function receives just one parameter and is of type NUMBER, so I dont know where's my mistake. I'll leave the code of my package just in case
CREATE OR REPLACE PACKAGE pkg_contabilidad AS
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
TYPE t_detalle IS TABLE OF
r_registro INDEX BY BINARY_INTEGER;
FUNCTION f_totalizar_detalle(p_clave NUMBER)RETURN t_detalle;
END pkg_contabilidad;
/
CREATE OR REPLACE PACKAGE BODY pkg_contabilidad AS
B_detalle t_detalle;
i integer :=1;
FUNCTION f_totalizar_detalle(p_clave NUMBER) RETURN t_detalle IS
v_detalle t_detalle;
CURSOR c_facturado IS
SELECT c.cod_rubro, 'H', CASE WHEN SUM(d.gravada)=0 THEN SUM(d.iva) ELSE SUM(d.gravada) END
FROM fn_documentos_det d JOIN fn_conceptos c ON d.cod_concepto = c.cod_concepto
WHERE d.clave_doc=p_clave
GROUP BY c.cod_rubro;
CURSOR c_datos IS
SELECT SUM(d.total_doc), 'D',r.cod_rubro
FROM fn_documentos d JOIN fn_cajas_ctas r ON d.num_caja_cta = r.num_caja_cta
WHERE d.clave_doc = p_clave
GROUP BY r.cod_rubro;
BEGIN
open c_datos;
LOOP
FETCH c_datos INTO v_detalle(1);
END LOOP;
CLOSE c_datos;
FOR fila IN c_facturado LOOP
i := i + 1;
v_detalle(i) := fila;
END LOOP;
END;
END PKG_CONTABILIDAD;
The function returns a pkg_contabilidad.t_detalle, so the test needs to be:
declare
resultao pkg_contabilidad.t_detalle;
numero number := 100;
begin
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
end;
It doesn't work in SQL because pkg_contabilidad.t_detalle is a PL/SQL type, not a SQL type (create or replace type). The database can perform some automatic conversions, but there are still limitations.
By the way, this loop will never complete because it lacks an exit condition:
open c_datos;
loop
fetch c_datos into v_detalle(1);
end loop;
close c_datos;
Your function returns a PL/SQL table type, with a table of a PL/SQL record type, which is defined in your package, which plain SQL doesn't know about and can't display - hence your invalid datatype error. If you need to call the function and access the data from SQL you can create schema-level object and collection types instead.
In your anonymous block you are a declaring a new record type. That looks the same to you because the structure is the same, but Oracle expects the exact type the function returns. That makes your test code shorter and simpler though. But you are also trying to return the whole collection into a single record.
DECLARE
l_detalle pkg_contabilidad.t_detalle;
l_registro pkg_contabilidad.r_registro;
l_idx pls_integer;
numero NUMBER :=100;
BEGIN
l_detalle := pkg_contabilidad.f_totalizar_detalle(numero);
l_idx := l_detalle.FIRST;
WHILE l_idx is not null LOOP
l_registro := l_detalle(l_idx);
-- do something with this record
dbms_output.put_line(l_registro.tipo);
l_idx := l_detalle.NEXT(l_idx);
END LOOP;
END;
db<>fiddle with dummy cursors.
Your function is a bit strange and probably isn't doing quite what you want; but also has two fatal problems: it isn't returning anything, and it has an infinite loop. I've fixed those for the fiddle but not anything else, as this seems to be an exercise.

Input sanitization - Numeric values

I've been asked to do input validation in order to prevent sql injection. I've been using dbms assert package functions to do the sanitization. However, when I try to sanitize a number(I'm getting it in varchar2(12 byte)) error is thrown. It's the same case with alphanumeric characters starting with number.
I tried various functions of dbms assert. Nothing seems to work except noop. But, noop is of no use since it does not do any validation.
create or replace procedure employee
(
v_emp_id IN varchar2(12 byte)
)
AS
lv_query CLOB;
BEGIN
if v_emp_id is NOT NULL THEN
lv_query := 'select * from employee where emp_id=''' || dbms_assert.enquote_name(v_emp_id) || '''';
--I also tried below:
-- lv_query := 'select * from employee where emp_id=''' || dbms_assert.simple_sql_name(v_emp_id) || '''';
end if;
END
No source gives more detailed input on dbms_assert package. Please help me in
Whether dbms_assert package can be used to sanitize numeric values(stored in VARCHAR2 variables). If yes, how?
Other ways of sanitizing input. (other than using bind variables)
Thanks.
Oracle 12.2 and higher
If you are on Oracle 12.2 or higher, you can use the VALIDATE_CONVERSION function which would be the simplest solution. Your code could potentially look something like this:
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
BEGIN
IF v_emp_id IS NOT NULL AND validate_conversion (v_emp_id AS NUMBER) = 1
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Earlier than Oracle 12.2
If you are not on Oracle 12.2 or higher, you can write your own small function to validate that the value is a number. Using a method similar to what Belayer suggested, just attempt to convert the value to a number using the TO_NUMBER function and if it fails, then you know it's not a number. In my example, I have it as a small anonymous block within the code but you can also make it a standalone function if you wish.
CREATE OR REPLACE PROCEDURE employee (v_emp_id IN VARCHAR2)
AS
lv_query CLOB;
l_is_number BOOLEAN;
BEGIN
--Verify that the parameter is a number
DECLARE
l_test_num NUMBER;
BEGIN
l_test_num := TO_NUMBER (v_emp_id);
l_is_number := TRUE;
EXCEPTION
WHEN VALUE_ERROR
THEN
l_is_number := FALSE;
END;
--Finished verifying if the parameter is a number
IF v_emp_id IS NOT NULL AND l_is_number
THEN
lv_query := 'select * from employee where emp_id = ' || v_emp_id;
ELSE
--do something here with an invalid number
null;
END IF;
END;
/
Well if you cannot change the procedure it means you have no test as that procedure will not compile, so it cannot be executed. However that may be a moot point. You need to define exactly what you mean by "sanitize numeric values". Do you mean validate a string contains a numeric value. If so DBMS_ASSERT will not do that. (Note: The function chooses ENQUOTE_NAME will uppercase the string and put double quotes (") around it thus making it a valid object name.) Further your particular validation may require you define a valid numeric value, is it: an integer, a floating point, is scientific nation permitted, is there a required precision and scale that must be satisfied, etc. As a brute force validation you can simulate the assertion by just convert to number. The following will do that. Like dbms_assert if the assertion is successful it returns the input string. Unlike dbms_assert, however, when the assertion fails it just returns null instead of raising an exception. See fiddle.
create or replace
function assert_is_numeric(value_in varchar2)
return varchar2
is
not_numeric exception;
pragma exception_init (not_numeric,-06502);
l_numeric number;
begin
l_numeric := to_number(value_in);
return value_in;
exception
when not_numeric then
return null;
end assert_is_numeric;

VARCHAR2(32767) not able to handle strings in stored procedure

I am concatenating string using cursor (to form query to execute later). Here, the query that will be formed is going to be way bigger that what VARCHAR2(32767) can handle. There fore, I am getting error on proc execution - ORA-06502: PL/SQL: numeric or value error: character string buffer too small.
I used CLOB data type as well bu got error ORA-06502: PL/SQL: numeric or value error.
My code is here below:
CREATE OR REPLACE PROCEDURE sp_Market
IS
Names VARCHAR2(32767);
BEGIN
DECLARE CURSOR cur IS ('Select ID, Order_of, field_name
FROM pld_medicare_config');
BEGIN
FOR i IN cur
LOOP
Names := Names || i.sqql;
END LOOP;
dbms_output.put_line(Names);
END;
END sp_Market;
How can I handle my string of queries and what data type is there to accomplish the task?
CLOB is OK (as far as I can tell); I doubt queries you store in there are that big.
Remove dbms_output.put_line call from the procedure; I suspect it is the one that raises the error.
I'm not sure how you got any runtime error, as your procedure won't compile.
The valid PL/SQL version would look something like this:
create or replace procedure sp_market is
names varchar2(32767);
begin
for r in (
select id, order_of, field_name
from pld_medicare_config
)
loop
names := names || ' ' || r.field_name;
end loop;
names := ltrim(names);
dbms_output.put_line(names);
end sp_market;
If names needs to be longer, change the datatype to clob.
Use the CLOB datatype and append data using the dbms_lob.writeappend procedure. This is the reference (Oracle 18c).
The error probably origins with the dbms_output.put_line call. The procedure is defined for varchar2 arguments only which means that an implicit conversion takes place during the call. It will fail for clob contents longer than 32767 chars/bytes.
Alternatively you may declare a collection over varchar2(4000) and fill the collection elements sequentially:
CREATE OR REPLACE PROCEDURE sp_Market
IS
TYPE tLongString IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
cNames tLongString;
BEGIN
DECLARE CURSOR cur IS Select ID, Order_of, field_name, sqql FROM pld_medicare_config;
BEGIN
FOR i IN cur
LOOP
cNames(cNames.COUNT+1) := i.sqql;
END LOOP;
END;
END sp_Market;
Note
Rectified code, will compile now.

How to correctly define an array of date type in Oracle?

I want to convert a string array to a date type array. But I'm not sure if my date array is correctly defined. Can't find any example of how to define or create an array of date type.
This is how I have declared my string array and date type array.
create or replace TYPE array_collection IS table OF VARCHAR2(50)
--string array is working absolutely fine so dw about that
create or replace type date_array is table of date;
--here i don't if I've defined this array correctly
my convert array procedure:
create or replace procedure convert_arr(
dat_ar in array_collection,perdate_arr out date_array)
as
begin
perdate_arr:=new date_array();
perdate_arr.extend(dat_ar.count);
for i in 1..dat_ar.count loop
perdate_arr(i):=to_date(dat_ar(i), 'yyyy-mm-dd');
end loop;
end convert_arr;
--compiles perfectly
calling anonymous block:
set serveroutput on
declare
--l_dat array_collection;
--l_darray date_array;
l_dat array_collection:=array_collection();
l_darray date_array:=date_array();
begin
l_dat := array_collection('2011-01-01','2011-04-01','2011-05-01');
--l_dat.extend(3);
-- l_dat(1):= to_date(2019-07-08);
-- l_dat(2):= to_date(2019-07-09);
-- l_dat(3):= to_date(2019-06-02);
convert_arr(dat_ar=>l_dat,perdate_arr=>l_darray);
dbms_output.put_line('Number of array:' || l_dat.count);
for i in 1..l_dat.count loop
dbms_output.put_line('Date ' || i || ':' || to_char(l_dat(i),'dd/mm/yyyy'));
end loop;
end;
this block gives me an error:
Error report -
ORA-01861: literal does not match format string
ORA-06512: at line 9
01861. 00000 - "literal does not match format string"
*Cause: Literals in the input must be the same length as literals in
the format string (with the exception of leading whitespace). If the
"FX" modifier has been toggled on, the literal must match exactly,
with no extra whitespace.
*Action: Correct the format string to match the literal.
Tried changing formats but doesn't help. Any help would be highly appreciated. Thankyou
Since l_darray is your date array, loop through it for displaying rather than l_dat
set serveroutput on
declare
--l_dat array_collection;
--l_darray date_array;
l_dat array_collection:=array_collection();
l_darray date_array:=date_array();
begin
l_dat := array_collection('2011-01-01','2011-04-01','2011-05-01');
--l_dat.extend(3);
-- l_dat(1):= to_date(2019-07-08);
-- l_dat(2):= to_date(2019-07-09);
-- l_dat(3):= to_date(2019-06-02);
convert_arr(dat_ar=>l_dat,perdate_arr=>l_darray);
dbms_output.put_line('Number of array:' || l_darray.count);
for i in 1..l_darray.count loop
dbms_output.put_line('Date ' || i || ':' || to_char(l_darray(i),'dd/mm/yyyy'));
end loop;
end;
/
Result
Number of array:3
Date 1:01/01/2011
Date 2:01/04/2011
Date 3:01/05/2011
PL/SQL procedure successfully completed.

Resources