Tải bản đầy đủ (.pdf) (5 trang)

Oracle Built−in Packages- P21 pptx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (80.67 KB, 5 trang )

BEGIN
v_start := DBMS_UTILITY.GET_TIME;
cursor_id := DBMS_SQL.OPEN_CURSOR;
/*
|| Parse first, outside of loop
*/
DBMS_SQL.PARSE (cursor_id, 'SELECT ', DBMS_SQL.native);
FOR i IN 1 &1
LOOP
/*
|| bind and excecute each loop iteration using host vars
*/
DBMS_SQL.BIND_VARIABLE(cursor_id,'i',i);
exec_stat := DBMS_SQL.EXECUTE(cursor_id);
END LOOP;
DBMS_SQL.CLOSE_CURSOR(cursor_id);
DBMS_OUTPUT.PUT_LINE
('Approach 3: ' || TO_CHAR (DBMS_UTILITY.GET_TIME − v_start));
END;
/
And here are the results from running this script twice:
SQL> @effdsql.tst 10000
Approach 1: 860
Approach 2: 981
Approach 3: 479
2.4.6 Problem−Solving Dynamic SQL Errors
Sometimes the hardest aspect to building and executing dynamic SQL programs is getting the string of
dynamic SQL right. You might be combining a list of columns in a query with a list of tables and then a
WHERE clause that changes with each execution. You have to concatenate that stuff together, getting the
commas right, and the ANDs and ORs right, and so on. What happens if you get it wrong? Well, let's take the
nightmare scenario and work it through.


I am building the most complicated PL/SQL application ever. It uses dynamic SQL left and right, but that's
OK. I am a pro at dynamic SQL. I can, in a flash, type OPEN_CURSOR, PARSE, DEFINE_COLUMN, and
other commands. I know the right sequence, I know how to detect when there are no more rows to fetch, and I
blast through the development phase. I also rely on some standard exception−handling programs I have built
that display an error message when encountered.
Then the time comes to test my application. I build a test script that runs through a lot of my code; I place it in
a file named testall.sql. With trembling fingers I start my test:
SQL> @testall
And, to my severe disappointment, here is what shows up on my screen:
ORA−00942: table or view does not exist
ORA−00904: invalid column name
ORA−00921: unexpected end of SQL command
ORA−00936: missing expression
ORA−00911: invalid character
Ugh. A whole bunch of error messages, clearly showing that various SQL statements have been constructed
improperly and are causing parse errors −− but which SQL statements are the troublemakers? That is a very
difficult question to answer. One way to get at the answer is to place all calls to the PARSE procedure inside
an exception section and then display the string causing the error.
[Appendix A] What's on the Companion Disk?
2.4.6 Problem−Solving Dynamic SQL Errors 91
CREATE OR REPLACE PROCEDURE whatever
IS
v_sql VARCHAR2(32767);
BEGIN
construct_sql (v_sql);
DBMS_SQL.PARSE (cur, v_sql, DBMS_SQL.NATIVE);
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('Error in ' || v_sql);

END;
/
This certainly would have helped explain those earlier error messages. The problem with this approach is that
I would need to build this exception section every time I call PARSE. I also might be raising exceptions from
lines of code other than those containing the call to PARSE. How could I distinguish between the errors and
the information I should display? Furthermore, I might discover after writing the previous code ten or twenty
times that I need more information, such as the error code. I would then have to go back to all those
occurrences and enhance them. This is a very tedious, high−maintenance, and generally nonproductive way of
doing things.
A different and better approach is to provide your own substitute for PARSE that encapsulates, or hides away,
all of these details. You don't have to add exception sections in each call to this substitute, because it would
come with its own exception section. And if you decide you want to do things differently, you just change this
one program. Doesn't that sound so much better?
Let's go through the steps involved in creating a layer over PARSE that enhance its error−detection
capabilities. First, we will build the interface to the underlying DBMS_SQL call. That is easy enough:
/* Filename on companion disk: dynsql.spp */*
/* Final version of package */
CREATE OR REPLACE PACKAGE dynsql
IS
PROCEDURE parse
(cur IN INTEGER,
sqlstr IN VARCHAR2,
dbmsmode IN INTEGER := NULL);
END;
/
Why did I bother to put this single procedure inside a package? I always start with packages, because sooner
or later I want to add more related functionality, or I need to take advantage of package features, like
persistent data. In this case, I could foresee providing an overloaded parse function, which opens and returns a
cursor. I also expect to be defining some package data pertaining to error information, which would require a
package.

