PL/SQL - better to fetch value of a constant in other package or call it each time - oracle

This is a performance / efficiency question:
I have a package containing hundreds of global variables to be used throughout my PL/SQL solution. Is it better to fetch a value of the variable at the declaration stage once, or rather call it by its name each time the variable is referenced?
For example, in my global definitions package, I have a variable called "gv_SuperUserRole" which has a value assigned to it.
In my application, is it better to declare this as
local_var := glabal_package.gv_SuperUserRole
and then use the local_var throughout my current procedure like so:
if x < local_var
Or rather call the global variable like so:
if x < glabal_package.gv_SuperUserRole
Is the variable stored in the definitions package easily accessible by the local procedure or rather additional overhead has to take place each time I call the variable?

As far as I can tell, there's no difference. As soon as you invoke a package for the first time, the whole package is loaded into memory so there's no additional disk I/O when you reference anything from that package.
So, whether you assign a global variable value to a local variable and then work with a local variable, or you use only a global variable, there shouldn't be any (significant) difference.
Here's an example; I hope it makes sense.
SQL> create or replace package global_package as
2 gv_superuserrole number := 1000;
3 end;
4 /
Package created.
SQL>
SQL> create or replace procedure p_test1 as
2 local_var number := global_package.gv_superuserrole;
3 l_sum number := 0;
4 begin
5 if 100 < local_var then
6 l_sum := l_sum + 1;
7 end if;
8 end;
9 /
Procedure created.
SQL>
SQL> create or replace procedure p_test2 as
2 l_sum number := 0;
3 begin
4 if 100 < global_package.gv_superuserrole then
5 l_sum := l_sum + 1;
6 end if;
7 end;
8 /
Procedure created.
SQL>
SQL> set timing on
SQL>
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test1;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.06
SQL>
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test2;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.02
SQL>
SQL> -- Once again --------------------------------
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test1;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL>
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test2;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.04
SQL>
SQL> -- Once again --------------------------------
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test1;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.03
SQL>
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test2;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.05
SQL>
SQL> -- Once again --------------------------------
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test1;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.04
SQL>
SQL> begin
2 for i in 1 .. 100000 loop
3 p_test2;
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.05

In principle, declaring and populating another variable is an overhead as it’s another processing step and requires another memory location to be allocated, although in practice I doubt this will be measurable.
Referring to the original definitions directly will be better from a code readability point of view, as you won’t have to remember that local_var comes from global_package.gv_SuperUser_Role.
There could be a big difference in the program logic, if global_package.gv_SuperUser_Role is really a variable as its name implies rather than a constant, as your local copy might ensure that the value won’t change during processing. Or if local_var is a variable then it might just use global_package.gv_SuperUser_Role as an initial value and update it to something different in a later step.
If I had to support this code, I’d be a lot happier if global_package contained constants. I’ve known global variables make code virtually impossible to understand and predict.

if x < glabal_package.gv_SuperUserRole
Is better than
local_var := glabal_package.gv_SuperUserRole
if x < local_var
But as the previous answer mentioned, it does not make a lot of difference performance wise in the code execution.

Related

How to pick a particular package based on a particular table column value in Oracle?

I have a situation here:
We need to run procedure_1 in a package when a particular column say column X has data in it and simultaneously run another procedure_2 in package if there is no data in that column X
Can anyone please advice on what can be done using Oracle ?
Something like this?
table DECIDE contains PARTICULAR_COLUMN
package has two simple procedures which do nothing; they just identify themselves
.
SQL> create table decide
2 (id number,
3 particular_column varchar2(1));
Table created.
SQL> insert into decide values (1, 'X');
1 row created.
SQL> insert into decide values (2, null);
1 row created.
SQL> create or replace package pkg_decide as
2 procedure p1;
3 procedure p2;
4 end;
5 /
Package created.
SQL> create or replace package body pkg_decide as
2 procedure p1 is
3 begin
4 dbms_output.put_line('-> running proc 1');
5 end;
6
7 procedure p2 is
8 begin
9 dbms_output.put_line('-> running proc 2');
10 end;
11 end;
12 /
Package body created.
SQL>
Testing: if the PARTICULAR_COLUMN isn't empty, P1 will run; otherwise, P2. That can be done with dynamic SQL, i.e. EXECUTE IMMEDIATE. As we're going to run a procedure, we have to form a full anonymous PL/SQL block (begin - procedure name - end).
P.S. As suggested by Matthew, a simple IF will do.
SQL> begin
2 for cur_r in (select particular_column,
3 case when particular_column is not null then 'pkg_decide.p1'
4 else 'pkg_decide.p2'
5 end prc_name
6 from decide
7 ) loop
8 dbms_output.put_line('particular column = ' || cur_r.particular_column ||
9 ', should run procedure ' || cur_r.prc_name);
10 execute immediate 'begin ' ||cur_r.prc_name ||'; end;';
11
12 -- Or, as suggested by Matthew, a simple IF will do
13 if cur_r.particular_column is not null then
14 pkg_decide.p1;
15 else
16 pkg_decide.p2;
17 end if;
18 end loop;
19 end;
20 /
particular column = X, should run procedure pkg_decide.p1
-> running proc 1
-> running proc 1
particular column = , should run procedure pkg_decide.p2
-> running proc 2
-> running proc 2
PL/SQL procedure successfully completed.
SQL>

