Function returned without value - sql

This is the function which I designed. It got complied successfully
CREATE OR REPLACE FUNCTION "F_CHECK"
(
p_id IN VARCHAR2
p_name IN VARCHAR2)RETURN VARCHAR2
is
v_id VARCHAR2;
v_name VARCHAR2;
cnt pls_integer;
BEGIN
IF id IS NULL THEN
RETURN NULL;
END IF;
SELECT COUNT(*) INTO cnt from emp_new where id = p_id;
IF (cnt > 0) THEN
SELECT id, name INTO v_id, v_name from emp_new where id=p_id;
IF (v_id is null and p_id is null and v_name is null and p_name is null) THEN
return NULL;
ELSE
IF (v_name =trunc(p_name)) then
return NULL;
else
insert into employees values(p_id,p_name,sysdate);
end if;
end if;
end if;
exception
when DUP_VAL_ON_INDEX THEN
raise application error (-20001, 'NAME EXISTS UNDER DIFFERENT ID');
END F_CHECK;
But I'm not getting the expected result, when I execute the function
select F_CHECK(1,1) from dual;
ERROR I'M getting is:
SQL EEROR: ORA-06503: PL/SQL : FUNCTION RETURNED WITHOUT VALUE

You must return a value when the execution flow reaches (around line 22)
...
else
insert into employees values(p_id,p_name,sysdate);
end if;
...
In this case, the function does not return a value which is expected by the caller, hence the error.
You can instruct the PL/SQL compiler to warn you about such code (and other problems) with
ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL'; prior to compilation.

One of the possible causes of exceptions that you will get when you run this code is: If your select into didn't return a value, you will get an exception, an nu-handled exception
Even though you have a return NULL in the end of the function, but still you need to catch all the exceptions that might occur
The area where you need to take care of is:
SELECT id, name INTO v_id, v_name from emp_new where id=p_id;
Surround it by Begin ... EXCEPTION WHEN NO_DATA_FOUND THEN ... END; block
Also, your insert statement might cause an exception if you violated some constraints on your table, so you might need to handle that also
Have a look at Error Handling in Oracle
Below is your updated code, also fixed code regarding the extra parenthesis
Edited
CREATE OR REPLACE FUNCTION F_CHECK
(
P_ID EMP_NEW.ID%TYPE,
P_NAME EMP_NEW.NAME%TYPE
)
RETURN VARCHAR2 IS
V_ID EMP_NEW.ID%TYPE;
V_NAME EMP_NEW.NAME%TYPE;
CNT NUMBER;
BEGIN
--IF ID IS NULL THEN
-- What is ID ?? is it P_ID
--Changed below
IF P_ID IS NULL THEN
RETURN 'Error: Add Value For Id';
END IF;
IF P_NAME IS NULL THEN
RETURN 'Error: Add Value For Name';
END IF;
SELECT
COUNT(*) INTO CNT FROM EMPLOYEES
WHERE ID = P_ID;
IF (CNT > 0) THEN
SELECT
ID, NAME INTO V_ID, V_NAME
FROM
EMP_NEW
WHERE
ID=P_ID;
----------------------------------------------------------------------------------------
--IF V_ID IS NULL AND P_ID IS NULL AND V_NAME IS NULL AND P_NAME IS NULL THEN
--The code above will always evaluate to False because P_ID at this stage is not null!
--Also, if P_Name must have a value, check it at the begining along with the ID, not here
----------------------------------------------------------------------------------------
IF V_ID IS NULL AND V_NAME IS NULL THEN
RETURN 'Error: Not found details';
ELSE
--Details are found
IF (V_NAME = TRUNC(P_NAME)) THEN
RETURN 'Name already assigned to this id';
ELSE --Its a new name, add it
INSERT INTO EMPLOYEES VALUES(P_ID,P_NAME,SYSDATE);
--Make sure your columns are only three and in the correct order as the Values specified
END IF;
END IF;
END IF;
RETURN 'Ok, added';
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
raise application error (-20001, 'NAME EXISTS UNDER DIFFERENT ID');
END F_CHECK;

