Coding on the Fly: Using the Execute Immediate
Procedure
Dr. Paul Dorsey & Yalim Gerger
Dulcian, Inc.
Overview
There are
a few new features in Oracle8 v. 8.1.5 that can greatly help developers create
systems more easily and efficiently. This article will discuss one of these –
the EXECUTE IMMEDIATE command and how it can be useful in building flexible
systems.
What does
EXECUTE IMMEDIATE do?
The
EXECUTE IMMEDIATE command enables you to build an anonymous PL/SQL block on the
fly and execute it in interpreted mode. This allows a developer to build a
block as a textual string and command that this string be executed at runtime.
For
example, if you want to execute a procedure called PROC_DO_IT, you would use
EXECUTE IMMEDIATE with the following syntax:
DECLARE
code_text varchar2(2000):=
'begin
proc_do_it;
end;'
BEGIN
EXECUTE IMMEDIATE (code_text);
END;
Of course,
instead of the code above, you could simply write a single line of code:
proc_do_it;
However,
by using the EXECUTE IMMEDIATE procedure, you can build the desired code on the
fly based on something queried from the database. At Dulcian, we build systems
based on very abstract structures with most of the business rules stored as
data in the database. We then generate potentially hundreds of PL/SQL
procedures that are continuously executed under particular circumstances. Using
EXECUTE IMMEDIATE greatly simplified our code generation procedures, creating
substantially less code that was simpler and much more readable.
Technical
Aspects of the EXECUTE IMMEDIATE command
Notice
that in the PROC_DO_IT procedure we used a “begin” and “end,” bracketing the
procedure and building an entire PL/SQL block. In this context, it is necessary
to do this since you cannot pass an isolated procedure call. If you are passing
a DDL or DML command such as UPDATE TABLE or CREATE AND REPLACE VIEW, you do
not need to bracket the procedure with “begin” and “end.” In those cases, the
command can be passed alone.
For DDL
commands, the appropriate grant must be given to the schema that the procedure
is in. Otherwise you will get the ORA-1031 “Insufficient privileges” error.
Advantages
There are
several advantages of using EXECUTE IMMEDIATE commands.
1. EXECUTE
IMMEDIATE commands do not usually significantly impact performance. However,
there are times that performance can be affected. We ran a 100-line program by
calling it directly. Then we used EXECUTE IMMEDIATE and passed the whole code
to it. The directly called procedure executed in 0.04 seconds. The EXECUTE
IMMEDIATE procedure executed in 0.396 seconds. Note that the performance hit
only occurs the first time that the routines are executed. When we tried it a
second time, they both executed in 0.04 seconds, which means that once the
PL/SQL block) in EXECUTE IMMEDIATE is compiled, it resides in the memory just
like any other PL/SQL block and calling it is just as fast as calling any other
PL/SQL block in the memory. Our experience is that the compilation time
required by EXECUTE IMMEDIATE is not a problem in our applications.
2. We were pleasantly surprised to find that the
error messages generated when performing an EXECUTE IMMEDIATE are of the same
quality as error messages produced by any other part of the program. Compiler
error messages are received during execution. This was helpful in finding bugs
in our code more easily.
Limitations
The
EXECUTE IMMEDIATE command does have the following limitations:
1. The
most significant limitation to the EXECUTE IMMEDIATE command is that it is not
exactly the same as an anonymous PL/SQL block built on the fly. The difference
is that variables outside of the EXECUTE IMMEDIATE program string are not
visible to the EXECUTE IMMEDIATE command. The only way to pass information to
an EXECUTE IMMEDIATE string is by concatenating it into the string when it is
created as in the following example, which would execute the command PROC_DEPT
for Department 10:
DECLARE
deptno NUMBER :=10;
code_text VARCHAR2(2000);
BEGIN
code_text :=
'begin
proc_dept('
||deptno||');';
EXECUTE IMMEDIATE (code_text);
END;
2. Getting
information out of an EXECUTE IMMEDIATE command is similarly problematic. You
cannot update any variable that was not declared within the context of the
EXECUTE IMMEDIATE command.
To make
information visible to code outside of the EXECUTE IMMEDIATE command, you have
to either write the information to some place in a database table, or place the
information into a package variable outside of the procedure when the command
is executed. You cannot update variables in the procedure that executes the
EXECUTE IMMEDIATE command.
3. You
cannot use EXECUTE IMMEDIATE within Oracle Developer v. 6.0. The command is not
recognized. If you want to use EXECUTE IMMEDIATE within a Forms application,
you must write a server-side procedure and call that procedure from within
Forms.
When Should You
Use EXECUTE IMMEDIATE?
Even if
you do not fully embrace abstract data structures and code generation, the
EXECUTE IMMEDIATE command is still useful. For example, DBAs can use it to
build a utility allowing them to delete all information from multiple tables
based on filter criteria.
Developers
might find EXECUTE IMMEDIATE handy for replacing large, cumbersome IF THEN
ELSIF statements that have dozens of similar ELSIF clauses.
The
EXECUTE IMMEDIATE command seems to be reasonably robust. We have used it to
execute a procedure, which itself successfully called an EXECUTE IMMEDIATE
command. However, thus far, we have not tried to build particularly large
routines using EXECUTE IMMEDIATE so we cannot comment on how well it would work
with large program units. It does work very well with small program units.
Examples
In one of
the systems we are building, we are generating procedures associated with a state
transition rule engine. Each state has a procedure that is generated in
association with it. We are taking documents that exist in a particular state
and, as a result of being in that state, a particular procedure should be
executed. Part of what this procedure does is to move the document along to the
next state in the workflow.
We wanted
to observe the state that the document is in, call the associated procedure and
move it along to its next state. We could do this with a large IF THEN ELSIF
statement using a separate condition for each state. Unfortunately, the
possible document states are very volatile. Either this procedure would require
constant maintenance or a code generator would be required to generate the
procedure every time the workflow changed. By using EXECUTE IMMEDIATE, we were
able to solve this problem easily as the following code illustrates:
PROCEDURE p_do_it IS
/*cursor to loop through all
active documents*/
CURSOR c_doc_id IS
SELECT d.doc_id, d.doc_state_id,
ds.z_doc_state_cd
FROM doc d, doc_state ds
WHERE d.activ_yn = 'Y'
AND d.doc_state_id =
ds.doc_state_id;
/*cursor to retrieve the current
state of the document*/
CURSOR c_info (cin_doc_id NUMBER) IS
SELECT ds.z_doc_state_cd, ds.doc_state_id
FROM doc d, doc_state ds
WHERE d.doc_state_id = ds.doc_state_id
AND d.doc_id = cin_doc_id;
ci c_info%ROWTYPE;
v_exec_tx VARCHAR2(2000);
v_z_doc_state_cd VARCHAR2(100) :=
'AUTO';
v_count_nr NUMBER;
BEGIN --------main-------------
--loop through active docs
FOR cdi IN c_doc_id LOOP
v_count_nr := 0;
---fetch doc state
OPEN c_info(cdi.doc_id);
FETCH c_info into ci;
CLOSE c_info;
/*only execute the procedure for
automatic states and only allow
a total of 10 states to be
executed for each doc*/
WHILE v_count_nr < 10 AND ci.z_doc_state_cd IN ('automatic_state','AUTO') LOOP
---build code string
v_exec_tx := 'begin p_auto_'||ci.doc_state_id||'('||cdi.doc_id||');
end;';
EXECUTE IMMEDIATE (v_exec_tx);
COMMIT;
OPEN c_info(cdi.doc_id);
FETCH c_info into ci;
CLOSE c_info;
v_count_nr := v_count_nr +1;
END LOOP; --state
COMMIT;
END LOOP; ---doc
END;
This
procedure walks through all active documents, one at a time, and passes them
from state to state until they reach a termination state. We added a loop
terminator so that no document could pass through more than ten states to
prevent infinite loops.
NOTE: This
procedure assumes the use of consistent naming conventions for procedures all
being called P_AUTO_[procedure ID for specific state].
For
additional information about EXECUTE IMMEDIATE, you can also see the whitepaper
“Oracle 8i PL/SQL: Faster, Easier,
and Better” by
Usha Sangam, Ashok Swaminathan, and
Dennis Fogg (Oracle Magazine, Sept/Oct 1999, pp. 79-82)
Conclusion
EXECUTE
IMMEDIATE can be very useful. Ever since we began using generic models and
tried to store business rules as data, we have wanted the capabilities that
EXECUTE IMMEDIATE supplies. From the authors’ perspective, the greatest benefit
of using EXECUTE IMMEDIATE is to make the coding of applications to support
generic data structures much easier. Now with Oracle8.1.5, we can realize the
time and effort savings that this new procedure provides.
About the
Authors
Dr. Paul Dorsey is the
founder and President of Dulcian, Inc. (www.dulcian.com) Dulcian specializes in Oracle
Client-Server and Web custom application development and data migration. Paul is co-author with Peter Koletzke of The Oracle Designer Handbook (now in its
second edition) and with Joseph R. Hudicka of Oracle8 Design Using UML Object Modeling, both from Oracle Press,
1999. Paul is an Associate Editor of SELECT Magazine. He is the President of the New York Oracle Users’ Group.
Paul and Peter shared the Pinnacle Publishing Technical Achievement
Award at ECO’95 for their work on a Forms template that became the basis for
this book. Paul has won best presentation awards at both ECO and IOUW
conferences.
Yalim
Gerger is a Developer with Dulcian, Inc. He graduated from Istanbul Technical
University in 1999. Yalim specializes in data modeling and Oracle development
using Designer and Developer.