Procedure add plus one (+1) everty time is called in PL/SQL

I have to do a procedure that add plus 1 to the previous value every time its is called in PL/SQL language. But I don't know how to do that.
I mean, if the procedure is call "plus1":
First execution:
exec plus1
will return value 1.
Second execution:
exec plus1
will return value 2.
And go on
The best way is to create a sequence, as noticed in comments:
create sequence my_seq;
To call the sequence in PL/SQL:
my_var := my_seq.nextval;
To call in SQL:
select t.*, my_seq.nextval from table t;
In the SQL query, a new value will be generated for each line.
If you don't need a sequence, and you don't need to store the value between sessions, create a package:
create or replace package my_package as
function get_next_value return number;
end my_package;
/
create or replace package body my_package as
current_num number := 0;
function get_next_value return number is
begin
current_num := current_num + 1;
return current_num;
end;
end my_package;
/
And then call my_package.get_next_value.
It is not entirely clear what you need. Here is one approach, assuming you need a session variable, initialized to zero at the start of the session, which you can call as needed, and is increased only when a procedure is executed. This is different from a function that increments the variable and returns it at the same time.
If you need to access the variable in SQL (rather than just in PL/SQL), you need to write a wrapper function that returns the value; I included the wrapper function in the code below.
create or replace package silly_p as
v number := 0;
function show_v return number;
procedure increment_v;
end;
/
create or replace package body silly_p as
function show_v return number is
begin
return v;
end show_v;
procedure increment_v is
begin
v := v+1;
end increment_v;
end silly_p;
/
Here is a SQL*Session demonstrating the compilation of this package and then its use - I access the variable both through SQL SELECT and from PL/SQL (with DBMS_OUTPUT) to demonstrate both access methods. Notice how the value is unchanged between calls to the procedure, and increases by one every time the procedure is executed.
SQL> create or replace package silly_p as
2 v number := 0;
3 function show_v return number;
4 procedure increment_v;
5 end;
6 /
Package created.
Elapsed: 00:00:00.03
SQL>
SQL> create or replace package body silly_p as
2 function show_v return number is
3 begin
4 return v;
5 end show_v;
6 procedure increment_v is
7 begin
8 v := v+1;
9 end increment_v;
10 end silly_p;
11 /
Package body created.
Elapsed: 00:00:00.00
SQL> select silly_p.show_v from dual;
SHOW_V
----------
0
1 row selected.
Elapsed: 00:00:00.00
SQL> exec dbms_output.put_line(silly_p.v)
0
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> exec silly_p.increment_v
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.04
SQL> select silly_p.show_v from dual;
SHOW_V
----------
1
1 row selected.
Elapsed: 00:00:00.14
SQL> exec silly_p.increment_v
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
SQL> exec dbms_output.put_line(silly_p.v)
2
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
SQL>
I've answered a similar question recently (have a look here); basically, you need to store current value somewhere (a table might be a good choice) and create a function (or, in your case, a procedure) that returns the next number.
How to convert the function I wrote to a procedure? Use it as a wrapper.
Here's the whole example:
SQL> CREATE TABLE broj (redni_br NUMBER NOT NULL);
Table created.
SQL>
SQL> CREATE OR REPLACE FUNCTION f_get_broj
2 RETURN NUMBER
3 IS
4 PRAGMA AUTONOMOUS_TRANSACTION;
5 l_redni_br broj.redni_br%TYPE;
6 BEGIN
7 SELECT b.redni_br + 1
8 INTO l_redni_br
9 FROM broj b
10 FOR UPDATE OF b.redni_br;
11
12 UPDATE broj b
13 SET b.redni_br = l_redni_br;
14
15 COMMIT;
16 RETURN (l_redni_br);
17 EXCEPTION
18 WHEN NO_DATA_FOUND
19 THEN
20 LOCK TABLE broj IN EXCLUSIVE MODE;
21
22 INSERT INTO broj (redni_br)
23 VALUES (1);
24
25 COMMIT;
26 RETURN (1);
27 END f_get_broj;
28 /
Function created.
SQL>
SQL> CREATE PROCEDURE p_get_Broj
2 AS
3 BEGIN
4 DBMS_OUTPUT.put_line (f_get_broj);
5 END;
6 /
Procedure created.
SQL>
SQL> EXEC p_get_broj;
PL/SQL procedure successfully completed.
SQL> set serveroutput on
SQL> EXEC p_get_broj;
2
PL/SQL procedure successfully completed.
SQL> EXEC p_get_broj;
3
PL/SQL procedure successfully completed.
SQL> EXEC p_get_broj;
4
PL/SQL procedure successfully completed.
I believe you need a session a variable like mathguy's assumption above. You can try below code and understand how it works. Note that each DB session could have different value to the NUM_VAR variable in var_pkg package depending on how many times the procedure below was executed for each session.
CREATE OR REPLACE PACKAGE var_pkg
IS
num_var NUMBER := 0;
PROCEDURE set_num_var(p_number NUMBER);
FUNCTION get_num_var RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY var_pkg
IS
PROCEDURE set_num_var(p_number NUMBER)
IS
BEGIN
num_var := p_number;
END;
FUNCTION get_num_var RETURN NUMBER
IS
BEGIN
RETURN num_var;
END;
END;
/
CREATE PROCEDURE plus1
IS
v_num NUMBER;
BEGIN
v_num := var_pkg.get_num_var + 1;
var_pkg.set_num_var(v_num);
DBMS_OUTPUT.PUT_LINE(v_num);
END;
/
To run the procedure,
exec plus1;
or
BEGIN
plus1;
END;
/
And in case you want to know the current value of the variable, you can query it using below code,
SELECT var_pkg.get_num_var
FROM dual;