A function MUST ALWAYS RETURN a VALUE of proper data type. Else, it would throw the following error:
ORA-06503: PL/SQL: Function returned without value
Cause: A call to PL/SQL function completed, but no RETURN statement was executed.
Action: Rewrite PL/SQL function, making sure that it always returns a value of a proper type.
Read ORA-06503: PL/SQL: Function returned without value
DB version : 11.2.0.2.0
Let’s see the various scenarios of this error :
Without a RETURN statement in the function body and without exception handler(most stupid way):
SQL> set serveroutput on
SQL> CREATE OR REPLACE FUNCTION f_test(i_val NUMBER)
2 RETURN NUMBER AS
3 o_val NUMBER;
4 BEGIN
5 SELECT 100 / i_val
6 INTO o_val
7 FROM DUAL;
8 END;
9 /
Function created
SQL> select f_test(100) from dual;
select f_test(100) from dual
ORA-06503: PL/SQL: Function returned without value
ORA-06512: at "F_TEST", line 8
Now, in the above code, the mathematical logic was correct, hence there was no SQL error to override the PL/SQL error. Let’s see how ORA-01476 will override the ORA-06503 error.
SQL> CREATE OR REPLACE FUNCTION f_test(i_val NUMBER)
2 RETURN NUMBER AS
3 o_val NUMBER;
4 BEGIN
5 SELECT 100 / i_val
6 INTO o_val
7 FROM DUAL;
8 END;
9 /
Function created
SQL> select f_test(0) from dual;
select f_test(0) from dual
ORA-01476: divisor is equal to zero
ORA-06512: at "F_TEST", line 5
Well, that’s quite obvious, isn’t it?
Without a RETURN statement in the exception handler(most common mistake):
SQL> CREATE OR REPLACE FUNCTION f_test(i_val NUMBER)
2 RETURN NUMBER AS
3 o_val NUMBER;
4 BEGIN
5 SELECT 100 / i_val
6 INTO o_val
7 FROM DUAL;
8
9 RETURN o_val;
10
11 EXCEPTION
12 WHEN OTHERS THEN
13 NULL;
14 END;
15 /
Function created
SQL> select f_test(0) from dual;
select f_test(0) from dual
ORA-06503: PL/SQL: Function returned without value
ORA-06512: at "F_TEST", line 14
Now let’s put a RETURN statement at required places and the code should work fine without any error:
SQL> CREATE OR REPLACE FUNCTION f_test(i_val NUMBER)
2 RETURN NUMBER AS
3 o_val NUMBER;
4 BEGIN
5 SELECT 100 / i_val
6 INTO o_val
7 FROM DUAL;
8
9 RETURN o_val;
10
11 EXCEPTION
12 WHEN OTHERS THEN
13 DBMS_OUTPUT.PUT_LINE('Came inside Exception handler');
14 RETURN 0;
15 END;
16 /
Function created
SQL> select f_test(0) from dual;
F_TEST(0)
0
Came inside Exception handler
Bottom line is that :
A function MUST ALWAYS RETURN a value of proper datatype, no matter from the body or exception.
We must do something with the error not just return junk. We must RAISE/log error and handle it, do something about the error so that underlying process has no impact.
Lastly, not to forget, EXCEPTION WHEN OTHERS THEN NULL; –> is itself a bug in the code waiting for its chance to break the code.

Related

Find id then assign 1 if id found from table PL sql create procedure

