Function in PL SQL (Oracle) Error - sql

CREATE OR REPLACE FUNCTION ComputeFreight(subtotal NUMBER)
RETURN NUMBER
IS
freightCharge NUMBER;
BEGIN
IF subtotal <= 15000 THEN
freightCharge := (subtotal * .10);
ELSIF subtotal > 15000 THEN
freightCharge := (subtotal * .15);
RETURN(freightCharge);
END IF;
END;
DECLARE
subtotal NUMBER := 15000;
BEGIN
DBMS_OUTPUT.PUT_LINE( ComputeFreight(subtotal) );
END;
I don't know whats wrong with my code but it always shows this error:
Invalid statement
Invalid statement
ORA-06503: PL/SQL: Function returned without value ORA-06512: at
"SQL_JQQXUMCMKFMRHRPZCYFNBEVTN.COMPUTEFREIGHT", line 12 ORA-06512: at
line 4 ORA-06512: at "SYS.DBMS_SQL", line 1721

The return statement should be outside the if-else:
CREATE OR REPLACE FUNCTION ComputeFreight(subtotal NUMBER)
RETURN NUMBER
IS
freightCharge NUMBER;
BEGIN
IF subtotal <= 15000 THEN
freightCharge := (subtotal * .10);
ELSIF subtotal > 15000 THEN
freightCharge := (subtotal * .15);
END IF;
RETURN(freightCharge); -- here
END;

If this is practice for the IF statement, that's fine (with GurV's suggested placement of the RETURN statement). However, if you actually need this in some sort of production, you may want to simplify the function and make it more efficient. Something like this:
CREATE OR REPLACE FUNCTION ComputeFreight(subtotal NUMBER)
RETURN NUMBER
IS
BEGIN
RETURN subtotal * (CASE WHEN subtotal <= 15000 THEN 0.1 ELSE 0.15 END);
END;

Related

PL/SQL CREATE PROCEDURE - factorial

I don't know what's wrong with my code block to create a procedure to find out the factorial of a number. Thank you.
Question 1: write a stored procedure that gets an integer number n and calculates and displays its factorial.
SET SERVEROUTPUT ON;
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER) AS
factorial NUMBER;
num Number;
BEGIN
FOR i IN REVERSE 1..n LOOP
num := i - 1;
factorial := factorial * num;
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
factorial_number(5);
END;
/
You're failing to initialize the local variable factorial. Uninitialized variables are initially null and multiplying null by any value produces null.
I don't see why you'd want your loop to go in reverse order. It doesn't matter since multiplication is communitive but it it unlikely to make your code easier to read/ debug/ follow.
You don't want to subtract 1 from the value you are multiplying by on each iteration of the loop. When i = 1, for example, you're multiplying factorial by 0 which means that (assuming you initialize factorial), you'd always end up with 0. You want to multiply by i so there is no need for the local variable num.
If I make those fixes
CREATE OR REPLACE PROCEDURE factorial_number(
n NUMBER)
AS
factorial NUMBER := 1; -- Initialize
BEGIN
FOR i IN 1..n LOOP -- Loop normally
dbms_output.put_line( 'Beginning iteration ' || i || ' of loop. ' ||
'Factorial = ' || factorial ||
' multiplying by ' || i );
factorial := factorial * i; -- Multiply by i not i-1
END LOOP;
DBMS_OUTPUT.PUT_LINE (factorial);
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.PUT_LINE ('Error!');
END;
Then
BEGIN
factorial_number(5);
END;
will print out 120 (5*4*3*2*1).
I'm also adding an additional dbms_output line to print out the current state of the variables on each iteration of the loop. That's a relatively old-school method of debugging. In the modern world, you'd walk through the code with a debugger where you can see the values of your local variables but introductory classes may not teach debugger usage initially.
How about
SQL> CREATE OR REPLACE PROCEDURE factorial_number (n NUMBER)
2 AS
3 factorial NUMBER := 1;
4 BEGIN
5 FOR i IN 1 .. n
6 LOOP
7 factorial := factorial * i;
8 END LOOP;
9
10 DBMS_OUTPUT.PUT_LINE (factorial);
11 END;
12 /
Procedure created.
SQL>
SQL> BEGIN
2 factorial_number (5);
3 END;
4 /
120
PL/SQL procedure successfully completed.
SQL>

