Can PL/SQL procedures call themselves recursively? - oracle

Using PL/SQL, it is possible to call a stored function from within that same function. This can be demonstrated with the following example:
CREATE OR REPLACE FUNCTION factorial(x in number)
RETURN number
IS
f number;
BEGIN
IF x = 0 THEN
f := 1;
ELSE
f := x * factorial(x-1);
END IF;
RETURN f;
END;
/
DECLARE
num number;
factorial number;
BEGIN
num := #
factorial := factorial(num);
dbms_output.put_line(' The factorial of '|| num || ' is ' || factorial);
END;
/
Can this be done using PL/SQL stored procedures as well?

Yes, you can write a procedure that calls itself recursively in PL/SQL. Here is an example - implementing the factorial.
With that said, don't ever write a procedure (or a function like yours) without error handling. If you don't understand why, change 5 to 5.3 in the anonymous block below, and you'll see why.
CODE window:
create or replace procedure fact ( x in number, x_fact out number )
as
begin
if x = 0 then x_fact := 1;
else fact(x-1, x_fact);
x_fact := x * x_fact;
end if;
end;
/
set serveroutput on
declare
z number;
begin
fact(5, z);
dbms_output.put_line(z);
end;
/
SCRIPT OUTPUT window (matching each "result" to the corresponding part of the code left as an exercise):
Procedure FACT compiled
PL/SQL procedure successfully completed.
120

You can certainly call PL/SQL functions recursively (with all the usual warnings about the dangers of doing so in any language!).
You are, however, going to run into trouble if you name a local variable the same as your function. You will, for example, get this error when you try to execute the block:
PLS-00222: no function with name 'FACTORIAL' exists in this scope

Related

Oracle PL/SQL speed of NVL/LENGTH/TRIM calls versus IS NOT NULL AND != ' '

I try to find the best way to check if a CHAR/VARCHAR2 variable contains characters (NULL or spaces should be considered the same, as "no-value"):
I know there are several solutions, but it appears that (NVL(LENGTH(TRIM(v)),0) > 0) is faster than (v IS NOT NULL AND v != ' ')
Any idea why? Or did I do something wrong in my test code?
Tested with Oracle 18c on Linux, UTF-8 db charset ...
I get the following results:
time:+000000000 00:00:03.582731000
time:+000000000 00:00:02.494980000
set serveroutput on;
create or replace procedure test1
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if v is not null and v != ' ' then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
create or replace procedure test2
is
ts timestamp(3);
x integer;
y integer;
v char(500);
--v varchar2(500);
begin
ts := systimestamp;
--v := null;
v := 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
for x in 1..50000000
loop
if nvl(length(trim(v)),0) > 0 then
y := x;
end if;
end loop;
dbms_output.put_line('time:' || (systimestamp - ts) ) ;
end;
/
begin
test1();
test2();
end;
/
drop procedure test1;
drop procedure test2;
quit;
The best practice is to ignore the speed difference between small functions and use whatever is easiest.
In realistic database programming, the time to run functions like NVL or IS NOT NULL is completely irrelevant compared to the time needed to read data from disk or the time needed to join data. If one function saves 1 seconds per 50 million rows, nobody will notice. Whereas if a SQL statement reads 50 million rows with a full table scan instead of using an index, or vice-versa, that could completely break an application.
It's unusual to care about these kinds of problems in a database. (But not impossible - if you have a specific use case, then please add it to the question.) If you really need optimal procedural code you may want to look into writing an external procedure in Java or C.

Calling procedure in function

Can you call a PL/SQL procedure from inside a function?
I haven't come across with the practical example.So if anyone has come across with this please share.
Yes. You can call any pl/sql program from inside any other pl/sql program. A function can call a function, a procedure can call a procedure which calls a function, a function can invoke a TYPE BODY...do an INSERT..which causes a TRIGGER to fire.
A simple (not really practical) example.
Using the HR schema where you have an EMPLOYEES table which includes columns EMPLOYEE_ID, FIRST_NAME, and LAST_NAME.
I have a function that takes in an INTEGER which we use to look up an EMPLOYEE record. We take their first and last names, and concat them together, and return the value back in UPPERCASE text.
Before we do that however, we call a procedure which does nothing but take a 5 second nap using the DBMS_LOCK package.
The code:
create or replace procedure do_nothing_comments (x in integer, y in integer)
is
begin
null;
-- yeah, this is a dumb demo
dbms_lock.sleep(5);
end;
/
create or replace FUNCTION upper_name (
x IN INTEGER
) RETURN VARCHAR2 IS
upper_first_and_last VARCHAR2 (256);
BEGIN
SELECT upper (first_name)
|| ' '
|| upper (last_name)
INTO upper_first_and_last
FROM employees
WHERE employee_id = x;
do_nothing_comments (1, 2); -- here we are calling the procedure
RETURN upper_first_and_last;
END;
/
Now let's invoke the function.
DECLARE
X NUMBER;
v_Return VARCHAR2(200);
BEGIN
X := 101;
v_Return := UPPER_NAME(
X => X
);
:v_Return := v_Return;
END;
/
I'm going to do this in SQL Developer using the Execute feature with the function open:
I get the answer back...it just takes 5 seconds longer than it really needed to.
Here you go:
create or replace function demo
return varchar2
as
begin
dbms_output.put_line('Hello');
return 1;
end demo;