Notice that the parse procedure looks just like the DBMS_SQL version, except that the database mode has a
default value of NULL (which will translate into DBMS_SQL.NATIVE). This way (a) you do not have to
bother with providing a mode, and (b) the default value is not a packaged constant, which could cause
problems for calling this program from within Oracle Developer Release 1.
It would be a good idea to compare using DBMS_SQL with dynsql before we even try to implement this
package; that will be a validation of the design of the interface. So instead of this,
DECLARE
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.PARSE (cur, 'CREATE INDEX ', DBMS_SQL.NATIVE);
[Appendix A] What's on the Companion Disk?
2.4.6 Problem−Solving Dynamic SQL Errors 92
I could use dynsql.parse as follows:
DECLARE
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
BEGIN
dynsql.parse (cur, 'CREATE INDEX ');
I get to write a little bit less code, but that isn't really the main objective. I just want to make sure that I can do
whatever I can do with DBMS_SQL (with parse, anyway) through dynsql. Now let's build the package body
and add some value:
CREATE OR REPLACE PACKAGE BODY dynsql
IS
PROCEDURE parse
(cur IN INTEGER, sqlstr IN VARCHAR2, dbmsmode IN INTEGER := NULL)
IS
BEGIN
DBMS_SQL.PARSE (cur, sqlstr, NVL (dbmsmode, DBMS_SQL.NATIVE));
EXCEPTION
WHEN OTHERS

THEN
DBMS_OUTPUT.PUT_LINE ('Error in ' || sqlstr);
END;
END;
/
With this program installed, I can replace all calls to PARSE with dynsql.parse and then see precisely which
dynamic SQL statements are causing me problems. As I mentioned earlier, though, I really want to get more
information. Suppose, for example, that I needed to see the error number (as surely I would), as well as the
position in the SQL statement in which the error was detected. No problem! I just go to the package body and
add a couple lines of code:
CREATE OR REPLACE PACKAGE BODY dynsql
IS
PROCEDURE parse
(cur IN INTEGER, sqlstr IN VARCHAR2, dbmsmode IN INTEGER := NULL)
IS
BEGIN
DBMS_SQL.PARSE (cur, sqlstr, NVL (dbmsmode, DBMS_SQL.NATIVE));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('Parse error: ' || TO_CHAR (SQLCODE) ||
' at position ' || TO_CHAR (DBMS_SQL.LAST_ERROR_POSITION));
DBMS_OUTPUT.PUT_LINE ('SQL string: ' || sqlstr);
END;
END;
/
This should put me in good stead, except for one problem: what if my SQL string is more than 243 bytes in
length? The PUT_LINE procedure will raise a VALUE_ERROR if the string passed to it exceeds 255 bytes in
length. What an annoyance! But since I have had the foresight to hide all my calls to PARSE away in this
single program, I can even address this difficulty. PL/Vision Lite[1] offers a display_wrap procedure in the