sql plus warning in procedure. procedure created with compilation error. (procedure with parameter)

hey i am trying to do a pl sql program with the help of procedure. i want to check if number given by user is even or odd using procedure but i am getting an warning : procedure created with compilation error .
create or replace procedure even( a in out number)
as n number :=&n;
begin
if(n,2)=0 then
dbms_output.put_line('even');
else
dbms_output.put_line('odd');
end if;
end;
/
It is meaningless to compile the procedure each time you get user input.You should rather be doing the following.
Compile the procedure without any substitution variables. The parameter should be just IN and not IN OUT unless you want to modify its value inside the procedure.
CREATE OR replace PROCEDURE Even(n IN NUMBER)
AS
BEGIN
IF MOD(n, 2) = 0 THEN
dbms_output.put_line('even');
ELSE
dbms_output.put_line('odd');
END IF;
END;
/
Then execute this compiled procedure as many times as you like by passing user input.
SQL> SET SERVEROUTPUT ON;
SQL> EXEC even ( &n );
Enter value for n: 5
odd
PL/SQL procedure successfully completed.
SQL> EXEC even ( &n );
Enter value for n: 4
even
PL/SQL procedure successfully completed.
Why don't you use you mod function:
if mod(a,2)=0 then
dbms_output.put_line('even');
else
dbms_output.put_line('odd');
end if;
I think you put "n" instead of "a"
Consider using a function instead; in my opinion, it is a better option for such a task than a procedure.
A natural choice would be a function that returns Boolean:
SQL> create or replace function f_is_even (par_n in number)
2 return boolean
3 is
4 begin
5 return mod(par_n, 2) = 0;
6 end;
7 /
Function created.
You'd then use it in some PL/SQL code as the following example (yes, it looks stupid because it appears that it does exactly what your procedure does, but note - this is just an example; in real life, you'd use it in smarter way):
SQL> begin
2 if f_is_even(6) then
3 dbms_output.put_Line('even');
4 else
5 dbms_output.put_Line('odd');
6 end if;
7 end;
8 /
even
PL/SQL procedure successfully completed.
Drawback of such a function is that you can't use it in SQL (but, as I said, PL/SQL):
SQL> select f_is_even(5) from dual;
select f_is_even(5) from dual
*
ERROR at line 1:
ORA-06552: PL/SQL: Statement ignored
ORA-06553: PLS-382: expression is of wrong type
A possible "workaround" is to create a procedure that doesn't return Boolean but, for example, number (0 for "false" and 1 for "true") or string (N for "false, no" and Y for "true, yes"). For example:
SQL> create or replace function f_is_even_01 (par_n in number)
2 -- returns 1 if number is even; returns 0 if number is odd
3 return number
4 is
5 begin
6 return case when mod(par_n, 2) = 0 then 1
7 else 0
8 end;
9 end;
10 /
Function created.
SQL> select f_is_even_01(5) r1,
2 f_is_even_01(6) r2
3 from dual;
R1 R2
---------- ----------
0 1
There's nothing wrong in using a procedure; I just thought that you might want to hear another opinion.

