Tải bản đầy đủ
4 PLVdyn: A Code Layer over DBMS_SQL

4 PLVdyn: A Code Layer over DBMS_SQL

Tải bản đầy đủ

[Appendix A] Appendix: PL/SQL Exercises
NOTE: To dynamically execute DDL from within PL/Vision, the account in which PLVdyn
is installed must have the appropriate privilege granted to it explicitly. If you rely solely on
role−based privileges, you receive an ORA−01031 error: insufficient
privileges. For example, if you want to create tables from within PLVdyn, you need to
have CREATE TABLE privilege granted to the PLVdyn account. Generic DDL interface
First of all, PLVdyn offers a single, completely generic procedure to execute a DDL statement. Its header
PROCEDURE ddl (string_in IN VARCHAR2);

You pass the string to PLVdyn.ddl and it takes care of the details (the implementation of which is discussed
in the section called "Bundling Common Operations").
I can use the ddl procedure to perform any kind of DDL, as the following examples illustrate.
Create an index on the emp table.
PLVdyn.ddl ('CREATE INDEX empsal ON emp (sal)');

Create or replace a procedure called temp.

PLVdyn offers a number of other, more specialized programs to execute various kinds of DDL. These are
described in the following sections. Dropping and truncating objects with PLVdyn
PLVdyn offers two separate "cleanup" programs: drop_object and truncate. The drop_object
procedure provides a powerful, flexible interface for dropping one or many objects in your schema. The
header for drop_object is:
PROCEDURE drop_object
(type_in IN VARCHAR2,
name_in IN VARCHAR2,
schema_in IN VARCHAR2 := USER);

The truncate command truncates either tables or clusters and has the same interface as drop_object:
PROCEDURE truncate
(type_in IN VARCHAR2,
name_in IN VARCHAR2,
schema_in IN VARCHAR2 := USER);

The rest of this section describes the behavior and flexibility of drop_object. The same information
applies to truncate.
You provide the type of object to drop, the name of the object, and the schema, (if you do want to drop objects
in another schema). So instead of typing statements like this in SQL*Plus:
SQL> drop table emp;

19.4.1 DDL Operations


[Appendix A] Appendix: PL/SQL Exercises
you can now use PLVdyn and enter this instead:
SQL> exec PLVdyn.drop_object ('table', 'emp');

If I were an aggressive and desperate salesperson, I would try to convince you that my way (with PLVdyn) is
better than the "native" DROP TABLE statement. That is, however, totally foolish. This is a case where the
simple DDL statement is much more straightforward. So why did I bother writing the drop_object
procedure? Because when I wrote it, I didn't plan simply to match the capabilities of a single DDL statement.
Instead, I examined the way in which DBAs often need to drop and manipulate objects in a schema and I
discovered a way to leverage PL/SQL to provide added value when it came to dropping objects. Adding value with PL/SQL
In many of the accounts in which I have worked, an application abbreviation is prefixed onto the names of all
objects (tables, views, programs, synonyms, etc.) for the application. The inventory system would use the INV
abbreviation, for instance, to segregate by name all related objects. The main inventory table is named
INV_master, the line items table named INV_item, and the package to maintain the invoices named
A common action taken in such environments is to drop all objects for a particular application or to clean out
all of the stored procedures or tables or views. I may want to clear the inventory application from my test
schema so that I can move the next release over from development. Without dynamic SQL in PL/SQL, you
would have to use SQL to generate SQL, reading rows from the USER_OBJECTS table to create a series of
DROP statements, and then execute those statements in SQL*Plus.
With DBMS_SQL and the PLVdyn package, you no longer have to take such a convoluted path to get the job
done. Both the type and name arguments of the drop_object procedure can be wildcarded, and this gives
you a tremendous amount of flexibility. I can drop all the objects with the inventory prefix as follows:
SQL> exec PLVdyn.drop_object ('%', 'INV%');

I can also request that all tables in the SCOTT schema be dropped with this command (it will, of course, only
work if the owner of PLVdyn has the authority to drop objects in the SCOTT schema):
SQL> exec PLVdyn.drop_object ('table', '%', 'scott');