PLVprs package. So I can avoid any VALUE_ERROR exceptions as follows:
[1] This software comes with my book Advanced Oracle PL/SQL Programming with
Packages (O'Reilly & Associates, 1996). You can also download it from
.
CREATE OR REPLACE PACKAGE BODY dynsql
IS
PROCEDURE parse
[Appendix A] What's on the Companion Disk?
2.4.6 Problem−Solving Dynamic SQL Errors 93
(cur IN INTEGER, sqlstr IN VARCHAR2, dbmsmode IN INTEGER := NULL)
IS
BEGIN
DBMS_SQL.PARSE (cur, sqlstr, NVL (dbmsmode, DBMS_SQL.NATIVE));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('Parse error: ' || TO_CHAR (SQLCODE) ||
' at position ' || TO_CHAR (DBMS_SQL.LAST_ERROR_POSITION));
PLVprs.display_wrap ('SQL string: ' || sqlstr);
END;
END;
/
See how easy it is to upgrade your programs and fix shortcomings once you have encapsulated your repetitive
actions behind a programmatic interface?
2.4.7 Executing DDL in PL/SQL
DBMS_SQL allows you to execute almost any DDL statements from within PL/SQL. Here are some
considerations to keep in mind:

You should explicitly execute and then close your DDL cursors. Currently, Oracle will automatically
execute DDL statements when they are parsed with a call to PARSE. Oracle Corporation warns users

of DBMS_SQL that this behavior might not be supported in the future.

You cannot establish a new connection to Oracle through PL/SQL. You cannot, in other words, issue
a CONNECT command from within PL/SQL; you will get an "ORA−00900: invalid SQL statement"
error. From this, one can deduce that CONNECT is not a SQL statement. It is, rather, a SQL*Plus
command.

You must have the necessary privileges to execute that DDL statement granted explicitly to the
account owning the program in which the DDL is being run. Remember that roles are disabled during
PL/SQL compilation and execution. If you want to create a table using dynamic SQL, you must have
CREATE TABLE or CREATE ANY TABLE privileges granted directly to your schema.

Your dynamic DDL execution can result in your program hanging. When I call a procedure in a
package, that package is locked until execution of that program ends. If another program attempts to
obtain a conflicting lock (this might occur if you try to drop that package using dynamic DDL), that
program will lock waiting for the other program to complete execution.
2.4.8 Executing Dynamic PL/SQL
Dynamic PL/SQL is an awful lot of fun. Just think: you can construct your PL/SQL block "on the fly" and
then execute it from within another PL/SQL program. Here are some factors to keep in mind as you delve into
this relatively esoteric aspect of PL/SQL development:

The string you execute dynamically must start with a DECLARE or BEGIN statement and terminate
with "END." It must, in other words, be a valid anonymous block.

[Appendix A] What's on the Companion Disk?
2.4.7 Executing DDL in PL/SQL 94
The string must end with a semicolon, unlike DDL and DML statements, which cannot end with a
semicolon.

The dynamic PL/SQL block executes outside the scope of the block in which the EXECUTE function

is called, but that calling block's exception section will trap exceptions raised by the dynamic PL/SQL
execution.

As a direct consequence of the previous rule, you can only reference globally available data structures
and program elements from within the dynamic PL/SQL block.
Let's explore those last two restrictions so as to avoid any confusion. First of all, I will build a little utility to
execute dynamic PL/SQL.
/* Filename on companion disk: dynplsql.sp */*
CREATE OR REPLACE PROCEDURE dyn_plsql (blk IN VARCHAR2)
IS
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.PARSE (cur,
'BEGIN ' || RTRIM (blk, ';') || '; END;',
DBMS_SQL.NATIVE);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_SQL.CLOSE_CURSOR (cur);
END;
/
This one program encapsulates many of the rules mentioned previously for PL/SQL execution. It guarantees
that whatever I pass in is executed as a valid PL/SQL block by enclosing the string within a BEGIN−END
pairing. For instance, I can execute the calc_totals procedure dynamically as simply as this:
SQL> exec dyn_plsql ('calc_totals');
Now let's use this program to examine what kind of data structures you can reference within a dynamic
PL/SQL block. In the following anonymous block, I want to use DBMS_SQL to assign a value of 5 to the
local variable num:
<<dynamic>>
DECLARE
num NUMBER;

BEGIN
dyn_plsql ('num := 5');
END;
/
This string is executed within its own BEGIN−END block, which 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 'NUM' 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 num. I get the same error even if I
qualify the variable name with its block name.
<<dynamic>>
DECLARE
[Appendix A] What's on the Companion Disk?
2.4.7 Executing DDL in PL/SQL 95

×