| Please Note: Unless otherwise noted, all of the PL/SQL Pipeline's monthly tips are contributed by
Steven Feuerstein. |
Oracle PL/SQL Tips of the Month-
2000
| January |
How to Compare the Contents of Two Tables |
| February |
Maintaining One Version of Code for Both Definer and Invoker Rights Models |
| March |
Exception Handling and Rolling Back DML |
| April |
Tailoring Your Code to Answer the Question |
| May |
Safe Trapping of Failed DBMS_JOB Job |
| June |
Achieving Aligned Decimals |
| July |
Wildcard Characters and the LIKE Operator |
| August |
Using LIKE Instead of SUBSTR |
| September |
Identifying Valid UTL_FILE Directories |
| October |
Indexing Nulls |
| November |
Invoker Rights Method Leads to Lots of Parsing |
| December |
Encapsulate Dynamic SQL Parsing to Improve Error Detection and Clean Up |
1999 Tips
1998 Tips
1997 Tips
January's Tip of the Month
How to compare the contents of two tables
Have you ever wanted to compare the contents of two tables to make sure they are identical? A PL/SQL Pipeliner
suggested that you should run the following two queries:
SELECT * FROM table1
MINUS
SELECT * FROM table2;
SELECT * FROM table2
MINUS
SELECT * FROM table1;
The esteemed Solomon Yakobson pointed out that this solution will almost always work -- except for the scenario when
table1 and table2 are comprised of the same rows, but some of the rows occur n times in table1 yet m times in table2.
To handle this situation, Solomon suggests running the following two queries:
SELECT table1.*,count(*)
FROM table1
GROUP BY column1,column2,...
MINUS
SELECT table2.*,count(*)
FROM table2
GROUP BY column1,column2,...
and
SELECT table2.*,count(*)
FROM table2
GROUP BY column1,column2,...
MINUS
SELECT table1.*,count(*)
FROM table1
GROUP BY column1,column2,...
February's Tip of the Month
Update to February's Tip - February 15
Maintaining One Version of Code for Both Definer and Invoker Rights Models
Oracle8i introduces the invoker rights model,which allows code to resolve references to tables and views at run-time,
based on the invoker and not the definer privileges. (See my latest book, Oracle PL/SQL Programming Guide to Oracle
Features, for lots more details).
That is really great! One problem that you can run into, however, is needing to maintain a program for both Oracle8i
and prior releases, where this feature was not available. I have come up with a way to use SQL*Plus substitution
variables to achieve this effect. In the code you see below...
- I determine the Oracle version (works for 7.3 and above).
- Use the version to set the values of substitution parameters so that my AUTHID CURRENT_USER syntax (used for
requesting invoker rights) is either commented out (prior to Oracle8i) or activated.
SET FEEDBACK OFF
SET VERIFY ON
COLUMN col NOPRINT NEW_VALUE v_orcl_vers
SELECT SUBSTR(version,1,3) col
FROM product_component_version
WHERE UPPER(product) LIKE 'ORACLE7%'
OR UPPER(product) LIKE 'PERSONAL ORACLE%'
OR UPPER(product) LIKE 'ORACLE8%';
COLUMN col NOPRINT NEW_VALUE authidopen
SELECT DECODE (upper('&v_orcl_vers'),
'8.1', '/**/',
'/* Oracle 8.1 Only!') col
FROM dual;
COLUMN col NOPRINT NEW_VALUE authidclose
SELECT DECODE (upper('&v_orcl_vers'),
'8.1', '/**/',
'*/') col
FROM dual;
CREATE OR REPLACE PROCEDURE either_way
&authidopen AUTHID CURRENT_USER &authidclose
IS
v_count PLS_INTEGER;
BEGIN
SELECT COUNT(*) INTO v_count FROM emp;
DBMS_OUTPUT.PUT_LINE ('Emp count: ' || v_count);
END;
/
GRANT EXECUTE ON either_way TO PUBLIC;
SET SERVEROUTPUT ON
exec either_way;
CONNECT DEMO/DEMO
SET SERVEROUTPUT ON
DELETE FROM emp;
exec SCOTT.either_way;
ROLLBACK;
CONNECT SCOTT/TIGER
DEFINE authidopen = /*
DEFINE authidclose = */
CREATE OR REPLACE PROCEDURE either_way
&authidopen AUTHID CURRENT_USER &authidclose
IS
v_count PLS_INTEGER;
BEGIN
SELECT COUNT(*) INTO v_count FROM emp;
DBMS_OUTPUT.PUT_LINE ('emp count: ' || v_count);
END;
/
GRANT EXECUTE ON either_way TO PUBLIC;
SET SERVEROUTPUT ON
exec either_way;
CONNECT DEMO/DEMO
SET SERVEROUTPUT ON
DELETE FROM emp;
exec SCOTT.either_way;
ROLLBACK;
CONNECT SCOTT/TIGERSET FEEDBACK OFF
February Tip of the Month Add-On: February 15, 2000
We received this suggestion for a shorter version of our February Tip from PL/SQL user Steve
Cosner:
"I played with your Feb. tip,and have a little shorter version. You might like this -- all you need is one COLUMN
command and one SELECT:"
COLUMN authid NOPRINT NEW_VALUE authid
SELECT 'AUTHID CURRENT_USER' authid
FROM product_component_version
WHERE INSTR(UPPER(product),'ORACLE') > 0
AND SUBSTR(version,1,3) >= '8.1';
"Below is the output (with some extra output lines removed) from a script I ran on two servers, as you can see
from the output:"
SQL> CONNECT simrusr/******@ATEST
Connected.
SQL> UNDEFINE authid
SQL> COLUMN authid NOPRINT NEW_VALUE authid
SQL> SELECT 'AUTHID CURRENT_USER' authid
2 FROM product_component_version
3 WHERE INSTR(UPPER(product),'ORACLE') > 0
4 AND SUBSTR(version,1,3) >= '8.1';
SQL> DEFINE
DEFINE _O_VERSION = "Oracle8i Enterprise Edition Release 8.1.5.0.0
DEFINE AUTHID = "AUTHID CURRENT_USER" (CHAR)
SQL> CREATE OR REPLACE PROCEDURE P1 &AUTHID IS BEGIN NULL;END;
2 /
old 1: CREATE OR REPLACE PROCEDURE P1 &AUTHID IS BEGIN NULL;END;
new 1: CREATE OR REPLACE PROCEDURE P1 AUTHID CURRENT_USER IS BEGIN
NULL;END;
Procedure created.
SQL> SHOW ERRORS PROCEDURE P1
No errors.
SQL> DROP PROCEDURE P1;
Procedure dropped.
SQL> CONNECT AGTMS09/******@CASA
Connected.
SQL> UNDEFINE authid
SQL> COLUMN authid NOPRINT NEW_VALUE authid
SQL> SELECT 'AUTHID CURRENT_USER' authid
2 FROM product_component_version
3 WHERE INSTR(UPPER(product),'ORACLE') > 0
4 AND SUBSTR(version,1,3) >= '8.1';
no rows selected
SQL> DEFINE
DEFINE _O_VERSION="Oracle7 Server Release 7.3.2.2.0 - Production Release
DEFINE AUTHID = "" (CHAR)
SQL> CREATE OR REPLACE PROCEDURE P1 &AUTHID IS BEGIN NULL;END;
2 /
old 1: CREATE OR REPLACE PROCEDURE P1 &AUTHID IS BEGIN NULL;END;
new 1: CREATE OR REPLACE PROCEDURE P1 IS BEGIN NULL;END;
Procedure created.
SQL> SHOW ERRORS PROCEDURE P1
No errors.
SQL> DROP PROCEDURE P1;
Procedure dropped.
March's Tip of the Month
Exception Handling and Rolling Back DML
Have you ever written an application that uses a database table as an error log? You run your application. It hits an
error, writes information to the log, and then the exception goes unhandled. You see the error stack in SQL*Plus and
you immediately run a query against the log to see what went wrong...and the log is empty! You scratch your head and
wonder what happened to the log information. Well, be puzzled no longer -- after you read this tip, anyway.
DML stands for Data Manipulation Language. There are three DML statements in SQL: INSERT, UPDATE and DELETE.
There is often lots of confusion in the minds of PL/SQL developers about the interaction between exception handling
and DML. Specifically, the question arises: if I perform an INSERT in my procedure and then an exception is raised, is
my INSERT rolled back?
Here's the scoop:
The state of your transaction (pending, committed, rolled back) is not affected by the raising of an exception in a
block of PL/SQL code. From within PL/SQL, your changes are committed only when you issue a COMMIT, and your changes
are rolled back only when you issue a ROLLBACK statement.
Suppose, in other words, that you execute an UPDATE of 1,000 rows of data and then following that perform a SELECT
INTO that raises a NO_DATA_FOUND exception. You have an exception section that traps the error and displays a message
to the user. When you run your program, it will terminate due to the exception. Control will then pass to the outer
block (if there is one). But the 1,000 updated rows are still waiting to be committed or rolled back.
There is one important nuance to this general rule: if your exception propagates all the way out of the outermost
PL/SQL block (the exception, in other words, goes unhandled), then you will find that most host environments
(SQL*Plus, for example) will automatically ROLLBACK any uncommitted DML.
And that is why the error log is empty after running a test that results in an unhandled exception.
April's Tip of the Month
Answer the Question Asked!
Being a good listener is a crucial skill for human beings, whether it's a matter of being responsive to your
significant other or understanding what your users need you to build. If you don't listen to what's being asked of
you, the chance of you giving the right answer is small at best.
Consider the following requirement:
"If Steven has more than one favorite flavor of ice cream, give him a 20% raise so that he can afford to buy all
the cones he'll want this summer."
Here is one possible implementation of that task:
DECLARE
numflavs PLS_INTEGER;
BEGIN
SELECT COUNT(*) INTO numflavs
FROM fav_flavors
WHERE name = 'STEVEN'
AND item = 'ICE CREAM';
IF numflavs > 1
THEN
UPDATE compensation
SET salary = salary * 1.2
WHERE name = 'STEVEN';
END IF;
END;
But what is wrong with this solution? Hint: what of the following questions do I answer with my SELECT statement?
- "Does Steven have more than one favorite flavor of ice cream?"
- "How many favorite flavors of ice cream does Steven have?"
Clearly, it is (b) and that is not the question I was asked. And depending on the contents of my fav_flavors table,
that SELECT COUNT(*) could be doing lots of unnecessary work.
Here is a rewrite of my code that provides an answer to the question asked and is, as a rule, much more efficient:
DECLARE
atleastone BOOLEAN := FALSE;
CURSOR mmmm_cur IS
SELECT 1
FROM fav_flavors
WHERE name = 'STEVEN'
AND item = 'ICE CREAM';
mmmm_rec mmmm_cur%ROWTYPE;
BEGIN
OOPEN mmmm_cur;
FETCH mmmm_cur INTO mmmm_rec;
IF mmmm_cur%FOUND
THEN
FETCH mmmm_cur INTO mmmm_rec;
atleastone := mmmm_cur%FOUND;
END IF;
CLOSE mmmm_cur;
IF atleastone
THEN
UPDATE compensation
SET salary = salary * 1.2
WHERE name = 'STEVEN';
END IF;
END;
Sure, it's more code than the first implementation, but the performance gain might well be significant. And if it
seemed like this logic was needed in more than one program, I would even take it another step and move most of that
logic into a function:
CREATE OR REPLACE FUNCTION lotsa_favflavs (
Name_in IN VARCHAR2, item_in IN VARCHAR2)
RETURN BOOLEAN
IS
atleastone BOOLEAN := FALSE;
CURSOR mmmm_cur IS
SELECT 1
FROM fav_flavors
WHERE name = name_in
AND item = item_in;
mmmm_rec mmmm_cur%ROWTYPE;
BEGIN
OOPEN mmmm_cur;
FETCH mmmm_cur INTO mmmm_rec;
IF mmmm_cur%FOUND
THEN
FETCH mmmm_cur INTO mmmm_rec;
atleastone := mmmm_cur%FOUND;
END IF;
CLOSE mmmm_cur;
RETURN atleastone;
END;
/
Once this piece of reusable code is in place, my specific block of code is now only this:
BEGIN
IF lotsa_favflavs ('STEVEN', 'ICE CREAM')
THEN
UPDATE compensation
SET salary = salary * 1.2
WHERE name = 'STEVEN';
END IF;
END;
So listen closely and make sure that when you write code, you are answering the question (fulfilling the requirement)
asked, and not something else!
May's Tip of the Month
Safe Trapping of Failed DBMS_JOB Job
From the compute-intensive brain of John Beresniewicz, Savant Corporation via the PL/SQL Pipeline:
A while back, I gave my DBMS_JOB talk at the EOUG (European Oracle User Group) and cooked up this example of how to
safely trap the error message from a failing job (in which you might not have access to source code and must simply
work with the program call only.) Note the following aspects:
- Use an anonymous PL/SQL block for job definition.
- Rollback any pending transactions for failing job.
- Capture and write Oracle exception error message to table.
- Re-raise exception (optional) allowing job queue to apply its failure logic.
Here is the job submission call, where "failingproc" is the stub for the name of the program and any
parameters that are failing:
DBMS_JOB.SUBMIT (
job=>jobno
,what=>'DECLARE errcode NUMBER;'||
'BEGIN failingproc;' ||
'EXCEPTION WHEN OTHERS ||
'THEN errcode := SQLCODE; ROLLBACK;'||
' log_error(SQLERRM(errcode));'||
' COMMIT; RAISE; END;'
,next_date=> SYSDATE
,interval=>'SYSDATE+1');
The log_error procedure is elementary, but could easily be made much more sophisticated:
PROCEDURE log_error(errmsg_IN IN VARCHAR2)
IS
BEGIN
INSERT INTO error_log(errmsg)
VALUES (errmsg_IN);
END log_error;
I think this will prove a useful technique for those plagued by anonymous job failures not of their own design.
June's Tip of the Month
Achieving Aligned Decimals
Steve Cosner (stevec@csufresno.edu), a very devoted PL/SQL Pipeliner
offering some really great Oracle Forms utilities on his site, had this to say a few weeks ago: "You have to be
an HTML jockey to get things to line up on this WebBoard. And you have to be a character function jockey to get text
to line up using PL/SQL. Here's a SQL Plus script I put together to achieve aligned decimals. Have fun reading the
code!"
SET SERVEROUTPUT ON SIZE 20000 FORMAT WRAPPED
DECLARE
PROCEDURE prnt (n NUMBER)
IS
txt0 VARCHAR2 (20);
txt1 VARCHAR2 (20);
txt2 VARCHAR2 (20);
txt3 VARCHAR2 (20);
txt4 VARCHAR2 (20);
BEGIN
txt0 := RPAD (TO_CHAR (n), 8);
txt1 := TO_CHAR (n, '9999990.9999');
txt2 :=
RPAD (RTRIM (TO_CHAR (n, '99990.9999'), '0'), 12);
txt3 :=
RPAD (
RTRIM (RTRIM (TO_CHAR (n, '99990.9999'), '0'), '.'),
12
);
txt4 :=
REPLACE (
RPAD (RTRIM (TO_CHAR (n, '99990.9999'), '0'), 12),
'. ',
'.0'
);
DBMS_OUTPUT.put_line (
' ' || txt0 || txt1 || txt2 || txt3 || txt4
);
END prnt;
BEGIN
DBMS_OUTPUT.put_line (
' Input Txt1 Txt2 ' ||
' Txt3 Txt4 '
);
DBMS_OUTPUT.put_line (
' --------- -------- --------' ||
' -------- --------'
);
prnt (1.24);
prnt (2.356);
prnt (12.1248);
prnt (0);
prnt (.123);
prnt (456);
END;
And here's the output:
Input Txt1 Txt2 Txt3 Txt4
--------- -------- -------- -------- --------
1.24 1.2400 1.24 1.24 1.24
2.356 2.3560 2.356 2.356 2.356
12.1248 12.1248 12.1248 12.1248 12.1248
0 0.0000 0. 0 0.0
.123 0.1230 0.123 0.123 0.123
456 456.0000 456. 456 456.0
July's Tip of the Month
Wildcard Characters and the LIKE Operator
Let's explore a nuance of the LIKE operator...
expression LIKE 'pattern' [ESCAPE 'escape-character']
I bet just about every one of you is aware of the two wildcard characters used in Oracle's SQL for string comparisons:
_ Match to any single character in this position
% Match to any number of characters in this position
(As you can see, "*" is not a wildcard character in SQL.)
These wildcard characters come in handy when using the LIKE operator. If, for example, I want to display the names of
everyone who likes any variety of chocolate ice cream, I could write this query:
SELECT name
FROM general_preference
WHERE flavor LIKE 'CHOC%'
AND topic = 'ICE CREAM';
And if I need to identify all companies placing orders in the 3rd quarter of the year, that information
("3") stored (in a very questionable design decision) in the 16th character of the order number, I could
write this query:
SELECT company_id
FROM order
WHERE order_id LIKE RPAD ('_', '_', 15) || '3%';
Great stuff! OK, but what if our string contains wildcard characters? It is certainly common, for example, to place
underscores in the names of database objects (see "general_preference" above).
How do I answer the question "Show me all database objects that contain an underscore?" I'd like to write
the query as follows:
SELECT object_name
FROM user_objects
WHERE object_name LIKE '%_%';
Yet this query would always return the names of all my objects. To write this query, I need to tell SQL to stop using
the underscore character as a wildcard, which I can do with the ESCAPE clause:
SELECT object_name
FROM user_objects
WHERE object_name LIKE '%^_%' ESCAPE '^';
Thanks to Alan Slay (als@akc.org) and Solomon Yakobson (syakobson@erols.com) for the idea and contributions to the content!
August's Tip of the Month
Using LIKE Instead of SUBSTR
Do you LIKE to SUBSTR?
I need to implement the following requirement:
"If the first three characters of the pet type is CAT, then buy catnip. If the first three characters of the pet
type is DOG, then buy milkbones."
What's the best way to satisfy this requirement? Well, I know how to identify the first three characters of a
string...use SUBSTR. So my first inclination would be to code as follows:
v_first_three := SUBSTR (pet_type, 1, 3);
IF v_first_three = 'CAT'
THEN
buy_catnip;
ELSIF v_first_three = 'DOG'
THEN
buy_milkbones;
END IF;
And that certainly does the trick. But is it the only way? Unlikely...in very few situations do you just have a single
way to implement a requirement. In this case, I could also use the LIKE operator, as follows:
IF pet_type LIKE 'CAT%'
THEN
buy_catnip;
ELSIF pet_type LIKE 'DOG%'
THEN
buy_milkbones;
END IF;
It turns out that using the LIKE operator is more efficient than SUBSTR in this scenario (not by a whole lot, but
consistently faster).
My conclusion: when checking the contents of a beginning of a string, use LIKE instead of SUBSTR.
September's Tip of the Month
Identifying Valid UTL_FILE Directories
Compliments of Dan Clamage
The UTL_FILE package can read and write only with directories specified by the UTL_FILE_DIR initialization parameter.
Unlike many other parameters, you can actually have more than one UTL_FILE_DIR entry. How can a developer determine
whether or not a directory is supported by UTL_FILE? You can run the following query:
select value from v$parameter
where name='utl_file_dir';
You can even encapsulate this query inside simple functions such as:
CREATE OR REPLACE FUNCTION isvaliddir (dir_in IN VARCHAR2)
RETURN BOOLEAN
/* Formatted by PL/Formatter v3.1.2.1 on 2000/08/30 09:25 */
IS
v_value v$parameter.VALUE%TYPE;
BEGIN
SELECT VALUE
INTO v_value
FROM v$parameter
WHERE name = 'utl_file_dir'
AND ( UPPER (VALUE) LIKE
UPPER (dir_in)
OR VALUE = '*');
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN FALSE;
WHEN TOO_MANY_ROWS
THEN
DBMS_OUTPUT.put_line (
'Multiple directories fit criteria. Please narrow.'
);
RETURN TRUE;
END;
/
October's Tip of the Month
Indexing Nulls
Compliments of James Padfield (jpadfiel@cellops.com)
While null values are not part of a usual index, it is possible to index them using a function-based index, as
follows...
ALTER SESSION SET QUERY_REWRITE_ENABLED = TRUE;
CREATE INDEX index_name ON
table_name (DECODE (column_name, NULL, '#', NULL));
Using DECODE appears better that NVL for this purpose as the function converts all non-NULL values to NULL and so they
are not included in the resulting index, saving space (this can be demonstrated by analyzing the resulting index and
verifying that distinct_keys = 1).
By using a hint we can encourage the use of our new index and return null values without the need for a full table
scan.
SELECT /*+ INDEX(t,index_name) */
column_name
FROM table_name t
WHERE DECODE (column_name, NULL, '#', NULL) = '#';
November's Tip of the Month
Invoker Rights Method Leads to Lots of Parsing
A new feature in Oracle8i, called Invoker Rights Method, allows you to ask the PL/SQL engine to execute a stored
function or procedure under the authority of the "invoker" of the program. Prior to Oracle8i, stored code
always executed under the authority of the "definer" or owner of the program. To define a program with
invoker rights, you only need to add the AUTHID clause to the header of the program:
CREATE OR REPLACE PROCEDURE show_books
AUTHID CURRENT_USER
AS
BEGIN
FOR rec IN (SELECT * FROM book)
LOOP
DBMS_OUTPUT.PUT_LINE (rec.title);
END LOOP;
END;
For more information about invoker rights, check Oracle documentation, Steven Feuerstein's fourth book, Oracle PL/SQL
Programming: Guide to Oracle8i Features, or Knowledge Xpert PL/SQL.
There is one big problem, however, with invoker rights: every time you run the program, any and all SQL within the
program will be parsed -- again and again and again, even if the SQL statement doesn't change at all. This behavior
makes sense, since the whole point of invoker rights is that all SQL references are resolved according to the rights
of the invoker, and the invoker could change each time the program is run.
So if you decide to use AUTHID CURRENT_USER (and it is an absolute necessity when sharing programs that rely on
dynamic SQL, for example), watch out for scenarios in which that program will be run with a very high frequency.
December's Tip of the Month
Encapsulate Dynamic SQL Parsing to Improve Error Detection and Clean Up.
This month's tip is drawn from the unfinished manuscript of Steven Feuerstein's latest book-in-progress: Oracle
PL/SQL Best Practices. This book offers succinct, practical advice for improving the quality of your PL/SQL code. It
will be published by O'Reilly and Associates in the first half of 2001. If you would like to do a technical review of
some or all of this book, drop Steven a note at steven@stevenfeuerstein.com.
Dynamic SQL is tricky; you generally glom together different chunks of text to form what you hope is a valid SQL or
PL/SQL statement. Either through programmer error or user error, you can end up with a bad chunk of SQL, resulting in
a parse error. To identify and fix these errors, you should create your own parsing "engine" on top of
DBMS_SQL.PARSE and the NDS (native dynamic SQL, an Oracle8i feature) statements. This program will trap and display
error information, as well as clean up cursors.
Example
This technique is most crucial for DBMS_SQL. Don't ever call DBMS_SQL.PARSE directly in your program. Instead call
your own parse. Here is a very simple example:
CREATE OR REPLACE FUNCTION open_and_parse (
dynsql_in IN VARCHAR2,
dbms_mode_in IN INTEGER := NULL)
RETURN INTEGER
IS
dyncur INTEGER;
BEGIN
dyncur := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE (dyncur, dynsql_in,
NVL (dbms_mode_in, DBMS_SQL.NATIVE));
RETURN dyncur;
EXCEPTION
WHEN OTHERS
THEN
DBMS_SQL.CLOSE_CURSOR (dyncur);
DBMS_OUTPUT.PUT_LINE (SQLERRM);
DBMS_OUTPUT.PUT_LINE (dynsql_in);
RETURN NULL;
END;
/
Here is the Native Dynamic SQL equivalent:
CREATE OR REPLACE PROCEDURE exec_immed (
dynsql_in IN VARCHAR2)
AUTHID CURRENT_USER
IS
BEGIN
EXECUTE IMMEDIATE dynsql_in;
EXCEPTION
WHEN OTHERS
THEN
pl (SQLERRM)
pl (dynsql_in);
END;
/
Benefits
- You will be able to identify and fix errors in your program, or train your users to use the interface to your
dynamic SQL, much more effectively.
- You will not inadvertently leave DBMS_SQL cursors open (and impossible to close in your session) .
Challenges
- With DBMS_SQL (prior to Oracle8i), any SQL statement passed to open_and_parse will be parsed under the
privileges of the owner of open_and_parse. You should, therefore, install this program in every schema that wants
to use it. Or, if you are running Oracle8i and still using DBMS_SQL, use the AUTHID CURRENT_USER clause to ensure
that the program runs under the invoker's authority.
- With NDS, you cannot separate the parse and execute phases – it is all done by EXECUTE IMMEDIATE. That makes
it hard to write a truly generic program to handle any SQL string (you have to account for the USING and INTO
clauses). The general principle still applies: trap, handle and display dynamic SQL errors!
For questions or comments on this site: webmaster@quest-pipelines.com
All content Copyright ©
Quest Software, Inc. All rights reserved.
|