You can provide the same kinds of wildcarded arguments to truncate. If you specify "%" for the object
type, for instance, truncate automatically applies the truncate command only to objects of type TABLE or
CLUSTER. Implementing multiobject actions
The implementation of drop_object and truncate is interesting; both of these programs simply call a
private module called multiobj, which stands for "multiple objects." This procedure applies the specified
command to all the objects in the ALL_OBJECTS view that match the type and name provided.
The multiobj procedure itself is a combination of static and dynamic SQL, as shown below:
PROCEDURE multiobj
(action_in IN VARCHAR2,
type_in IN VARCHAR2,
name_in IN VARCHAR2,
schema_in IN VARCHAR2 := USER)
/* The static cursor retrieving all matching objects */
CURSOR obj_cur IS
SELECT object_name, object_type

19.4.1 DDL Operations


[Appendix A] Appendix: PL/SQL Exercises
FROM all_objects
WHERE object_name LIKE UPPER (name_in)
AND object_type LIKE UPPER (type_in)
AND (UPPER (action_in) != c_truncate OR
(UPPER (action_in) = c_truncate AND
object_type IN ('TABLE', 'CLUSTER')))
AND owner = UPPER (schema_in);
/* For each matching object ... */
FOR obj_rec IN obj_cur
/* Open and parse the drop statement. */
(action_in || ' ' ||
obj_rec.object_type || ' ' ||
UPPER (schema_in) || '.' ||

The first argument to multiobj is the action desired. The rest of the arguments specify one or more objects
upon which the action is to be applied.
The static cursor fetches all records from the ALL_OBJECTS data dictionary view that match the criteria. The
dynamic cursor is defined and executed inside the generic PLVdyn.ddl procedure.
Why, my readers may be asking, did I not use the PLVobj package to fetch from the ALL_OBJECTS view?
The whole point of that package was to allow me to avoid making direct references to that view; instead I
could rely on a programmatic interface and very high−level operators. Using PLVobj.loopexec, I could
theoretically implement multiobj with a single statement, in which I execute PLVdyn.ddl as a
dynamically constructed PL/SQL program.
Believe me, I really wanted to use PLVobj in this program. The problem I encountered is that PLVobj is not
sufficiently flexible. As you can see in the declaration section of multiobj, my cursor against
ALL_OBJECTS is somewhat specialized. I have very particular logic inserted that automatically filters out
objects that are not appropriate for TRUNCATE operations. There was no way for me to incorporate this logic
into PLVobj as it currently exists. I do plan, however, that a future version of PLVobj will utilize dynamic
SQL and then allow me to modify the WHERE clause (and even which view it works against:
In the meantime, I write my own, somewhat redundant cursor against ALL_OBJECTS and then construct the
appropriate DDL statement based on the incoming action and values from the row fetched from
ALL_OBJECTS. Generating sequence numbers
The nextseq function returns the nth next value from the specified Oracle sequence. Its header is as
FUNCTION nextseq
(seq_in IN VARCHAR2, increment_in IN INTEGER := 1)

The default value for the increment is 1, so by default you get the immediate next value from the sequence
with a call like this:
:emp.empno := PLVdyn.nextseq ('emp_seq');

19.4.1 DDL Operations


[Appendix A] Appendix: PL/SQL Exercises
You can also use nextseq to move your sequence forward by an arbitrary number of values. This is often
necessary when the sequence has somehow gotten out of sync with the data. To move the emp_seq sequence
ahead by 1000 values, simply execute this statement:
SQL> exec PLVdyn.nextseq.('emp_seq', 1000);

Why did I bother building nextseq ? The Oracle database offers a very powerful method for generating
unique sequential numbers: the sequence generator. This database object guarantees uniqueness of values and
comes in very handy when you need to get the next primary key value for INSERTs. One drawback of the
sequence generator is that you can only obtain the next value in the sequence by referencing the sequence
object from within a SQL statement.
The following SELECT statement, for example, is like the one often used within PRE−INSERT triggers in
Oracle Forms applications:
SELECT emp_seq.NEXTVAL INTO :emp.empno FROM dual;

In other words, you must make this artificial query from the dual table to obtain the sequence number to
then use inside the Oracle Forms application.
And suppose that you want to move the sequence forward by 1000 (this kind of requirement arises when, for
one reason or another, the sequence has gotten out of sync with the table's primary key). You would then have
to write and execute a FOR along these lines:
dummy INTEGER;
FOR val IN 1 .. 1000
SELECT emp_seq.NEXTVAL INTO dummy FROM dual;

I don't know about you, but the year is 1996 and I just don't think I should have to execute queries against
dual to get things done. That makes me feel like a dummy! I also believe that Oracle Corporation will come
to its senses eventually and allow you to obtain sequence numbers without going to the SQL layer. In the
meantime, however, PLVdyn offers a programmatic interface to any sequence so that you can at least hide the
fact that you are using dual to get your sequence number. You even get to hide the specific syntax of
sequence generation (the NEXTVAL keyword, for instance), which will make it easier for anyone from
novices to developers to utilize this technology. What about the overhead?
Of course, since you are executing dynamic SQL, it takes more time to generate the sequence with nextseq
compared with a static generation. My tests showed that, on average, the static generation approach (using a
direct call to the sequence NEXTVAL through a dummy SQL query) took .55 seconds for 100 increments of
the sequence. The dynamic approach using PLVdyn.nextseq, on the other hand, required 1.54 seconds for
100 increments of the sequence. There are two ways of looking at these results:
Cup half empty: The dynamic sequence generation is three times slower than static! How awful!
Cup half full: Dynamic generation of a single sequence value took only .0154 seconds on average.
Unless I am working in a high−transaction, subsecond kind of environment, this is a totally acceptable
level of performance.
19.4.1 DDL Operations


[Appendix A] Appendix: PL/SQL Exercises
Is your cup half empty or half full? It depends on your specific application situation. As a general rule, you
should carefully evaluate your use of dynamic SQL to make sure that you can afford the overhead. When your
application can absorb the extra CPU cycles (and your users can tolerate the difference in response time), a
program like PLVdyn.nextseq offers many advantages. Compiling source code with PLVdyn
When you CREATE OR REPLACE a PL/SQL program unit in SQL*Plus, you are executing a DDL
command. You can, consequently, issue that same command using DBMS_SQL −− the difference is that you
can construct, create, and compile the PL/SQL program dynamically. PLVdyn offers two versions of the
compile procedure precisely to allow you to take this action. The headers of the compile programs are as
(stg_in IN VARCHAR2,
show_err_in IN VARCHAR2 := PLV.noshow);
(table_in IN PLVtab.vc2000_table,
lines_in IN INTEGER,
show_err_in IN VARCHAR2 := PLV.noshow);

The second version of compile assumes that the program definition is in a PL/SQL table in which each row
starting from 1 and going to lines_in rows contains a sequential line of code. This procedure simply
concatenates all of the lines together and passes them to the first version of compile.
The string version of cor takes two arguments: stg_in, a string of up to 32,767 characters that contains the
definition of the program, and show_err_in, which indicates whether you want to call PLVvu.err after
compilation to check for compile errors. The program definition should start with the program unit type
REPLACE to the string; compile does this automatically. You also should not include a final / after the
END; statement. This syntax is necessary only when executing the CREATE OR REPLACE directly in
SQL*Plus. You can, on the other hand, include newline characters in your string.
Here is an example of using PLVdyn.compile to create a very simple procedure with an error. I also
request that PLVdyn show me the compile errors.
SQL> exec PLVdyn.compile('procedure temp is begin nul; end;',PLV.show);
PL/Vision Error Listing for PROCEDURE TEMP
Line# Source
1 procedure temp is begin nul; end;
PLS−00313: 'NUL' not declared in this scope

How would you use PLVdyn.compile? You might try building yourself a Windows−based frontend for
PL/SQL development. For example, find a really good programming editor that has a macro language and the
ability to execute dynamic link libraries (DLLs). Create a DLL that accepts a string and executes
PLVdyn.compile. Tie this in to the editor and you are on your way to dramatically improving your
PL/SQL development environment.
NOTE: To dynamically compile and create stored PL/SQL code from within PL/Vision, the
account in which PLVdyn is installed must be directly granted CREATE PROCEDURE
authority. If you rely solely on role−based privileges, you receive an ORA−01031 error:
insufficient privileges.

19.4.1 DDL Operations


[Appendix A] Appendix: PL/SQL Exercises
You might also use PLVdyn.compile to temporarily create program units for use in your application, and
then drop them at the end of your session. You could, for example, create a package dynamically that would
define some global data structures whose names are established dynamically. They could only be referenced
through dynamic PL/SQL as well, but PLVdyn does make this possible. Implementing the compile procedure
The implementation of the string−based compile procedure is a good example of how I am able to leverage
existing elements of PL/Vision to easily extend the functionality of my library. The body of compile is
shown below:
(stg_in IN VARCHAR2,
show_err_in IN VARCHAR2 := PLV.noshow)
v_name1 PLV.plsql_identifier%TYPE;
v_name2 PLV.plsql_identifier%TYPE;
ddl ('CREATE OR REPLACE ' || stg_in);
IF show_err_in = PLV.show
v_name1 := PLVprs.nth_atomic (stg_in, 1, PLVprs.c_word);
v_name2 := PLVprs.nth_atomic (stg_in, 2, PLVprs.c_word);
IF UPPER(v_name1||' '||v_name2) = 'PACKAGE BODY'
v_name1 := v_name1 || ' ' || v_name2;
v_name2 :=
PLVprs.nth_atomic (stg_in, 3, PLVprs.c_word);
PLVvu.err (v_name1 || ':' || v_name2);

Notice, first of all, that it relies on the ddl procedure to execute the program. Then compile checks to see if
you wanted to view compile errors. If so, it uses the nth_atomic function of PLVprs to extract the first two
words from the program definition. If a PACKAGE BODY, it then retrieves the third word, which is the name
of the package. Finally, it calls PLVvu.err to display any errors.

19.3 The Dynamic
Packages of PL/Vision

19.5 DML Operations

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

19.4.1 DDL Operations


Chapter 19
PLVdyn and PLVfk:
Dynamic SQL and PL/SQL

19.5 DML Operations
Although it is possible to execute DML statements with static strings in PL/SQL, the dynamic nature of
DBMS_SQL does offer many new opportunities. The PLVdyn packages offers a programmatic interface for
several common DML operations to make it easier for you to take advantage of this technology.
PLVdyn offers three different DML operations: dml_insert_select, dml_delete, and
dml_update. There are many other possibilities for dynamic DML in PLVdyn; I encourage you to
experiment with your own extensions to this package.
NOTE: You cannot include bind variables for any of the DML programs in PLVdyn.
Generally, PLVdyn does not support bind variables. The consequence of this is that the
WHERE clauses may not contain any colons unless they are embedded in a literal string. The
PLVdyn1 package (described on the companion disk) does allow you to specify single bind
variable values for a variety of DML statements.

19.5.1 Performing an INSERT−SELECT FROM
You can execute an INSERT−SELECT FROM statement using the dml_insert_select. The header for
this procedure is:
PROCEDURE dml_insert_select
(table_in IN VARCHAR2, select_in IN VARCHAR2);

In the first argument you provide the name of the table that is going to receive the rows from the SELECT
statement. The second argument is the SELECT statement itself. Here is an example of using this procedure to
copy current invoices to a history table.
'SELECT * FROM inv_current' ||
' WHERE inv_date < ' || TO_CHAR (v_enddate));

19.5.2 Executing dynamic DELETEs
The dml_delete procedure allows you to delete from any table. The header is:
PROCEDURE dml_delete
(table_in IN VARCHAR2, where_in IN VARCHAR2 := NULL);

The first argument is the table from which rows are to be deleted. The second argument is an optional
WHERE clause, which restricts the number of rows deleted. As with static SQL, the simplest form of a call to
dml_delete (a NULL WHERE clause) results in the largest number of rows deleted.
The following call to dml_delete removes all employees in department 10 from the emp table:


[Appendix A] Appendix: PL/SQL Exercises
PLVdyn.dml_delete ('emp', 'deptno = 10');

Clearly, this syntax offers very little in the way of productivity enhancement over simply executing this SQL
SQL> delete from emp where deptno=10;

The big news about PLVdyn.dml_delete is that you can execute it from within a PL/SQL environment.

19.5.3 Executing dynamic UPDATEs
The dml_update procedure is overloaded to allow you to easily perform updates of single columns of date,
number or string datatypes. Here is the overloaded header for dml_update:
PROCEDURE dml_update
(table_in IN VARCHAR2,
column_in IN VARCHAR2,
where_in IN VARCHAR2 := NULL);

The only optional argument is the WHERE clause. If you do not supply a WHERE clause, the requested
UPDATE will be performed for all rows in the table.
The following examples demonstrate the different ways to use dml_update.
Set the salary of all employees to $100,000.
PLVdyn.dml_update ('emp', 'sal', 100000);

Set the last updated time stamp to SYSDATE for all invoices with creation dates in the last three
('invoice', 'update_ts', SYSDATE,
'create_ts >= ADD_MONTHS(SYSDATE,−3)');

19.5.4 Checking feedback on DML
Each of the PLVdyn DML programs calls the PLVdyn.execute procedure, which, in turn, calls the
DBMS_SQL.EXECUTE builtin to actually execute the SQL statement. DBMS_SQL.EXECUTE is a function
that returns an integer, telling you the number of rows affected by the SQL statement.
PLVdyn saves and hides this feedback value to make it easier to execute your SQL. You can, however, check
the result of your latest dynamic execution by calling the dml_result function. In the example following, I
delete rows from the emp table and then check to see how many rows were actually deleted.
PLVdyn.dml_delete ('emp');
IF PLVdyn.dml_result > 100
|| Too many deletes. There is some kind
|| of data problem. Issue a rollback.

19.5.3 Executing dynamic UPDATEs


[Appendix A] Appendix: PL/SQL Exercises

19.5.5 Executing Dynamic PL/SQL Code
PLVdyn makes it easy for you to execute dynamically constructed PL/SQL statements with the plsql
procedure. The header for this program is:
PROCEDURE plsql (string_in IN VARCHAR2);

You construct a PL/SQL statement or block of statements and then pass that string to PLVdyn.plsql. It
then executes that code. By using PLVdyn.plsql, you do not have to code the separate open, parse, and
execute steps that are required with dynamic PL/SQL. In addition, this procedure relieves you of the burden of
dealing with the following rules about dynamic PL/SQL:
The statement must end in a semicolon.
The statement must be a valid PL/SQL block. It must begin, in other words, with either the
DECLARE or BEGIN keywords, and end with the END; statement.
You can provide to PLVdyn.plsql a string that meets these requirements, or you can ignore those
requirements and plsql takes care of things for you. As a result, any of the following calls to
PLVdyn.plsql properly executes the calc_totals procedure:
SQL> exec PLVdyn.plsql ('calc_totals');
SQL> exec PLVdyn.plsql ('calc_totals;');
SQL> exec PLVdyn.plsql ('BEGIN calc_totals; END;');

The plsql procedure accomplishes this by stripping off any trailing semicolons and wrapping your string
inside a BEGIN−END block, regardless of whether or not you have already done so. Implementation of PLVdyn.plsql
The implementation of plsql, shown below, is a good example of how you can build additional smarts into
your software so that it is easier and more intuitive to use. It also demonstrates how I combine other, bundled
operations to construct higher−level programs (these bundled operations are explored in a later section).
PROCEDURE plsql (string_in IN VARCHAR2)
cur INTEGER :=
open_and_parse (plsql_block (string_in));
execute_and_close (cur);

The plsql_block function is designed specifically to return a valid PL/SQL block as follows:
FUNCTION plsql_block (string_in IN VARCHAR2) RETURN VARCHAR2
RETURN 'BEGIN ' || RTRIM (string_in, ';') || '; END;';

NOTE: The plsql procedure assumes that you do not have any bind variables. If a colon
appears in the PL/SQL code and it is not within single quotes (part, that is, of a literal string),
PLVdyn.plsql will not be able to execute your statement successfully.

19.5.5 Executing Dynamic PL/SQL Code


[Appendix A] Appendix: PL/SQL Exercises Scope of a dynamic PL/SQL block
Precisely what kind of PL/SQL code can you execute with DBMS_SQL? The answer is not as simple as it
might seem at first glance. The rule you must follow is this:
A dynamically−constructed PL/SQL block can only execute programs and reference data
structures which are defined in the specification of a package.
Consider, for example, the following simple script:
PLVdyn.plsql ('n := 5');

All I am doing is assigning a value of 5 to the local variable n. This string is executed within its own
BEGIN−END block, that would appear to be a nested block within the anonymous block named "dynamic"
with the label. Yet when I execute this script I receive the following error:
PLS−00302: component 'N' must be declared
ORA−06512: at "SYS.DBMS_SYS_SQL", line 239

The PL/SQL engine is unable to resolve the reference to the variable named n. I get the same error even if I
qualify the variable name with its block name:
/* Also causes a PLS−00302 error! */
PLVdyn.plsql ('dynamic.n := 5');

Yet if instead of modifying the value of n, I modify the PLV.plsql_identifier variable as shown
below, I am able to execute the dynamic assignment successfully.
PLVdyn.plsql ('PLV.plsql_identifier := ''5''');

What's the difference between these two pieces of data? The variable n is defined locally in the anonymous
PL/SQL block. The plsql_identifier variable is a public global defined in the PLV package. This
distinction makes all the difference with dynamic PL/SQL.
It turns out that a dynamically constructed and executed PL/SQL block is not treated as a nested block.
Instead, it is handled like a procedure or function called from within the current block. So any variables local
to the current or enclosing blocks are not recognized in the dynamic PL/SQL block. You can only make
references to globally defined programs and data structures. These PL/SQL elements include standalone
functions and procedures and any element defined in the specification of a package. That is why my reference
to the plsql_identifier variable of the PLV package passed without error. It is defined in the package
specification and is globally available.
19.5.5 Executing Dynamic PL/SQL Code


[Appendix A] Appendix: PL/SQL Exercises
Fortunately, the dynamic block is executed within the context of the calling block. If you have an exception
section within the calling block, it traps exceptions raised in the dynamic block. The overhead of dynamic PL/SQL
As soon as you move to dynamic processing, you can expect that there will be some overhead associated with
the extra work performed (construct the string, parse the string, and −− with dynamic PL/SQL −− compile
the anonymous block). Dynamic PL/SQL is really neat stuff −− but is it practical? What is the performance
penalty when executing, for example, the PLVdyn.plsql procedure?
I came up with an answer by using the PLVtmr package (see Chapter 14, PLVtmr: Analyzing Program
Performance). The script below first determines how much time it takes to do, well, nothing with a call to the
NULL statement. This provides a baseline for static code execution (and you can't get much more base than
that). I then execute the NULL statement dynamically, with a call to the PLVdyn.plsql procedure. The
single substitution parameter, &1, allows me to specify the number of iterations for the test.
FOR rep IN 1 .. &1
PLVtmr.show_elapsed ('static');
FOR rep IN 1 .. &1
PLVdyn.plsql ('NULL');
PLVtmr.show_elapsed ('dynamic');

I then executed this script in SQL*Plus several times to make sure my results were consistent:
SQL> @temp 100
static Elapsed: 0 seconds.
dynamic Elapsed: 1.38 seconds.
SQL> @temp 1000
static Elapsed: .11 seconds.
Dynamic Elapsed: 13.57 seconds.
SQL> set verify off
SQL> @temp 1000
static Elapsed: .16 seconds.
dynamic Elapsed: 13.41 seconds.

I conclude, therefore, that the overhead of constructing, compiling, and executing a dynamic block of PL/SQL
code is at least (and approximately) .0133 seconds. I say "at least" because if your PL/SQL block is bigger
(consisting of more than, say, a single statement or call to a stored procedure), your overhead increases. It
looks to me that for most situations the additional processing time required for dynamic PL/SQL should not
deter you from using this technique.

19.5.6 Bundled and Passthrough Elements
While PLVdyn does offer a number of useful, high−level operations to perform dynamic SQL, there is no way
that I can build enough of these kinds of programs to handle all dynamic SQL needs. I can, however, still add
value for a developer who has very unique requirements: I can bundle together common, often−repeated steps
into single lines of code. The developer still has to write his or her own full−fledged dynamic SQL program,
but can rely on these lower−level "prebuilts" (as opposed to "builtins") to improve productivity and code
19.5.5 Executing Dynamic PL/SQL Code