PLSQL Procedural Logic

How to write a PL/SQL function that:
Uses only numeric datatypes and functions-no VARCHAR2, CHAR, CLOB, XML, etc.
" In other words, character/string functions such as REVERSE, SUBSTR, etc are not to be used in your solution.
Accepts a PLS_INTEGER parameter
If the provided value is less than or equal to zero throws application error -20001 and provides a good error message
Returns a PLS_INTEGER value that has the digits from the input parameter in reverse order. If the input value ends in one or more zeroes those zeroes will not appear in the returned numeric value, since they would be leading zeroes.
Here is what I have written so far:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_num pls_integer := 0;
p_in_num pls_integer := 0;
p_out_num pls_integer := 0;
begin
p_num := p_input;
loop
exit when p_num p_in_num := mod(p_num,10);
p_out_num := (p_out_num * 10) + p_in_num;
p_num := trunc(p_num / 10);
end loop;
return p_out_num;
end;
The problem is to turn 1234 into 4321. The string-y solution (admittedly using an undocumented built-in) is simplicity itself: to_number(reverse(to_char(1234))). Sticking to numeric datatypes is more cumbersome. My solution is very procedural: undoubtedly more elegant solutions exist.
Anyway, to turn 1234 into 4321 we need to generate 1 + 20 + 300 + 4000. My solution isolates each value in turn and multiplies by the appropriate power of ten. To isolate the values we use trunc() with a negative value. This rounds down the number to the left of the decimal point. Thus, trunc(1234,-3) produces 1000. To convert this into the required value we multiply by ten to the power of minus three. Thus 1000 * 10(-3) = 1.
The function iterates through the numbers. Having converted 1000 to 1 we calculate the remainder, 1234 - 1000 = 234. So now we need to isolate the 200 and convert it to 20; that is trunc(234, -2) and power(200, -1). So we can the offset fed into trunc() decrements by 1 and the exponent fed into power() increments by 2.
Here is a working function (very loosely based on the one you posted):
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
rmdr := p_input;
offset := length(p_input)-1;
exp := -offset;
loop
tgt := trunc(rmdr, -offset);
p_out_num := p_out_num + (tgt * power(10, exp));
exp := exp + 2;
rmdr := rmdr - tgt;
exit when offset = 0;
offset := offset-1;
end loop;
return p_out_num;
end test_reverse;
/
Here is a LiveSQL demo (free Oracle Technet account required, alas). That doesn't feature the parameter validation but it is straightforward enough:
create or replace function test_reverse
(p_input in pls_integer)
return pls_integer
is
p_out_num pls_integer := 0;
offset pls_integer;
tgt pls_integer;
rmdr pls_integer;
exp pls_integer;
begin
if p_input <= 0 then
raise_application_error(-20001
'invalid input: ' || || ' must be greater than zero.'
);
end if;
rmdr := p_input;
....
I like the use of the modulo function as the op suggested:
CREATE OR REPLACE FUNCTION test_reverse (
p_input IN PLS_INTEGER
) RETURN PLS_INTEGER
IS
remain PLS_INTEGER := p_input;
retval PLS_INTEGER := 0;
BEGIN
IF p_input < 1 THEN
raise_application_error(-20001,'error: input must me greater than 0');
END IF;
LOOP
retval := retval * 10 + MOD(remain,10);
remain := trunc(remain / 10);
EXIT WHEN remain = 0;
END LOOP;
RETURN retval;
END;
/
This also avoids the use of the length() function which to my taste feels a bit "stringish", even if it operates on numbers.