Finding the exponent (without using POW or built in functions) of a number using a procedure and/or Function

I am trying to create a way to find the exponent of a number (in this case the base is 4 and the exponent 2 so the answer should be 16) using a procedure without using the POW Function or any built in functions to find the exponent. Eventually I would like to take input numbers from the user.
set serveroutput on;
CREATE OR REPLACE PROCEDURE Exponent(base number, exponent number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
/
Error(7,25): PLS-00103: "expression 'BASE' cannot be used as an assignment target" and "expression 'EXPONENT' cannot be used as an assignment target"
Any ideas on how to solve the error and/or better ways of getting the exponent without using built-in functions like POW?
In your procedure base and exponent are input parameters and can't be changed. You've got a couple of options:
1) copy the parameters to variables internal to the procedure and manipulate those internal values, or
2) change the parameters to be input/output parameters so you can change them.
Examples:
1)
CREATE OR REPLACE PROCEDURE Exponent(pin_base number, pin_exponent number) as
base number := pin_base;
exponent number := pin_exponent;
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
2)
CREATE OR REPLACE PROCEDURE Exponent(base IN OUT number,
exponent IN OUT number) as
answer number;
BEGIN
base := 4;
exponent := 2;
LOOP
IF exponent > 1 THEN
answer := base * base;
END IF;
END LOOP;
dbms_output.put_line('Answer is: ' || answer);
END;
The best thing is that whatever Oracle provides as inbuilt functionality that serves the purpose in a best possible. (Almost all the times better then customized codes) Try to use EXP function. I have tried to make customized code per my understanding. Hope this helps.
CREATE OR REPLACE
FUNCTION EXP_DUMMY(
BASE_IN IN NUMBER,
EXPO_IN IN NUMBER)
RETURN PLS_INTEGER
AS
lv PLS_INTEGER:=1;
BEGIN
FOR I IN
(SELECT base_in COL1 FROM DUAL CONNECT BY level < expo_in+1
)
LOOP
lv:=lv*i.col1;
END LOOP;
RETURN
CASE
WHEN EXPO_IN = 0 THEN
1
ELSE
lv
END;
END;
SELECT EXP_DUMMY(2,4) FROM DUAL;

Call package into a procedure, Oracle PL/SQL

I'm trying to figure out this homework assignment we're supposed to solve in Oracle's 11g PL/SQL, but neither me nor my four friends have found a solution to this problem yet, and we're hoping someone more talented than us would give us a few pointers and/or comments about it.
Let's begin with the assignment in question (it's divided into two parts, and it's the last part we can't figure out):
First, create a procedure which fetches the current time with DBMS_UTILITY.GET_TIME, then performs a complex calculation. In the end, present how long it took (in seconds) to get the result/reach the answer.
And we've solved that one very well by creating a for-loop that calculates prime numbers (took roughly 5 seconds to calculate each prime number from 1 to 5000), however, the last part says this:
Now, create a package that will contain this complex calculation, and then re-write your procedure so that it calls in the "package method" of that calculation. Your procedure will now just measure the starting-time, call in the package with the calculation, and present how long it took (in seconds) to get the result/reach the answer.
So, to recap: the procedure will measure the time it takes for the package to make the calculation. And this is where we're stuck. There's not much information on the net about this, but my code looks like this:
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE time_measure_procedure
AS
time_start NUMBER;
time_end NUMBER;
time_diff NUMBER;
BEGIN
time_start := dbms_utility.get_time;
--This is where I'd presumably call in the
--package into the procedure, whereas the time
--will be measured until the calculation is done.
heavy_calculation_package;
time_end := dbms_utility.get_time;
time_diff := ( time_end - time_start ) / 100;
dbms_output.Put_line('Time it took: ' || time_diff || ' seconds.');
END time_measure_procedure;
/
CREATE OR REPLACE PACKAGE BODY heavy_calculation_package
AS
PROCEDURE time_measure_procedure
IS
x NUMBER;
counter NUMBER;
BEGIN
--This is the prime number calculation, which
--we're supposed to call into the procedure.
FOR n IN 1 .. 5000 LOOP
counter := 0;
x := Floor(n / 2);
FOR i IN 2 .. x LOOP
IF ( MOD(n, i) = 0 ) THEN
counter := 1;
END IF;
END LOOP;
IF ( counter = 0 ) THEN
dbms_output.Put_line(n ||' is a prime number.');
END IF;
END LOOP;
END time_measure_procedure;
END heavy_calculation_package;
/
The errors I receive are typically:
object package_heavy_calc is invalid
cannot compile body of package_heavy_calc without its specification
Does anyone have any clue to why this isn't working? Any hints or pointers at all would be lovely!
You need to have a package in a valid state before you can call it. In this case you need to declare the package first like this:
CREATE OR REPLACE PACKAGE package_heavy_calc
AS
PROCEDURE heavy_calc;
END PACKAGE_HEAVY_CALC;
You can have a package without a body but not a body without a package. You could do this by putting the code in the package head. For your purposes declare the package and the package body, much easier.
As an aside naming is critical for good readable code. I would have named your package PKG_MATH and the procedure P_CALC_PRIME_NUMBERS so when you call it you have an idea of what it does. Naming however is like sport's teams, everyone has a favorite one and the style can be quite different.
Please add the package specs and then try. Below is a snippet which
will help you.
CREATE OR REPLACE PACKAGE package_heavy_calculation
AS
PROCEDURE heavy_calc;
END package_heavy_calculation;
CREATE OR REPLACE PACKAGE BODY package_heavy_calculation
AS
PROCEDURE heavy_calc
IS
x NUMBER;
counter NUMBER;
BEGIN
--This is the prime number calculation, which
--we're supposed to call into the procedure.
FOR n IN 1 .. 5000
LOOP
counter := 0;
x := Floor(n / 2);
FOR i IN 2 .. x
LOOP
IF ( MOD(n, i) = 0 ) THEN
counter := 1;
END IF;
END LOOP;
IF ( counter = 0 ) THEN
dbms_output.Put_line(n ||' is a prime number.');
END IF;
END LOOP;
END heavy_calc;
END package_heavy_calculation;

