PL/SQL recursive function return no value - oracle

I got the error message while running Oracle PL/SQL recursive function
function returned without value
Anyone knows what might be the issue?
Here's my function
FUNCTION cgic (cnt IN NUMBER)
RETURN VARCHAR2
IS
n_inv_code VARCHAR2 (20);
t_ic_chk NUMBER;
BEGIN
SELECT DBMS_RANDOM.STRING ('X', 10)
INTO n_inv_code
FROM DUAL;
select count(*) into t_ic_chk from inv_code where inv_code = n_inv_code and rownum = 1;
IF t_ic_chk = 1
THEN
n_inv_code := cgic(cnt);
ELSE
IF t_ic_chk = 0
THEN
RETURN n_inv_code;
END IF;
END IF;
END cgic;

In the event t_ic_chk = 1
you assign the value of the recursive function back to the variable: n_inv_code
however, you don't DO anything with it.
You probably want to return it.
I would recommend this code in your final section:
IF t_ic_chk = 1
THEN
n_inv_code := cgic(cnt);
END IF;
RETURN n_inv_code;
END cgic;
That's all you need:
1) if you find a row, recurse back in until you can't find one, and return that value.
2) if you can't find a row, return that value back.
3) in the event you found a row, just hand-shake the value returned back to whoever called you.

The code starting with IF t_ic_chk = 1 might be replaced with
CASE t_ic_chk
WHEN 0 THEN RETURN n_inv_code;
WHEN 1 THEN RETURN cgic(cnt);
ELSE RAISE_APPLICATION_ERROR(-20202, 'Unexpected t_ic_chk value=' || t_ic_chk);
END;
Done this way your function will either return an expected value or will raise an exception if it doesn't know what to do with the value it finds in t_ic_chk.
I do wonder why you're passing in the cnt parameter as it's never used in the procedure, except to pass it on in the recursive call.
Best of luck.

Related

Add inline function with IF/ELSE clause in Oracle 12c

I have table A. If I make a query with inline function
with function f(n number) return varchar2 as
begin
return 'const string';
end;
select id, val, count, f(count) as value from A;
the result will be following:
ID VAL COUNT VALUE
---------- -------------------- ---------- ---------------
1 car 4 const string
2 building 15 const string
But if I try to make the function more complicated
with function f(n number)
return varchar2 as
begin
IF n < 5 THEN
return 'small';
ELSIF n < 50 THEN
return 'normal';
ELSE
return 'big';
END IF;
end;
select id, val, count, f(count) as value from A;
an error message appears:
with function f(n number)
*
ERROR at line 1:
ORA-00905: missing keyword
What's the problem here? Do I use right syntax for the command?
Your if statement is missing a then after the elsif condition clause, hence the missing keyword error pointing to function f. Also, after you fix this error you may get a ORA-00933: SQL command not properly ended pointing to the last semi-colon. Interestingly, the ";" does not seem to work as a terminator to the SQL statement when the PL/SQL declaration is included in the WITH clause. If we attempt to use it on its own, SQL*Plus waits for more text to be entered. So you have to end it with a / on a new line. Even in the example in SQL Reference manual uses a combination of ; and /. Here is my example I tested in PL/SQL Developer 11:
WITH
FUNCTION f(n number) return varchar2 IS
begin
if n<5 then
return 'small';
elsif (n>5 AND n<50) then
return 'medium';
else
return 'big';
end if;
end;
select f(25) from dual
/
Output:
F(25)
medium
EDIT: Also, change your AS to IS in your function definition.

Comparing number with varchar2