Get the name of the calling procedure or function in Oracle PL/SQL

Does anyone know whether it's possible for a PL/SQL procedure (an error-logging one in this case) to get the name of the function/procedure which called it?
Obviously I could pass the name in as a parameter, but it'd be nice to make a system call or something to get the info - it could just return null or something if it wasn't called from a procedure/function.
If there's no method for this that's fine - just curious if it's possible (searches yield nothing).
There is a package called OWA_UTIL (which is not installed by default in older versions of the database). This has a method WHO_CALLED_ME() which returns the OWNER, OBJECT_NAME, LINE_NO and CALLER_TYPE. Note that if the caller is a packaged procedure it will return the PACKAGE name not the procedure name. In this case there is no way of getting the procedure name; this is because the procedure name can be overloaded, so it's not necessarily very useful.
Find out more.
Since 10gR2 there is also the $$PLSQL_UNIT special function; this will also return the OBJECT NAME (i.e. package not packaged procedure).
I found this forum: http://www.orafaq.com/forum/t/60583/0/. It may be what you are looking.
Basically, you can use the Oracle supplied dbms_utility.format_call_stack:
scott#ORA92> CREATE TABLE error_tab
2 (who_am_i VARCHAR2(61),
3 who_called_me VARCHAR2(61),
4 call_stack CLOB)
5 /
Table created.
scott#ORA92>
scott#ORA92> CREATE OR REPLACE PROCEDURE d
2 AS
3 v_num NUMBER;
4 v_owner VARCHAR2(30);
5 v_name VARCHAR2(30);
6 v_line NUMBER;
7 v_caller_t VARCHAR2(100);
8 BEGIN
9 select to_number('a') into v_num from dual; -- cause error for testing
10 EXCEPTION
11 WHEN OTHERS THEN
12 who_called_me (v_owner, v_name, v_line, v_caller_t);
13 INSERT INTO error_tab
14 VALUES (who_am_i,
15 v_owner || '.' || v_name,
16 dbms_utility.format_call_stack);
17 END d;
18 /
Procedure created.
scott#ORA92> SHOW ERRORS
No errors.
scott#ORA92> CREATE OR REPLACE PROCEDURE c
2 AS
3 BEGIN
4 d;
5 END c;
6 /
Procedure created.
scott#ORA92> CREATE OR REPLACE PROCEDURE b
2 AS
3 BEGIN
4 c;
5 END b;
6 /
Procedure created.
scott#ORA92> CREATE OR REPLACE PROCEDURE a
2 AS
3 BEGIN
4 b;
5 END a;
6 /
Procedure created.
scott#ORA92> execute a
PL/SQL procedure successfully completed.
scott#ORA92> COLUMN who_am_i FORMAT A13
scott#ORA92> COLUMN who_called_me FORMAT A13
scott#ORA92> COLUMN call_stack FORMAT A45
scott#ORA92> SELECT * FROM error_tab
2 /
WHO_AM_I WHO_CALLED_ME CALL_STACK
------------- ------------- ---------------------------------------------
SCOTT.D SCOTT.C ----- PL/SQL Call Stack -----
object line object
handle number name
6623F488 1 anonymous block
66292138 13 procedure SCOTT.D
66299430 4 procedure SCOTT.C
6623D2F8 4 procedure SCOTT.B
6624F994 4 procedure SCOTT.A
66299984 1 anonymous block
scott#ORA92>
Basically, all you need to do is to define vars and pass them in a call to a utility method to fill them up with values:
create or replace procedure some_test_proc (p_some_int int)
is
owner_name VARCHAR2 (100);
caller_name VARCHAR2 (100);
line_number NUMBER;
caller_type VARCHAR2 (100);
begin
....
OWA_UTIL.WHO_CALLED_ME (owner_name,caller_name,line_number,caller_type);
-- now you can insert those values along with systimestamp into a log file
....
end;