Oracle, calling PL/SQL issues from within SQL-Plus file x.sql says my_function "may not be a function"

so simple, if I create my function as CREATE OR REPLACE FUNCTION MD5_ENCODE it will run smoothly, but if it stays anonymously within the SQL-Plus block as PL/SQL --> "may not be a function" error.
What is this Oracle "feature" again?
DECLARE
FUNCTION MD5_ENCODE(CLEARTEXT IN VARCHAR2) RETURN VARCHAR2 IS
CHK VARCHAR2(16);
HEX VARCHAR2(32);
I INTEGER;
C INTEGER;
H INTEGER;
BEGIN
IF CLEARTEXT IS NULL THEN
RETURN '';
ELSE
CHK := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT_STRING
=> CLEARTEXT);
FOR I IN 1 .. 16 LOOP
C := ASCII(SUBSTR(CHK, I, 1));
H := TRUNC(C / 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
H := MOD(C, 16);
IF H >= 10 THEN
HEX := HEX || CHR(H + 55);
ELSE
HEX := HEX || CHR(H + 48);
END IF;
END LOOP;
RETURN HEX;
END IF;
END;
BEGIN
UPDATE ADDRESSES_T SET STREET = MD5ENCODE(STREET) ;
-- etc...
END
http://forums.oracle.com/forums/thread.jspa?threadID=245112
There are a number of things it could be.
My #1 candidate is, we can only use functions in SQL statements that are public i.e. declared in a package spec. This is the case even for SQL statements executed within the same Package Body. The reson is that SQL statements are executed by a different engine which can only see publicly declared functions.
Long story short, function must be defined in a package, like CREATE OR REPLACE FUNCTION MD5ENCODE(IN_TEXT IN VARCHAR2) RETURN VARCHAR2 IS ...
In order to call a function within a SQL statement, it needs to exist as a function object within the database. The SQL statement is parsed and executed separately from your PL/SQL code, so it doesn't have access to the locally defined function.
If you really don't want to create the function object, you could use a cursor to loop over the rows in the table, execute the function in a PL/SQL statement, and update each row as you go.
Your first problem is you have a typo.
FUNCTION MD5_ENCODE(CLEARTEXT IN
VARCHAR2) RETURN VARCHAR2 IS
versus
UPDATE ADDRESSES_T SET STREET =
MD5ENCODE(STREET) ;
You are missing the underscore in the function call in the update statement.
The next error you will encounter is :
PLS-00231: function 'MD5_ENCODE' may not be used in SQL
So you can simply assign the function results to a variable and use it in the Update statement.
As it mentioned above comments, this could be useful.
DECLARE
V NUMBER := 0;
FUNCTION GET_SQ(A NUMBER) RETURN NUMBER AS
BEGIN
RETURN A * A;
END;
BEGIN
V := GET_SQ(5);
--DBMS_OUTPUT.PUT_LINE(V);
UPDATE MYTABLE A SET A.XCOL = V;
END;

Resources