Stored Function in Oracle not Inserting Values into the desired table - sql

Here is my Code given below. I am trying to add 5 parameters which the function takes into the table Employee. But I am not successful in doing it and have tried a lot of things.
Error
ORA-01858: a non-numeric character was found where a numeric was
expected ORA-06512: at "xxxxxxx.A1SF_ADDEMP", line 14
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.
Plus how do I test a Stored function that has a Insert/Update or Delete Statement in it?
Execution Statement
Select A1SF_ADDEMP('Adesh', '33', 'M', 8000, '26/03/1990')
From dual;
Code
CREATE OR REPLACE Function A1SF_ADDEMP
(pEmpName In Varchar2,
pTaxFileNo In Varchar2,
pGender In Varchar2,
pSalary In Number,
pBirthdate In Varchar2
) Return Varchar2
Is
tEmpId Number(38,0);
tBirthDate Date;
BEGIN
tEmpId := A1Seq_Emp.nextval;
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values (tEmpId, pEmpName, pTaxFileNo, pGender, pSalary, tBirthdate);
Commit;
Return null;
END;

Firstly you cannot call a function with DML in it in a select statement. You have to assign the output to a variable in a PL/SQL block, something like:
declare
l_output number;
begin
l_output := my_function(variable1, variable2);
end;
It's bad practice to do DML in a function; partly because it causes the errors you're coming across. You should use a procedure as detailed below. The other reason for this is that you're as always returning null there's no need to return anything at all!
create or replace procedure my_procedure ( <variables> ) is
begin
insert into employees( <columns> )
values ( <values > );
end;
The specific reason for your error is this line:
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
pBirthdate is already a string; by putting a ' around it you're passing the string 'pBirthdate' to the function to_date and Oracle can't convert this string into a day, month or year so it's failing.
You should write this as:
tBirthdate := to_date(pBirthdate,'dd/mm/yyyy');
You also don't need to specify number(38,0), you can just write number instead.
It is possible to return a value from a procedure using the out keyword. If we assume that you want to return empid you could write is as something like this:
create or replace procedure A1SF_ADDEMP (
pEmpName in varchar2
, pTaxFileNo in varchar2
, pGender in varchar2
, pSalary in number
, pBirthdate in varchar2
, pEmpid out number
) return varchar2 is
begin
pempid := A1Seq_Emp.nextval;
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values ( pEmpId, pEmpName, pTaxFileNo, pGender
, pSalary, to_date(pBirthdate,'dd/mm/yyyy');
end;
To just execute the procedure call it like this:
begin
A1SF_ADDEMP( EmpName, TaxFileNo, Gender
, Salary, Birthdate);
commit;
end;
If you want to return the empid then you can call it like this:
declare
l_empid number;
begin
l_empid := A1SF_ADDEMP( EmpName, TaxFileNo, Gender
, Salary, Birthdate);
commit;
end;
Notice how I've moved the commit to the highest level, this is to avoid committing stuff in every procedure when you might have more things you need to do.
Incidentally, if you're using Oracle 11g then there's no need to assign the value A1Seq_Emp.nextval to a variable. You can just insert it directly into the table in the values list. You, of course won't be able to return it, but you could return A1Seq_Emp.curval, as long as there's nothing else getting values from the sequence.

You should use a procedure (instead of a function) if you are not returning any values.
If you look at the line mentioned in the error message you can spot your error:
tBirthdate := to_date('pBirthdate','dd/mm/yyyy');
You are passing the string literal 'pBirthdate' to the to_date() call. But you want to pass the parameter, so it should be
tBirthdate := to_date(pBirthdate,'dd/mm/yyyy');
(note the missing single quotes arount pBirthdate).
So as a procedure the whole thing would look like this:
CREATE OR REPLACE PROCEDURE A1SF_ADDEMP
(pEmpName In Varchar2,
pTaxFileNo In Varchar2,
pGender In Varchar2,
pSalary In Number,
pBirthdate In Varchar2
)
IS
BEGIN
Insert Into Employee(EmpId, EmpName, TaxFileNo, Gender, Salary, Birthdate)
Values (A1Seq_Emp.nextval, pEmpName, pTaxFileNo, pGender, pSalary, to_date(pBirthdate,'dd/mm/yyyy'));
Commit;
END;
To run it:
execute A1SF_ADDEMP('Adesh', '33', 'M', 8000, '26/03/1990');

In your situation you need to use procedure with out parameter where your out param is your returning param that contains your desirable value. This is I think best practice in this situation when you want to use DML in select statement.
Second way to do it but its not a good practice is to use one function like yours but before return a value you need to use if statement to check what is the value and if value is what you desire to call in this if statement PRAGMA AUTONOMOUS_TRANSACTION procedure which will do your DML independently of function calling to it. For more information about pragma transactions your can read here:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/autonotransaction_pragma.htm
Best Regards and hope I was helpful

Related

Oracle passing a variable to a function

I have some SQL code, which uses a hard coded date. My goal is to remove the hard coded dates and replace them with variables to make the code generic.
My test CASE is the following;
CREATE OR REPLACE FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
V_date DATE;
BEGIN
V_Date := TO_DATE( p_str, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
This works fine
select IsDate(DATE '2021-07-31','YYYY-MM-DD') from dual;
This causes an error (see below). Can someone please explain the issue and how can it be fixed.
I'm testing on live SQL. Thanks in advance to all who answer.
exec :my_date := DATE '2021-07-31';
select IsDate( DATE my_date,'YYYY-MM-DD') from dual;
i am getting below error
ORA-01008: not all variables bound ORA-06512: at "SYS.DBMS_SQL", line 1721
ORA-00936: missing expression
There a special function VALIDATE_CONVERSION since 12.2 that does exactly what you want to achieve:
VALIDATE_CONVERSION
Example:
SQL> select VALIDATE_CONVERSION('01-01-2000' as date,'dd-mm-yyyy') chk1 from dual;
CHK1
----------
1
SQL> select VALIDATE_CONVERSION('01-01-2000' as date,'yyyy-mm-dd') chk2 from dual;
CHK2
----------
0
Also livesql.oracle.com doesn't support exec command which is an SQL*Plus command. So if you want to test your functions with bind variables, you can use simple PL/SQL Variables:
declare
string_date varchar2(100);
FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
V_date DATE;
BEGIN
V_Date := TO_DATE( p_str, p_format );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
begin
string_date:='31-12-2000';
dbms_output.put_line(IsDate(string_date,'dd-mm-yyyy'));
end;
/
The DATE keyword expects a literal, you cannot use a string variable.
Regarding your function:
FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER
It expects two VARCHAR2, i.e. string values. However when you run IsDate(DATE '2021-07-31','YYYY-MM-DD') the you pass a DATE value into the function, not a string.
Checking the format of a DATE value is pointless, because a DATE values itself has no format, it is stored as an internal binary value. Never use TO_DATE on a value which is already a DATE. Function TO_DATE expects a string which shall be converted to a DATE.
What is displayed when you select a DATE is defined (by default) with your current user session NLS_DATE_FORMAT setting. You can change the default output format for example with ALTER SESSION SET NLS_DATE_FORMAT = 'DD.MM.YYYY'; or you set it explicitly by using the TO_CHAR() function.
When you call your function IsDate(DATE '2021-07-31','YYYY-MM-DD') then Oracle makes an implicit cast to a string. i.e. it runs
V_Date := TO_DATE( TO_CHAR(p_str, SYS_CONTEXT('USERENV', 'NLS_DATE_FORMAT')), p_format );
When you pass a DATE value, then your function actually acts like this:
CREATE OR REPLACE FUNCTION IsDate( p_str IN VARCHAR2, p_format IN VARCHAR2 ) RETURN NUMBER AS
ret NUMBER;
BEGIN
SELECT COUNT(*)
INTO ret
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_DATE_FORMAT'
AND VALUE = p_format;
RETURN ret;
-- p_str is ignored completely
END;
Note, in Oracle 18 the TO_DATE function provides the DEFAULT ... ON CONVERSION ERROR clause, so it may be an overkill to write the extra function.

Can we call procedure inside function in oracle PL/SQL? If not then why?

In some places I heard that we cannot call procedure inside function in oracle PL/SQL. May I know why is it so?
Also why cant we call a procedure within a SELECT statement whereas we can call a function in the same SELECT statement.
Yes you can call a procedure from a function in Oracle PL/SQL.
You can't call a procedure from a SELECT statement because it doesn't return a value. A function can be called from a SELECT because it does:
select empno, calc_salary_function(empno) salary
from emp;
Calling a procedure from a SELECT makes no sense really:
select empno, fire_employee(empno) -- Will fail
from emp;
What would you expect to see in the second column of the results?
Procedure is not used to return a value. Procedures performs group of operations to achieve a task, where as function is used to return a value.
Procedure is executed as an operation, function can be used in the select statement derive a value
you can call procedure from function.
create table test(a varchar2(30));
create or replace procedure pro_insert_values
as
begin
insert into test values('anything');
end;
create or replace function fn_get_data
return number
as
begin
pro_insert_values;
return 1;
end;
sql>variable x number
exec :x:=fn_get_data
You can use below code to validate that procedure can be called from inside function.
create table country_name(id number, country varchar2(100));
insert all
into country_name values(1, 'INDIA')
into country_name values(2, 'UK')
SELECT * FROM DUAL;
/
CREATE OR REPLACE FUNCTION COUNTRY_FOUND_NOTFOUND(V_NAME VARCHAR2)
RETURN VARCHAR2
AS
V_COUNTRY VARCHAR2(100);
BEGIN
BEGIN
SELECT COUNTRY INTO V_COUNTRY FROM country_name WHERE COUNTRY = V_NAME;
RETURN 'FOUND';
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT_COUNTRY(V_NAME);
RETURN 'NOT FOUND';
END;
RETURN 'NOT FOUND';
END;
/
CREATE OR REPLACE PROCEDURE INSERT_COUNTRY(V_NAME IN VARCHAR2)
AS
V_ID NUMBER;
BEGIN
SELECT MAX(ID) INTO V_ID FROM COUNTRY_NAME;
INSERT INTO COUNTRY_NAME VALUES(V_ID+1, V_NAME);
END;
/
SET SERVEROUTPUT ON;
DECLARE
V_NAME VARCHAR2(100);
BEGIN
V_NAME := COUNTRY_FOUND_NOTFOUND('SPAIN');
DBMS_OUTPUT.PUT_LINE(V_NAME);
END;

function with parameter return two values

hello stack overflow I have encountered an issue i am trying to create a function which needs parameter that will return two values however I'm having an error PLS-00103: Encountered the symbol i dont know where im going wrong if i could get assistance or advice of where im going wrong.
create or replace FUNCTION GET_LOCATIONcname (l_con_id_n in NUMBER)
RETURN NUMBER
IS LOCATION VARCHAR2(30) CST_NAME VARCHAR2(15);
BEGIN
SELECT LOCATION, CST_NAME INTO LOCATION FROM LDS_CONSULTANT WHERE CONSULTANT_ID = l_con_id;
RETURN l_con_id;
END;
Functions in Oracle PL/SQL can only return result value, however, that result value does not need to be an intrinsic/atomic data type, it could be a complex data type such as an array, record, or object data type.
create or replace TYPE My_Object_Type as OBJECT (
location varchar2(30),
cst_name varchar2(15)
;
create or replace FUNCTION GET_LOCATIONcname (l_con_id_n in NUMBER)
RETURN My_Object_Type
IS
My_Result My_Object_Type;
BEGIN
SELECT My_Object_Type(LOCATION, CST_NAME) INTO My_Result
FROM LDS_CONSULTANT
WHERE CONSULTANT_ID = l_con_id;
RETURN My_Result;
END;
Then you can use this function something like this:
declare
My_Result My_Object_Type;
begin
My_Result := GET_LOCATIONcname(1);
dbms_output.put_line(My_Result.location||' '||My_Result.cst_name);
end;

Write an insert statement with select clause returning id in oracle

I am trying to write a query as follows:
Eg:
DECLARE
V_Output varchar(20):='';
BEGIN
INSERT INTO T(ID, MY_PK, NAME, VALUE)
(SELECT ID, NEXT_TRAN_VALUE('T'), NAME, VALUE FROM T WHERE MY_PK = 'NO0000000000013');
RETURNING MY_PK INTO V_Output;
DBMS_OUTPUT.PUT(V_Output);
END;
using below function
create or replace FUNCTION NEXT_TRAN_VALUE (field IN VARCHAR2) RETURN
VARCHAR2
IS
n_value VARCHAR2 (20);
P_APR VARCHAR2(3);
CURSOR rec_exists IS SELECT * FROM TRANSACTION_SEQ where SEQ_NAME = field ;
jk_seq_rec TRANSACTION_SEQ%ROWTYPE;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
SELECT LC_CODE into P_APR FROM LOCATION_CONFIG;
OPEN rec_exists;
FETCH rec_exists INTO jk_seq_rec;
IF rec_exists%FOUND THEN
UPDATE TRANSACTION_SEQ SET curr_value = curr_value + 1 WHERE SEQ_NAME = field;
COMMIT;
END IF;
--SELECT curr_value INTO n_value FROM TRANSACTION_SEQ WHERE SEQ_NAME = field;
END;
But, it shows error.
the select statement shall always return single statement to be inserted, so in my opinion it should return the my_pk of inserted row.
That won't work. RETURNING clause can't be used the way you're doing it, i.e.
insert into t (id, my_pk)
select some_id, your_function from ...
returning into v_output
but would work if you inserted VALUES, as
insert into t
values (id, your_function)
returning my_pk into v_output
It means that you'll either have to rewrite that code, or look at a workaround described in returning with insert..select article (written by Adrian Billington).
BTW, wouldn't an ordinary Oracle sequence suit your purpose? Won't be gapless, but would be simple & effective. Mind the performance when inserting huge amount of data, using your solution.
BTW #2, what's the purpose of the last line in your function? You never use N_VALUE.
I use Cursor to resolve the issue.

Pl SQL Oracle PLS-00103: Encountered the symbol "CREATE"

this code takes input for zipcode, city, and state and then inserts that into the table created Address. Prior to inserting data it will check if the zipcode is already in the table, if so calling procedure(error) to display an error code.
Im getting an error code pls-00103: encountered the symbol "CREATE" when trying to execute the code. Here is my code so far. Thanks for any help in advance.
drop table address;
create table address(zipcode NUMBER, city varchar2(30), state varchar2(20));
create or replace procedure error as
begin
dbms_output.put_line('Error Zip Code already found in table');
end error;
declare
zzip number;
ccity varchar2(30);
sstate varchar2(30);
create or replace procedure location(p_zipcode NUMBER,
p_city varchar2,
p_state varchar2) is
zip address.zipcode%type;
cit address.city%type;
st address.state%type;
begin
select count(*) from address into zip where zipcode = zip;
if any_rows_found then
error;
else
Insert into address values(zip, cit, st);
end if;
end location;
begin
select &zipcode into zzip from dual;
select &city into ccity from dual;
select &state into sstate from dual;
procedure location(zzip, ccity, sstate);
end;
/
I'm not sure what you're trying to do, but the following may be closer to what you had in mind:
drop table address;
create table address(zipcode NUMBER, city varchar2(30), state varchar2(20));
declare
zzip number;
ccity varchar2(30);
sstate varchar2(30);
procedure error is
begin
dbms_output.put_line('Error Zip Code already found in table');
end error;
procedure location(p_zipcode NUMBER, p_city varchar2, p_state varchar2) is
zip_count NUMBER;
begin
select count(*)
into zip_count
from address
where zipcode = p_zipcode;
if zip_count > 0 then
error;
else
Insert into address
(zipcode, city, state)
values
(p_zipcode, p_city, p_state);
end if;
end location;
begin
select &zipcode into zzip from dual;
select &city into ccity from dual;
select &state into sstate from dual;
location(zzip, ccity, sstate);
end;
/
Best of luck.
I dont know if I understand your problem correctly, but there are certain correction I'd like to address to answer your problem
First, if you are going to create a procedure/function, do it in a separate worksheet then compile it. Dont compile it together with other anonymous blocks because most of the time, if you dont end your other blocks with '/', errors will surely generate.
Second, your DECLARE statement is misplaced, if you are going to make an anonymous block, make sure DECLARE, BEGIN and END are in line, dont create a procedure/function inside an anonymous block.
Third, you are declaring variables in your procedures and using them but doesnt have an initial value, so it will just pass a null value to the DML statement in your procedure. just use the parameter directly.
Fourth, avoid creating a procedure that only contains dbms_output.put_line. Its silly.
Lastly, your anonymous block that should be calling your procedure, uses '&', please avoid using '&' inside pl/sql as '&' is a feature in SQL*Plus and doesnt have any meaning in PL/SQL, instead, you can use ':' as for binding variables. But you use '&' not in binding variables so you should remove that;
Try this:
drop table address;
/
create table address(zipcode NUMBER, city varchar2(30), state varchar2(20));
/
create or replace procedure location(p_zipcode NUMBER,
p_city varchar2,
p_state varchar2) is
zip address.zipcode%type;
begin
select count(*)
from address
into zip
where zipcode = p_zipcode
and city =p_city
and state = p_state;
if zip > 0 then
dbms_output.put_line('Error Zip Code already found in table');
else
Insert into address values(p_zipcode, p_city, p_state);
end if;
end location;
/
begin
location(:zzip, :ccity, :sstate);
end;