I'm looking to create a procedure that looks for the given customer ID in the database. If the customer exists, it sets the variable found to 1. Otherwise, the found variable is set to 0. However, my call out code block does not provide a result. Did I miss something or my SELECT statement should be something else? Thank you.
CREATE OR REPLACE PROCEDURE find_customer(CUST_ID IN NUMBER, found OUT NUMBER) AS
CUSTID NUMBER := CUST_ID;
BEGIN
SELECT CUSTOMER_ID INTO CUSTID
FROM CUSTOMERS
WHERE CUSTOMER_ID = CUST_ID;
IF CUST_ID = NULL THEN
found := 1;
END IF;
EXCEPTION
WHEN no_data_found THEN
found := 0;
END;
/
DECLARE
CUSTOMER_ID NUMBER := 1;
found NUMBER;
BEGIN
find_customer(1,found);
DBMS_OUTPUT.PUT_LINE (found);
END;
I don't think there's anything other to it than the following part bellow. In your given example, it is not possible to get a null value from it as any null id would probably mean the item doesn't exist. Meaning it doesn't return a row, which triggers the NO_DATA_FOUND exception, which you catch.
This is what you wrote:
IF CUST_ID = NULL THEN
found := 1;
END IF;
This is probably what you meant:
IF CUST_ID IS NOT NULL THEN
found := 1;
END IF;
I'd rewrite it so that
you distinguish parameters from local variables from column names
use table aliases
fix what happens when something is found (is not null, line #11)
while testing, use variable you declared, not a constant (1)
So:
SQL> CREATE OR REPLACE PROCEDURE find_customer (par_cust_id IN NUMBER,
2 par_found OUT NUMBER)
3 AS
4 l_custid NUMBER;
5 BEGIN
6 SELECT c.customer_id
7 INTO l_custid
8 FROM customers c
9 WHERE c.customer_id = par_cust_id;
10
11 IF l_custid IS NOT NULL
12 THEN
13 par_found := 1;
14 END IF;
15 EXCEPTION
16 WHEN NO_DATA_FOUND
17 THEN
18 par_found := 0;
19 END;
20 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> SELECT * FROM customers;
CUSTOMER_ID
-----------
100
SQL> DECLARE
2 l_customer_id NUMBER := 1;
3 l_found NUMBER;
4 BEGIN
5 find_customer (l_customer_id, l_found);
6 DBMS_OUTPUT.put_line (l_found);
7 END;
8 /
0
PL/SQL procedure successfully completed.
SQL> DECLARE
2 l_customer_id NUMBER := 100;
3 l_found NUMBER;
4 BEGIN
5 find_customer (l_customer_id, l_found);
6 DBMS_OUTPUT.put_line (l_found);
7 END;
8 /
1
PL/SQL procedure successfully completed.
SQL>
You can simplify it down to:
CREATE OR REPLACE PROCEDURE find_customer(
p_cust_id IN CUSTOMERS.CUSTOMER_ID%TYPE,
p_found OUT NUMBER
) AS
BEGIN
SELECT 1
INTO p_found
FROM CUSTOMERS
WHERE CUSTOMER_ID = p_cust_id;
EXCEPTION
WHEN no_data_found THEN
p_found := 0;
END;
/
The line CUSTOMER_ID = p_cust_id will not match if either side is NULL so you don't need any further checks.
Then you can call it using:
DECLARE
v_found NUMBER;
BEGIN
find_customer(1,v_found);
DBMS_OUTPUT.PUT_LINE (v_found);
END;
/
db<>fiddle here

how to make a select query with functions pl sql

Function 1:
create or replace function get_books (l_id in number)
return varchar
is l_return varchar2(100);
begin
select books into l_return from people where id=l_id;
return l_return;
end
/
Function 2:
create or replace function get_author (l_id in number)
return varchar
is l_return varchar2(100);
begin
select author in l_return from authors where id=l_id;
return l_return;
end
/
I want to make a select with 2 functions, I want to display books and authors.
is it possible?
Yes, it is possible. For example:
select get_books (1) book,
get_author(1) author
from dual;
Presumably, both functions return a single value. If any of those selects (used in functions) returns more than one row, it'll fail.
As of the procedure: code you posted is invalid so I fixed it.
SQL> create or replace procedure get_categories (l_categories in varchar2,
2 l_id in number,
3 l_boolean out boolean,
4 l_error out varchar2)
5 is
6 begin
7 -- INSERT INTO categories (id, categories)
8 -- VALUES (l_categories, l_id);
9
10 l_boolean := true;
11 commit;
12 exception
13 when others
14 then
15 l_boolean := false;
16 l_error := sqlerrm;
17 end;
18 /
Procedure created.
As it returns 2 OUT parameters, you have to declare variables to accept those values (v_boolean and v_error). Furthermore, as you can't directly display Boolean value, use CASE and display a string instead.
Procedure can't be called as a function within the SQL SELECT statement so you have to use another PL/SQL piece of code; an anonymous PL/SQL block is OK.
SQL> set serveroutput on
SQL> declare
2 v_categories varchar2(10) := 'ABC';
3 v_id number := 1;
4 v_boolean boolean;
5 v_error varchar2(200);
6 begin
7 get_categories(l_categories => v_categories,
8 l_id => v_id,
9 l_boolean => v_boolean,
10 l_error => v_error
11 );
12
13 dbms_output.put_line(case when v_boolean then 'true' else 'false' end ||' - '||
14 v_error
15 );
16 end;
17 /
true -
PL/SQL procedure successfully completed.
SQL>

What does SELECT INTO var do if the statement returns more than one row?

Given a PL/SQL function which looks a bit like:
Function f(pVar IN VARCHAR2) return VARCHAR2 IS
vs_ret VARCHAR2 := NULL;
BEGIN
select name into vs_ret from people where nickname = pVar;
return vs_ret;
END f;
What happens if people.nickname has no uniqueness constraint? What happens if (when) two people have the same nickname - will it lead to an error or just return the value from first row the statement returns?
This appears to be existing functionality which I'm tweaking, so options are somewhat limited to change everything.
It will throw a predefined TOO_MANY_ROWS (ORA-01422) exception. You can handle the exception like this:
CREATE FUNCTION f(
pVar IN VARCHAR2
) RETURN VARCHAR2
IS
vs_ret VARCHAR2;
BEGIN
SELECT name
INTO vs_ret
FROM people
WHERE nickname = pVar;
RETURN vs_ret;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RETURN NULL; -- or you could do: RETURN 'Error: Too Many Rows';
WHEN NO_DATA_FOUND THEN
RETURN NULL; -- or you could do: RETURN 'Error: Not Found';
END f;
Or, you can leave the exception unhandled; in which case, the exception will get passed back up the hierarchy of calling blocks and each will get a chance to handle it and if it remains unhandled will terminate the query with a ORA-01422: exact fetch returns more than requested number of rows.
An alternative, if you only want the first name returned regardless of how many matches there actually are, is to add AND ROWNUM = 1 to the WHERE clause of the SELECT query (that way there will never be more than one row returned - although there could still be zero rows returned).
Another alternative, if you really do want multiple values (or no values) returned, is to use BULK COLLECT INTO and a collection:
CREATE FUNCTION f(
pVar IN VARCHAR2
) RETURN SYS.ODCIVARCHAR2LIST
IS
vs_ret SYS.ODCIVARCHAR2LIST;
BEGIN
SELECT name
BULK COLLECT INTO vs_ret
FROM people
WHERE nickname = pVar;
RETURN vs_ret;
END f;
unsure if this is useful to you but in instances where I don't care about too many rows or no data found I change it to a cursor.
that way in the no data found it doesn't go into the loop, and for too many rows it just loops that many times.
/* Formatted on 12/17/2015 8:18:33 AM (QP5 v5.115.810.9015) */
FUNCTION f (pVar IN VARCHAR2)
RETURN VARCHAR2
IS
vs_ret VARCHAR2 := NULL;
BEGIN
FOR c IN (SELECT name
FROM people
WHERE nickname = pVar)
LOOP
vs_ret := c.name;
END LOOP;
RETURN vs_ret;
END f;
You will get an error, that you might want to catch if you know something meaningful to do when the error occurs.
drop table people;
create table people (name varchar2(200), nickname varchar2(200));
insert into people values('name1','nick1');
insert into people values('name1','nick1');
select * from people;
create or replace function f(pVar IN VARCHAR2)
return VARCHAR2
IS
vs_ret people.name%type;
BEGIN
select name into vs_ret from people where nickname = pVar;
return vs_ret;
END f;
select f('nick1') from dual;
--- ==> error
create or replace function f(pVar IN VARCHAR2)
return VARCHAR2
IS
vs_ret people.name%type;
BEGIN
select name into vs_ret from people where nickname = pVar;
return vs_ret;
exception
when TOO_MANY_ROWS then
return null; -- This is no good solution but just to demo it
END f;
select f('nick1') from dual;
-- ==> null as Output
Script Output
Table dropped.
Table created.
1 row created.
1 row created.
NAME
--------------------------------------------------------------------------------
NICKNAME
--------------------------------------------------------------------------------
name1
nick1
name1
nick1
2 rows selected.
Function created.
select f('nick1') from dual
*
Error at line 1
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "DB.F", line 6
Function created.
F('NICK1')
--------------------------------------------------------------------------------
1 row selected.

PL / SQL Function to return varchar2 / numbers

I have this PL / SQL function that accepts the name of a student (f_name). The function then displays all of the information for the given student from a premade table called students. The table contains 5 columns, 2 number type, and 3 varchar2 type. If the name isn't found in the table an error message is returned. My code so far is
CREATE OR REPLACE FUNCTION studentName(
f_name IN VARCHAR2)
RETURN
IS
v_test students%rowtype;
CURSOR c1
IS
SELECT * FROM students WHERE first_name = f_name;
BEGIN
OPEN c1;
FETCH c1 INTO v_test;
IF c1%notfound THEN
v_test := NULL;
END IF;
CLOSE c1;
RETURN v_test;
END;
I keep getting:
PLS-00382: expression is of wrong type
I believe from my initial return varchar2 statement. How do I allow the return to accept both varchar2 type and number type?
RETURN varchar2
You need to return the rowtype, but you are returning a scalar. VARCHAR2 cannot hold a row, it can hold only a string value.
Modify it to:
RETURN students%rowtype;
Demo using standard EMP table:
SQL> CREATE OR REPLACE FUNCTION studentName(
2 f_name IN VARCHAR2)
3 RETURN emp%rowtype
4 IS
5 v_test emp%rowtype;
6 CURSOR c1
7 IS
8 SELECT * FROM emp WHERE ename = f_name;
9 BEGIN
10 OPEN c1;
11 FETCH c1 INTO v_test;
12 IF c1%notfound THEN
13 v_test := NULL;
14 END IF;
15 CLOSE c1;
16 RETURN v_test;
17 END;
18 /
Function created.
SQL> sho err
No errors.
NOTE : %ROWTYPE implies PL/SQL record type and PL/SQL types are not known to SQL. So you won't be able to use the function directly in plain SQL. You need to use SQL object type. Else you will get:
ORA-06553: PLS-801: internal error [55018]
Workaround to use it in SQL:
SQL> create or replace
2 type student_obj_type
3 as object(
4 student_id number,
5 stu_name varchar2(20),
6 dept varchar2(20)
7 )
8 /
Type created.
Use student_obj_type instead of students%rowtype to use the function in SQL.

NO_DATA_FOUND exception handling with Select Into MULTIPLE variables

I have looked for a while now for a solution to this issue, and all the NO_DATA_FOUND handling tutorials I found only show how to do it when doing a Select Into with only one column into one variable.
First, here is my code:
BEGIN
OPEN c_no_source;
LOOP
DECLARE
l_price_code VARCHAR2(20) := '';
l_price_level VARCHAR(10) := '';
l_code_date DATE := p_effective_date;
BEGIN
FETCH c_no_source INTO c_no_source_row;
exit WHEN c_no_source%NOTFOUND;
BEGIN
WITH codeList AS /* liste des dates ayant une donnée avant la date effective incluse. */
(
SELECT distinct
NVL(effective_date,p_effective_date) as effective_date,
NVL(user_group2,'') as price_code,
NVL(user_group3,'') as price_level
FROM DGA_Service.Hjvsecmaster_Hist
WHERE effective_date <= p_effective_date
AND user_group2 IS NOT NULL
AND security_alias = c_no_source_row.security_alias
ORDER BY 1 DESC
)
SELECT price_code, price_level, effective_date
INTO l_price_code, l_price_level, l_code_date
FROM codelist
WHERE ROWNUM = 1;
EXCEPTION WHEN no_data_found THEN NULL;
...
[UPDATE statement using the variables]
...
END;
END;
END LOOP;
CLOSE c_no_source;
END;
What I'm trying to do is take the top 1 result of my codeList temp result set into three separate variables.
However one of the three columns in codeList will often be NULL. And none of my 3 variables will be set.
I tried to handle the exception but I can't get my code to insert the columns that DO have a value into their respective variables. If even one of the columns is NULL then none of them are inserted into their variable.
I would like the Select Into statement to set the variables that it CAN set.
I tried to handle them all separately using something like this:
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
But I got the following error message:
ORA-06550: line 294, column 18:
PLS-00201: identifier 'PRICE_CODE' must be declared
ORA-06550: line 294, column 15:
PL/SQL: Statement ignored
ORA-06550: line 295, column 18:
PLS-00201: identifier 'PRICE_LEVEL' must be declared
ORA-06550: line 295, column 15:
PL/SQL: Statement ignored
ORA-06550: line 296, column 18:
PLS-00201: identifier 'EFFECTIVE_DATE' must be declared
ORA-06550: line 296, column 15:
PL/SQL: Statement ignored
So I ran out of ideas. I tried specifying the temp table name, to no avail, like this:
IF codeList.price_code IS NOT NULL
I would love any help on this issue. The package this piece of code runs in is already heavy and I would prefer not to have to get each column with a separate With ... As () Select Into clause.
Okay, I think I get you; most of your problem is caused by you nesting PL/SQL blocks incorrectly.
Your update statement is contained within the EXCEPTION block, which means it'll only get executed if the exception is thrown. Secondly, you're referencing the columns directly in the following:
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
This is the cause of your compilation error.
Lastly, if there is a single row in your select into then every variable will be set, so there's no need to try to deal with this.
BEGIN
OPEN c_no_source;
LOOP
DECLARE
l_price_code VARCHAR2(20);
l_price_level VARCHAR(10);
l_code_date DATE := p_effective_date;
BEGIN
FETCH c_no_source INTO c_no_source_row;
exit WHEN c_no_source%NOTFOUND;
BEGIN
WITH codelist AS (
SELECT DISTINCT effective_date
, user_group2 AS price_code
, user_group3 AS price_level
FROM hjvsecmaster_hist
WHERE effective_date <= p_effective_date
AND user_group2 IS NOT NULL
AND security_alias = c_no_source_row.security_alias
ORDER BY 1 DESC
)
SELECT price_code, price_level, effective_date
INTO l_price_code, l_price_level, l_code_date
FROM codelist
WHERE ROWNUM = 1;
EXCEPTION WHEN no_data_found THEN
-- All variables are their initial setting
null;
END;
...
[UPDATE statement using the variables]
...
END;
END LOOP;
CLOSE c_no_source;
END;
This is normally a highly inefficient way of doing this. If you can fit everything into a single UPDATE or MERGE then I would do so. You don't appear to have any additional logic so it should be possible.
However one of the three will often be NULL, and trigger the
NO_DATA_FOUND error, and none of my 3 variables will be set
NO_DATA_FOUND is raised when the query does not return any rows. It has nothing to do with the column values. They could all be null and the statement would succeed.
EXCEPTION WHEN NO_DATA_FOUND THEN
BEGIN
IF price_code IS NOT NULL THEN l_price_code := price_code; END IF;
IF price_level IS NOT NULL THEN l_price_level := price_level; END IF;
IF effective_date IS NOT NULL THEN l_code_date := effective_date; END IF;
END;
your variables are prefixed with "l_" and you are using the column names eg.price_code in your comparision, hence the error. More importantly, NO_DATA_FOUND would mean all the variable values are null, so this exception block does not do anything.
All you'd probably need is this.
if you don't want to insert null values for a given id (if all are null)
BEGIN
insert into target_table (id, col1, col2, col3)
select id, col1, col2, col3
from (target_table)
where not (col1 is null and col2 is null and col3 is null);
commit;
end;
/
If there is no data for a given id, nothing is inserted. If atleast one of them is not null, then these values are inserted.