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;
Related
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.
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
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;
I want to make a procedure called OE that will have text output based on the number that I define.
For example, inputting the number 6 will give the following output:
odd
even
odd
even
odd
even
= even steven!
and inputting the number 5 will give the following output:
odd
even
odd
even
odd
= you oddball!
I'm completely new at this and have been struggling to get the odd number to load correctly (for some reason, it gets stuck in an infinite loop). Any help would be appreciated! Here is what I got so far:
CREATE OR REPLACE procedure oe
(p_n IN number)
AS
v_n number;
v_on number;
BEGIN
v_n := p_n;
v_on := p_n;
IF v_n>0 THEN LOOP
dbms_output.put_line('odd');
v_n := v_n-1;
dbms_output.put_line('even');
v_n := v_n-1;
If v_n=0 then
exit;
if v_on mod 2 > 0 then dbms_output.put_line('=' || ' you oddball!');
exit;
else
dbms_output.put_line('=' || ' even steven!');
exit;
end if;
end if;
end loop;
end if;
END;
/
You are not using exit conditions properly hence your code is going in infinite loop. You simplify your logic as below. Let me know it it works for you.
You may add few validations to make sure you get proper input parameters such as p_n > 0 and other.
CREATE OR REPLACE procedure oe
(p_n IN number)
AS
begin
for i in 1..p_n
loop
if mod(i,2)=1 then dbms_output.put_line('odd');
else dbms_output.put_line('even');
end if;
end loop;
if mod(p_n,2)=1 then dbms_output.put_line('= you oddball!');
else dbms_output.put_line('= even steven!');
end if;
end;
hemalp108 has already answered this, but I just wanted to add that you don't even need the if/else logic that fills the procedure (except perhaps for handling values less than 1, which I'll leave as an exercise), because we have case:
create or replace procedure oe
( p_n in number )
as
begin
for i in 1 .. p_n loop
dbms_output.put_line(case mod(i,2) when 1 then 'odd' else 'even' end);
end loop;
dbms_output.put_line(case mod(p_n,2) when 1 then '= you oddball!' else '= even steven!' end);
end;
(You may also notice how laying your code out neatly is half the way towards debugging it.)
I have a script that uses one VARRAY multiple times. But, I can't seem to figure out how to reset the VARRAY after looping through it once. I wrote the following basic script to help me troubleshoot:
DECLARE
TYPE multi_show_id_type IS VARRAY (60) OF VARCHAR2 (10);
multi_show_id multi_show_id_type := multi_show_id_type ();
counter NUMBER := 1;
i NUMBER := 1;
BEGIN
DBMS_OUTPUT.put_line ('BEGIN');
WHILE i < 10
LOOP
DBMS_OUTPUT.put_line (i);
--counter:=0;
--multi_show_id :=multi_show_id_type();
--multi_show_id.delete;
WHILE counter < 25
LOOP
multi_show_id.EXTEND ();
multi_show_id (counter) := counter * counter;
DBMS_OUTPUT.put_line ('VArray: [' || counter || '] [' || multi_show_id (counter) || ']');
counter := counter + 1;
END LOOP;
i := i + 1;
END LOOP;
DBMS_OUTPUT.put_line ('END');
END;
/
This script works when it is only looping through the array once. But if you uncomment the counter:=0 line, which forces it to loop through the array population loop 10 times, I get an ORA-06532 error. You can see some of the stuff I've tried in the other commented lines. Any help would be appreciated.
Actually, #akf is correct; your code as written won't work because a VARRAY starts at item 1, not zero.
Change your code thusly and it works:
...
LOOP
DBMS_OUTPUT.put_line (i);
counter:=1;
--multi_show_id :=multi_show_id_type();
multi_show_id.delete;
WHILE counter < 26
LOOP
...
EDIT: if you want to run thru the loop 25 times you do need to change the WHILE loop upper bound to 26...
there seems to be two problems here. first, the VARRAY index starts at 1. second, you will stop once your VARRAY is full at 60 items, as defined in your declaration.
use the following:
TYPE multi_show_id_type IS VARRAY (250) OF VARCHAR2 (10);
and
counter:=1;
uncomment the multi_show_id :=multi_show_id_type(); line if you want to start at 1 for each loop. if you want to ensure that no more than 4 values, your inner while loop should make that restriction.