I have this function and I need to compare number with varchar.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
begin
declare odd integer;
declare i_perecentage=0;
begin
if i_odd ='SP'
then
return (0);
end if;
odd:=round(to_number((1-i_perecentage/100)*i_odd),2);
if odd<1
then
return(i_odd);
else
return(round(odd,2));
end if;
end;
end;
/
PS: I edited function and i resolve problem with comparing , now i have another situation that i dont like..
This function returns calculated percentage of i_odd. The problem is that if i pass 0 in i_percentage in results i get result with no decimal places(for example: i_odd = 3.10 and i_percentage = 0 i get odd = 3 but if I pass i_odd = 3.10 and i_percentage = 1 i get odd = 3.10 ).
Why is on i_percentage = 0 i dont get decimal places ??
If you want to validate a varchar2 field as a number in PL/SQL, typically you'd just try converting it to a number and catch the exception.
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
BEGIN
-- if i_odd = 'SP' (or another non-number), this will throw an ORA-01722
-- exception which will be caught in the exception block, below
odd := to_number(i_odd); -- you might want a format mask here
--... now you can use "odd" as a number
EXCEPTION WHEN INVALID_NUMBER THEN
return 0;
END;
/
You can also nest a begin..end block in the middle of your code just to catch exceptions, if that works better for you:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return number as
odd number;
begin
begin
odd := to_number(i_odd); -- you might want a format mask here
exception when INVALID_NUMBER then
odd := 0;
end;
--... now you can use "odd" as a number
end;
/
The reason why you can't catch the invalid_number exception is because you are declaring the input parameter as a number. When you call your function, Oracle tries to convert the string to a number first (and it fails of course, before entering your code at all).
If you change the input parameter to varchar2, then the conversions to number (implicit in this case) is done inside the function, and invalid numbers can be caught and handled as you want (here I'm just returning a different string to denote the issue):
create or replace function is_odd_even(i_num in varchar2)
return varchar2
is
begin
-- conversion to number is done here
if (mod(i_num, 2) = 0) then
return 'EVEN';
else
return 'ODD';
end if;
exception
when INVALID_NUMBER or VALUE_ERROR then
-- do something meaningful
return 'INV';
end;
Usage example:
with x as (
select '1' as val from dual
union all
select 'SP' as val from dual
union all
select '2' as val from dual
)
select x.val, is_odd_even(x.val)
from x;
Output:
1 ODD
SP INV
2 EVEN
SOLUTION:
CREATE OR REPLACE FUNCTION getOdds(i_odd in varchar2, i_id in number) return varchar2 as
odd varchar2(10);
ret_value number(4);
begin
if (i_odd ='SP') or i_odd is null then
return 'SP';
else
odd :=ROUND( TO_NUMBER( ( 1 - (play_beting.get_odds_percentage(i_id) / 100 ) ) * TO_NUMBER(i_odd) ), 2);
IF(odd < 1) THEN
ret_value := TO_NUMBER(i_odd);
ELSE
ret_value := to_char(odd,'9999.00');
END IF;
END IF;
RETURN to_char(ret_value,'9999.00');
END getOdds;

Where in stored procedure I can make exit status?

I have oracle stored procedure where i check sender I'd,source system, and transaction number at the beginning of the procedure. Can I do it this way:
If Id != "aaa"
Exit -1;
Else if source = " ".
Exit -1;
Else if trans = " ".
Exit -1;
Else.
-- continues stored procedure
I appreciate any help
To rephrase your question more generally, you want a caller of your routine to know if something bad has happened inside it. There are (at least) three ways of doing this in PL/SQL.
Use an OUT parameter
Procedure cannot return a value, the way a function does, but it can set an output parameter:
CREATE PROCEDURE inner (p_id IN VARCHAR2(10), p_res OUT NUMBER)
IS
BEGIN
p_res := 0; -- default value
IF p_id = 'aaa' THEN
p_res := -1;
RETURN;
ELSE
-- do something
END IF;
END;
Then in the caller you would have:
DECLARE res NUMBER;
...
inner('aaa', res);
IF res = -1 THEN
-- panic!
END IF;
...
Use a function
Despite your seeming aversion to functions, this might be an option.
CREATE FUNCTION inner (p_id IN VARCHAR2(10))
RETURN NUMBER
IS
BEGIN
IF p_id = 'aaa' THEN
RETURN -1;
END IF;
-- do something
RETURN 0;
END;
Then in the caller:
...
IF inner('aaa') = -1 THEN
-- panic!
END IF;
...
Use an exception
Similar to other programming languages, PL/SQL has exceptions:
CREATE PROCEDURE inner (p_id IN VARCHAR2(10))
IS
BEGIN
IF p_id = 'aaa' THEN
RAISE_APPLICATION_ERROR(-20000, 'ID cannot be ''aaa''');
ELSE
-- do something
END IF;
END;
and in the caller:
...
DECLARE
panic EXCEPTION; -- declare exception
PRAGMA EXCEPTION_INIT (panic, -20000); -- assign error code to exception
...
BEGIN
inner ('aaa');
EXCEPTION
WHEN panic THEN
-- proceed to panic
END;
You are using a wrong syntax both for the IF...ELSE and for the exit.
Given that you are saying you need to get a return value, you probably need a function like this, using CASE:
create or replace function testFun ( pIn1 varchar2, pIn2 varchar2) return varchar2 is
begin
case
when pIn1 is null then
return -1;
when pIn2 = ' ' then
return -2;
else
return 999;
end case;
end;

Get a array of values form a PLSQL Function

I coded a function to select some flux in a queue and lock them with an updated flag.
I made it with a cursor and it worked great. But i need to get the ID of the flux i locked to process them in my application.
So i start to code a function:
CREATE OR REPLACE Function getIDArray
RETURN VARCHAR2 is
arr varchar2(1000);
CURSOR flux_to_process
IS
SELECT FLUX_ID, LOCKED_FLAG
FROM (
SELECT FLUX_ID, FLUX, GROUP_STORE_ID, STORE_ID, REFID, FLUX_TYPE, LOCKED_FLAG
FROM DEV_ISB_TRANSACTIONS.BUFFER_FLUX
WHERE status = 0
AND LOCKED_FLAG = 0
ORDER BY DATE_CREATION ASC)
WHERE ROWNUM <= 8;
BEGIN
FOR flux_rec IN flux_to_process
LOOP
IF flux_rec.LOCKED_FLAG = 0
THEN
UPDATE DEV_ISB_TRANSACTIONS.BUFFER_FLUX
SET LOCKED_FLAG = 1
WHERE FLUX_ID = flux_rec.FLUX_ID;
arr := flux_rec.FLUX_ID;
else exit;
COMMIT;
END IF;
END LOOP;
RETURN arr;
END;
The function compilation return an OK but i got no return of my values.
Do you guys have any clue to how to do this ?
Concerning your issue per se, the only two reasons I can see for the function to return "no value" would be either:
the SELECT part returns an empty set,
you have one record where FLUX_ID is NULL.
For improbable that could be that later option given the name of the column, it would be rather coherent with the fact that you override the result at each iteration -- and the ORDER BY orders NULL after not-NULL by default.

Is it possible to use "return" in stored procedure?

CREATE PROCEDURE Pname(in_Tid IN VARCHAR2,in_IP IN VARCHAR2,outstaticip OUT VARCHAR2,outcount OUT NUMBER)
AS
BEGIN
select STATIC_IP into outstaticip from OP_TTER_MAPPING where TERMINAL_ID = in_Tid;
if in_IP = outstaticip then
return 1;
else
select COUNT(*) into outcount from OP_TTER_MAPPING where DYNAMIC_IP_LOW <= in_IP AND DYNAMIC_IP_HIGH >= in_IP AND TERMINAL_ID = in_Tid;
if outcount = 1 then
return 1;
else
return 0;
end if;
end if;
END;
Is it possible to use return in stored procedure like above?
If we can use return, how can i get that return value in Executesql("begin Pname(----)END") method
EDIT
Now I edited my return value in stored procedure like this, am I doing it right ?
CREATE PROCEDURE P_ValidateTIDIP(in_Tid IN VARCHAR2,in_IP IN VARCHAR2,outstaticip OUT VARCHAR2,outcount OUT NUMBER,outretvalue OUT NUMBER)
AS
BEGIN
select STATIC_IP into outstaticip from OP_TTER_MAPPING where TERMINAL_ID = in_Tid;
if in_IP = outstaticip then
outretvalue:=1;
else
select COUNT(*) into outcount from OP_TTER_MAPPING where DYNAMIC_IP_LOW <= in_IP AND DYNAMIC_IP_HIGH >= in_IP AND TERMINAL_ID = in_Tid;
if outcount = 1 then
outretvalue:=1;
else
outretvalue:=0;
end if;
end if;
END;
In Stored procedure, you return the values using OUT parameter ONLY. As you have defined two variables in your example:
outstaticip OUT VARCHAR2, outcount OUT NUMBER
Just assign the return values to the out parameters i.e. outstaticip and outcount and access them back from calling location. What I mean here is: when you call the stored procedure, you will be passing those two variables as well. After the stored procedure call, the variables will be populated with return values.
If you want to have RETURN value as return from the PL/SQL call, then use FUNCTION. Please note that in case, you would be able to return only one variable as return variable.
Use FUNCTION:
CREATE OR REPLACE FUNCTION test_function
RETURN VARCHAR2 IS
BEGIN
RETURN 'This is being returned from a function';
END test_function;
-- IN arguments : you get them. You can modify them locally but caller won't see it
-- IN OUT arguments: initialized by caller, already have a value, you can modify them and the caller will see it
-- OUT arguments: they're reinitialized by the procedure, the caller will see the final value.
CREATE PROCEDURE f (p IN NUMBER, x IN OUT NUMBER, y OUT NUMBER)
IS
BEGIN
x:=x * p;
y:=4 * p;
END;
/
SET SERVEROUTPUT ON
declare
foo number := 30;
bar number := 0;
begin
f(5,foo,bar);
dbms_output.put_line(foo || ' ' || bar);
end;
/
-- Procedure output can be collected from variables x and y (ans1:= x and ans2:=y) will be: 150 and 20 respectively.
-- Answer borrowed from: https://stackoverflow.com/a/9484228/1661078
It is possible.
When you use Return inside a procedure, the control is transferred to the calling program which calls the procedure. It is like an exit in loops.
It won't return any value.
CREATE PROCEDURE pr_emp(dept_id IN NUMBER,vv_ename out varchar2 )
AS
v_ename emp%rowtype;
CURSOR c_emp IS
SELECT ename
FROM emp where deptno=dept_id;
BEGIN
OPEN c;
loop
FETCH c_emp INTO v_ename;
return v_ename;
vv_ename := v_ename
exit when c_emp%notfound;
end loop;
CLOSE c_emp;
END pr_emp;

Resources