How can we achieve Standard Normal CDF in Oracle SQL or PL/SQL?

How can i achieve below functionality using Oracle SQL or PL/SQL?
This stored procedure gives the same result as NORMDIST function in Calc.
The parameters that need to be passed are x, mean, standard deviation and cumulative.
Cumulative parameter gives a choice to get normal distribution value at x (0) or cumulative probability of value<=x (1).
create or replace FUNCTION NORMDIST(x_value number,mean_value number,stddev_value number, cumulative NUMBER DEFAULT 0)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
IF cumulative = 1 THEN
z := abs(x)/SQRT(2);
t := 1/(1+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
ELSE
RETURN 1/(sqrt(2*3.14159265358979)*stddev_value)*Exp(-(Power(x_value-mean_value,2)/(2*Power(stddev_value,2)) ));
END IF;
END;
/
This is a quick solution, I have not tried to gain maximum precision or performance. Depending on your req, you might need to tweak number format, precision, calculation logic, etc.
create or replace function calc_sn_pdf(x in number) return number
is
pi CONSTANT NUMBER := 3.14159265358979;
begin
return 1/sqrt(2*pi) * exp(-x*x/2);
end;
/
The cdf must be approximated (as it is az integral function which has no simple mathematical formula), one possible approximation is implemented as follows. Many other approximations can be found on Wikipedia.
create or replace function calc_sn_cdf(x in number) return number
is
b0 CONSTANT NUMBER := 0.2316419;
b1 CONSTANT NUMBER := 0.319381530;
b2 CONSTANT NUMBER := -0.356563782;
b3 CONSTANT NUMBER := 1.781477937;
b4 CONSTANT NUMBER := -1.821255978;
b5 CONSTANT number := 1.330274429;
v_t number;
begin
--see 26.2.17 at http://people.math.sfu.ca/~cbm/aands/page_932.htm
--see https://en.wikipedia.org/wiki/Normal_distribution#Numerical_approximations_for_the_normal_CDF
--Zelen & Severo (1964) approximation
if x < 0 then
--this approximation works for x>0, but cdf is symmetric for x=0:
return 1 - calc_sn_cdf(-x);
else
v_t := 1 / (1 + b0*x);
return 1 - calc_sn_pdf(x)*(b1*v_t + b2*v_t*v_t + b3*v_t*v_t*v_t + b4*v_t*v_t*v_t*v_t + b5*v_t*v_t*v_t*v_t*v_t);
end if;
end;
/
Btw, if you need to run these functions a lot of time, it would be useful to turn on native pl/sql compilation.
--I wrote this function in PL/SQL and it works. I compared results with the NORMDIST
--Function in excel and the results match very closely. You will need to pass the
--following --parameters to the function.
-- 1. Value of X
-- 2. Value of Mean
-- 3. Value of Standard Deviation
--This function returns the same result when you pass cumulative=TRUE in excel.
create or replace FUNCTION NORMSDIST(x_value number,mean_value number,stddev_value number)
RETURN NUMBER IS
x number;
t number;
z number;
ans number;
BEGIN
IF (stddev_value = 0) THEN
RETURN 1;
END IF;
x := (x_value-mean_value)/stddev_value;
z := abs(x)/SQRT(2);
t := 1.0/(1.0+0.5*z);
ans := t*exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))))/2.0;
If (x <= 0)
Then RETURN ans;
Else return 1-ans;
End if;
END NORMSDIST;

pl/sql even and odd sum block