How can we define output parameter size in stored procedure?

How can we define output parameter size in stored procedure?
You can't. Of course, you are in control of how much data you put into the OUT parameter in the stored procedure. If you want you can create a sized local variable to hold the data and then assign the value of that variable to the OUT parameter.
The calling program determines the size of the variable that receives the OUT parameter.
Here is a simple package which declares and uses a subtype:
SQL> create or replace package my_pkg as
2 subtype limited_string is varchar2(10);
3 procedure pad_string (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string);
6 end my_pkg;
7 /
Package created.
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 p_out_str := rpad(p_in_str, p_length, 'A');
9 end pad_string;
10 end my_pkg;
11 /
Package body created.
SQL>
However, if we call PAD_STRING() in such a way that the output string exceeds the subtype's precision it still completes successfully. Bother!
SQL> var out_str varchar2(128)
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
PL/SQL procedure successfully completed.
SQL>
SQL> select length(:out_str) from dual
2 /
LENGTH(:OUT_STR)
----------------
12
SQL>
This is annoying but it's the way PL/SQL works so we have to live with it.
The way to resolve the situaton is basically to apply DBC principles and validate our parameters. So, we can assert business rules against the inputs like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 begin
8 if length(p_in_str) + p_length > 10 then
9 raise_application_error(
10 -20000
11 , 'Returned string cannot be longer than 10 characters!');
12 end if;
13 p_out_str := rpad(p_in_str, p_length, 'A');
14 end pad_string;
15 end my_pkg;
16 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-20000: Returned string cannot be longer than 10 characters!
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
Or we can assert business rules against the output like this:
SQL> create or replace package body my_pkg as
2 procedure pad_string
3 (p_in_str varchar
4 , p_length number
5 , p_out_str out limited_string)
6 as
7 l_str limited_string;
8 begin
9 l_str := rpad(p_in_str, p_length, 'A');
10 p_out_str := l_str;
11 end pad_string;
12 end my_pkg;
13 /
Package body created.
SQL>
SQL> exec my_pkg.pad_string('PAD THIS!', 12, :out_str)
BEGIN my_pkg.pad_string('PAD THIS!', 12, :out_str); END;
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "APC.MY_PKG", line 9
ORA-06512: at line 1
SQL>
In most scenarios we should do both. This is the polite way to build interfaces, because it means other routines can call our procedures with the confidence that they will return the values they say they will.
You could use a subtype in a package header and type check that in the body...
CREATE OR REPLACE PACKAGE my_test
AS
SUBTYPE my_out IS VARCHAR2( 10 );
PROCEDURE do_something( pv_variable IN OUT my_out );
END;
/
CREATE OR REPLACE PACKAGE BODY my_test
AS
PROCEDURE do_something( pv_variable IN OUT my_out )
IS
lv_variable my_out;
BEGIN
-- Work on a local copy of the variable in question
lv_variable := 'abcdefghijklmnopqrstuvwxyz';
pv_variable := lv_variable;
END do_something;
END;
/
Then when you run this
DECLARE
lv_variable VARCHAR2(30);
BEGIN
my_test.do_something( lv_variable );
DBMS_OUTPUT.PUT_LINE( '['||lv_variable||']');
END;
/
You would get the error
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
Seems to go against the spirit of using an out parameter, but after Tony's comment this was the only thing I could think of to control data within the called code.

Resources