i'm using a function to convert currencies but i'm getting this error 12703. 00000 - "this character set conversion is not supported", i don't know why.
here's the code i'm using
CREATE OR REPLACE FUNCTION convert(r_comm IN commande.ref_commande%type,
devise IN VARCHAR2)
RETURN commande.montant_ttc%type IS
mt commande.montant_ttc%type;
montant NUMBER(20) :=0;
BEGIN
IF devise = 'dollar' THEN
SELECT montant_ttc INTO mt FROM commande WHERE ref_commande = r_comm;
montant := mt*10;
END IF;
IF devise = 'euro' THEN
SELECT montant_ttc INTO mt FROM commande WHERE ref_commande = r_comm;
montant := mt*11;
END IF;
RETURN montant;
END convert;
/
DECLARE
r_comm commande.ref_commande%type :=1;
devise VARCHAR2(6) := 'dollar';
mt NUMBER(20);
BEGIN
mt := convert(r_comm, devise);
dbms_output.put_line(mt);
END;
/
THE ERROR
THE TABLE
It seems that the call is going to the different convert function (oracle provides convert function).
The Oracle CONVERT() function converts a string from one character
set to another.
Solution:
I would suggest changing the name of your function as the convert is the oracle function.
Or
Use your schemaname.function while calling it. But It is not recommended to have same object name so option 1 is better.
Cheers!!
Related
I'm a student and I have this exercise: we have to write a function with 2 parameters, account number and withdrawal, and return the new balance of a bank account only if the account balance - withdrawal > Flow threshold
This is my code:
set serveroutput on
CREATE OR REPLACE FONCTION Retrait
(f_numcomp in varchar2,f_montant NUMBER(38,3))
RETURN NUMBER(38,3)
AS
v_compte compte%rowtype;
v_solde compte.Solde%type;
BEGIN
SELECT * into v_compte from compte where f_numcomp = compte.NUMEROCOMPTE;
if (v_compte.Solde - f_montant) > v_compte.SeuilDebit /*and compte.Etat != 'desactiver'*/ THEN
v_solde := v_compte.Solde - f_montant;
UPDATE compte SET Solde = Solde - f_montant where f_numcomp = compte.NumeroCompte;
else
dbms_output.put_line('solde insufusant!');
end if;
return(v_solde);
END Retrait;
/
This is what I get:
Rapport d'erreur -
ORA-06550: Ligne 9, colonne 16 :
PLS-00103: Symbole "(" rencontré à la place d'un des symboles suivants :
. ;
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I'm new here; I read some articles here but still didn't find the error
You should be able to simplify the function to use a single UPDATE statement with a RETURNING clause (rather than SELECT and then UPDATE):
CREATE FUNCTION Retrait (
f_numcomp IN COMPTE.NUMEROCOMPTE%TYPE,
f_montant IN COMPTE.SOLDE%TYPE
) RETURN COMPTE.SOLDE%TYPE
AS
v_solde COMPTE.SOLDE%TYPE;
BEGIN
UPDATE compte
SET Solde = Solde - f_montant
WHERE f_numcomp = NumeroCompte
AND solde - f_montant > SeuilDebit
RETURNING solde INTO v_solde;
IF SQL%ROWCOUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('solde insufusant!');
END IF;
RETURN v_solde;
END Retrait;
/
However, it is not usual to have DML statements in a function; you would normally use a PROCEDURE and have an OUT parameter to return the value.
fiddle
FONCTION must be FUNCTION, but that was obviously just a typo in the request here, because the error you got is something else.
In the function declaration Oracle wants types without precision. I.e. NUMBER instead of NUMBER(38, 3).
CREATE OR REPLACE FUNCTION Retrait (f_numcomp IN VARCHAR2, f_montant NUMBER)
RETURN NUMBER
AS
v_compte compte%ROWTYPE;
v_solde compte.Solde%TYPE;
BEGIN
SELECT * INTO v_compte
FROM compte
WHERE f_numcomp = compte.NUMEROCOMPTE;
...
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.
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;
How to write a simple function that returns in parameter changed so it doesn't contain certain symbols anymore?
(č=>c, ć=>c, š=>s, đ=>d, ž=>z..)
e.g. *đurđević* => '*djurdjevic*'
e.g. *kuća* => *kuca*
e.g. *čaćkati* => *cackati*
I have no code so far. I am very new at this and am trying to learn something.
For Croatian characters you can use a combination of TRANSLATE and REPLACE, since TRANSLATE doesn't support one to many translation (e.g. đ => dj).
SELECT REPLACE(TRANSLATE('đak žvakaća čičak šuma','žćčš', 'zccs'), 'đ', 'dj') out FROM dual;
And the output:
out
--------------------------
djak zvakaca cicak suma
Edit
So here are two wrapper functions which implements this feature. The first one uses the built-ins while the other one has its own custom implementation.
-- first
CREATE OR REPLACE FUNCTION f_translate(p_string IN VARCHAR)
RETURN VARCHAR
AS
BEGIN
RETURN REPLACE(TRANSLATE(p_string,'žćčš', 'zccs'), 'đ', 'dj');
END;
-- second
CREATE OR REPLACE FUNCTION f_translate_custom(p_string IN VARCHAR)
RETURN VARCHAR
AS
v_current varchar(1);
v_retval VARCHAR(255);
BEGIN
FOR i IN 1..LENGTH(p_string) LOOP
v_current := SUBSTR(p_string, i, 1);
v_retval := v_retval || CASE v_current
WHEN 'č' THEN 'c'
WHEN 'ć' THEN 'c'
WHEN 'ž' THEN 'z'
WHEN 'š' THEN 's'
WHEN 'đ' THEN 'dj'
ELSE v_current
END;
END LOOP;
RETURN v_retval;
END;
And some test code.
SET SERVEROUTPUT ON;
BEGIN
DBMS_OUTPUT.PUT_LINE('Built-in: ' || f_translate('đak žvakaća čičak šuma'));
DBMS_OUTPUT.PUT_LINE('Custom: ' || f_translate_custom('đak žvakaća čičak šuma'));
END;
Check out translate
https://www.techonthenet.com/oracle/functions/translate.php
Example:
TRANSLATE('1tech23', '123', '456')
Result: '4tech56'
You can try the convert function.
SELECT CONVERT('Ä Ê Í Õ Ø A B C D E ', 'US7ASCII', 'WE8ISO8859P1')
FROM DUAL;
CONVERT('ÄÊÍÕØABCDE'
---------------------
A E I ? ? A B C D E ?
This is the closest you can get without creating any structure:
SELECT utl_raw.cast_to_varchar2((nlssort('čaćkati', 'nls_sort=binary_ai')))
FROM dual;
If you have a lot of combinations, I suggest on creating a table with possible combinations and using the TRANSLATE function.
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'