I have a pl/sql programming question: For numbers between 1..50, you need to multiply even numbers by five, odd numbers by 3 and then find sum of all the numbers in the result.
So I had this so far
DECLARE
ln_num NUMBER :=0;
ln_num1 NUMBER :=0;
ln_num2 NUMBER :=0;
BEGIN
for i in 1..50 loop
if mod(i,2) =0 then
ln_num:=i*5;
elsif mod(i,2) = 1 then
ln_num1:=i*3;
ln_num2 := ln_num+ln_num1;
dbms_output.put_line(ln_num2);
end if;
end loop;
END;
This gives me a last list of numbers but i need the sum of all of them. I was wondering what I was missing and how do I fix this?
Thanks
create or replace function odd_even_Numbers(num number)
return varchar2 is
value1 number;
message varchar2(30);
a number;
begin
a:=num;
loop
select abs(REMAINDER(a, 2)) into value1 from dual;
if (value1=1) then
message:= 'odd number';
dbms_output.put_line (a||'-----'||message);
a:=a+1;
else
message:='even number';
dbms_output.put_line (a||'-----'||message);
a:=a+1;
end if;
exit when a=20;
end loop;
message:='done well';
return message;
end;
SQL> DECLARE
2 ln_num NUMBER :=0;
3 ln_num1 NUMBER :=0;
4 ln_num2 NUMBER :=0;
5
6 BEGIN
7 for i in 1..50 loop
8
9 if mod(i,2) =0 then
10 ln_num:=ln_num+i*5; -- changes
11
12 elsif mod(i,2) = 1 then
13 ln_num1:=ln_num1+i*3; -- changes
14
15
16 end if;
17 end loop;
18 ln_num2 := ln_num+ln_num1;
19 dbms_output.put_line(' the result is ' || ln_num2);
20
21 END;
22
23
24 /
the result is 5125
PL/SQL procedure successfully completed.

oracle pl/sql ora-01722 error

I have a simple oracle statement in my procedure:
update org.security_training_question a
set a.actv_indr = 'N' where a.qstn_id in (v_qstns_to_delete);
v_qstns_to_delete is a parameter being passed. It is a varchar2 field and a.qstn_id is a numeric field.
When calling the Stored Procedure, for v_qstns_to_delete I am passing the following String: "24, 43, 23, 44, 21".
When I run the statement output the stored procedure thenn it runs fine but when I run it as a stored procedure I get an error on the above line saying Invalid Number.
Any clue?
You can't use a "in" clause with a variable like that. One way around it is
declare stmt varchar2(4000);
begin
stmt := 'update org.security_training_question a set a.actv_indr = ''N'' where a.qstn_id in ('||v_qstns_to_delete||')';
execute immediate stmt;
end;
if v_qstns_to_delete is a varchar, you would need to convert it somewhat to let Oracle understand that there may be several items in it. One method would be to convert the string to a table of items.
Supposing qstn_id is a NUMBER column, you would:
SQL> CREATE TYPE tab_number AS TABLE OF NUMBER;
2 /
Type created
SQL> CREATE OR REPLACE FUNCTION to_tab_number(p_in VARCHAR2,
2 p_separator VARCHAR2 DEFAULT ',')
3 RETURN tab_number AS
4 l_result tab_number := tab_number();
5 l_tail LONG := p_in;
6 BEGIN
7 WHILE l_tail IS NOT NULL LOOP
8 l_result.EXTEND;
9 IF instr(l_tail, p_separator) != 0 THEN
10 l_result(l_result.COUNT) := to_number(substr(l_tail,
11 1,
12 instr(l_tail, p_separator) - 1));
13 l_tail := substr(l_tail, instr(l_tail, p_separator) + 1);
14 ELSE
15 l_result(l_result.COUNT) := to_number(l_tail);
16 l_tail := NULL;
17 END IF;
18 END LOOP;
19 RETURN l_result;
20 END;
21 /
Function created
You could then convert a string to a table of number from SQL:
SQL> SELECT * FROM TABLE(to_tab_number('24, 43, 23, 44, 21'));
COLUMN_VALUE
------------
24
43
23
44
21
To do a variable in-list:
SQL> SELECT object_id, owner
2 FROM all_objects
3 WHERE object_id IN (SELECT column_value FROM TABLE(to_tab_number('18,19,20')));
OBJECT_ID OWNER
---------- ------------------------------
18 SYS
19 SYS
20 SYS
More on the same subject on askTom.