You are on page 1of 140

All non-technical views expressed are those of Steven Feuerstein and do

not necessarily (and not likely!) reflect those of Oracle Corporation.

21st Century PL/SQL

Breaking out of
Oracle7, Oracle8 and Oracle8i
programming ruts

Steven Feuerstein
steven@stevenfeuerstein.co
Copyright 2000-2005 Steven Feuerstein - Page 1 m
New stuff is good stuff.

 Major Oracle10g PL/SQL compiler upgrades
– Optimizing compiler, compile-time warnings
 Collections
– String-based indexes, multi-level collections, high level set
operations for nested tables, table functions, and more.
 Advanced topics in Dynamic SQL
– Dynamic PL/SQL, method 4 dynamic SQL, when and how to
use DBMS_SQL.
 Handy new built-in package functionality
– Schedule jobs, send email, recompile code
– Miscellaneous wonderful capabilities in DBMS_OUTPUT and
DBMS_UTILITY.
Copyright 2000-2005 Steven Feuerstein - Page 2
"All" about Steven Feuerstein
www.stevenfeuerstein.com

 Bachelor's degree in mathematics (1980), with three
computer 101 classes to my name (a self-taught
programmer).
 Five years with Oracle Corporation (1987 - 1992), with too
much time spent helping salespeople sell. Life is too
short...
 Author/co-author of nine texts on PL/SQL, most notably
Oracle PL/SQL Programming
 Senior Technology Advisor for Quest Software
 I live in Chicago with one wife (Veva), two sons (Chris and
Eli), and three cats (Sister, Moshe and Mica).
Copyright 2000-2005 Steven Feuerstein - Page 3
Ten Years of Writing on
the Oracle PL/SQL Language

Copyright 2000-2005 Steven Feuerstein - Page 4
Software used (or recommended)

 You can download all my training materials and
demonstration scripts from:
– http://oracleplsqlprogramming.com/resources.html
 Toad and/or SQL Navigator: make sure you've got a top-
notch IDE, otherwise you are wasting lots of time.
 Ounit and utPLSQL, software for unit testing of PL/SQL code
at www.ounit.com.
 MasterMind and Set: have fun while keeping
your brain tuned up.
 Qnxo, active mentoring software: a repository of
reusable and templated code, www.qnxo.com.
plsql_ides.txt
Copyright 2000-2005 Steven Feuerstein - Page 5
Qnxo is..."Quality iN, eXcellence Out"

 A searchable, customizable repository for
reusable code and templates.
– Starter set: "PL/SQL by Feuerstein"
– You can create your own toolboxes and libraries.
 A flexible code generator that helps you avoid
writing tedious, repetitive code.
 An error manager for PL/SQL-based
applications.
www.qnxo.com
Copyright 2000-2005 Steven Feuerstein - Page 6
Qnxo is....

 NOT an integrated development environment, aka
IDE, aka editor, for PL/SQL programming.
– It complements Toad, SQL Navigator, PL/SQL Developer,
etc.
 NOT needed in order to benefit from this class.
– Qnxo contains a repository of examples.
– The Qnxo backend reflects my latest (and, I believe, best)
thinking on how to build high quality PL/SQL applications.
 NOT free.
– There is a 30-day trial version available at www.qnxo.com.
One student will win a one year subscription to Qnxo.

Copyright 2000-2005 Steven Feuerstein - Page 7
Major Oracle10g PL/SQL compiler upgrades

 Optimizing compiler
– Recompile in 10g and experience 100%
improvement in performance (results may vary).
 Compile-time warnings
– Now the PL/SQL compiler tells you more than
simply compilation errors.

Copyright 2000-2005 Steven Feuerstein - Page 8
Oracle10g Wow! An optimizing compiler!

 Yes, the PL/SQL compiler now has the ability to
automatically optimize your code.
– Possible rearrangements to the code itself (under the covers).
 You can choose the level of optimization through the
plsql_optimize_level setting:
– 2 Most aggressive, maximum possible code transformations,
biggest impact on compile time. [default]
– 1 Smaller scale change, less impact on compile times
– 0 Pre-10g compilation without optimization

ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 1;

10g_optimize_cfl.sql
Copyright 2000-2005 Steven Feuerstein - Page 9
Learn about the PL/SQL optimizer

http://www.oracle.com/technology/tech/pl_sql/htdocs/new_in_10gr1.htm

 PL/SQL Just Got Faster
– Explains the workings of the PL/SQL compiler and runtime system and
shows how major improvements on this scale are indeed possible.
 PL/SQL Performance Measurement Harness
– Describes a performance experiment whose conclusion is the large
factors quoted above. We’ve provided a downloadable kit to enable you to
repeat the experiment yourself.
 Freedom, Order, and PL/SQL Optimization
– Intended for professional PL/SQL programmers, explores the use and
behavior of the new compiler.
 PL/SQL Performance — Debunking the Myths
– Re-examines some old notions about PL/SQL performance.
Copyright 2000-2005 Steven Feuerstein - Page 10
Optimizing compiler details

 Oracle retains optimizer settings on a
module-by-module basis.
– When you recompile a particular module with
non-default settings, the settings will "stick,"
allowing you to recompile later using REUSE
SETTINGS. For example:
ALTER PROCEDURE bigproc COMPILE PLSQL_OPTIMIZE_LEVEL = 1;

 and then:
ALTER PROCEDURE bigproc COMPILE REUSE SETTINGS;

Copyright 2000-2005 Steven Feuerstein - Page 11
Oracle10g Wow! Compile-time warnings!

 You can now enable compiler warnings, helping
you avoid nuisance issues in your code.
– Generally, these are not severe errors, but potential
problems with code structure or performance.
 To use compiler warnings, you must turn them
on in your session.
[ENABLE | DISABLE | ERROR]:[ALL|SEVERE|INFORMATIONAL|PERFORMANCE|warning_number]

REM To enable all warnings in your session execute:
ALTER SESSION SET plsql_warnings = 'enable:all‘;

REM If you want to enable warning message number 06002 and all warnings in
REM the performance category, and treat warning 5005 as a "hard" compile error:
ALTER SESSION SET plsql_warnings =
'enable:06002', 'enable:performance', 'ERROR:05005';

Copyright 2000-2005 Steven Feuerstein - Page 12
Compiler time warnings - example

 Check for “unreachable end” code….
SQL> CREATE OR REPLACE PROCEDURE unreachable_code IS
2 x NUMBER := 10;
3 BEGIN
4 IF x = 10 THEN
5 x := 20;
6 ELSE
7 x := 100; -- unreachable code
8 END IF;
9 END unreachable_code;
10 /
 
SP2-0804: Procedure created with compilation warnings
 
SQL> show err
Errors for PROCEDURE UNREACHABLE_CODE:
 
LINE/COL ERROR plw*.sql
-------- -------------------------------------
7/7 PLW-06002: Unreachable code

Copyright 2000-2005 Steven Feuerstein - Page 13
Useful data dictionary views

 ALL_PLSQL_OBJECT_SETTINGS
– New to Oracle10g, this view provides information about
the characteristics of a PL/SQL object that can be
modified through the ALTER-SET DDL command, such
as the optimization level, debug settings and more.

 ALL_PROCEDURES
– Introduced in Oracle9i, this view lists all functions and
procedures (stand-alone or packaged), along with
associated properties, including whether or not a function
is pipelined, parallel enabled, or aggregate.

Copyright 2000-2005 Steven Feuerstein - Page 14
Using the object settings view

 Show all the program units that are not fully
leveraging code optimization:
SELECT owner, name
FROM all_plsql_object_settings
WHERE plsql_optimize_level IN (1,0);

 Show all objects which have had one or more
compile-time warnings disabled:
SELECT owner, NAME, plsql_warnings
FROM all_plsql_object_settings
WHERE plsql_warnings LIKE '%DISABLE%'
AND owner NOT IN ('SYS', 'SYSTEM');

Copyright 2000-2005 Steven Feuerstein - Page 15
Using the ALL_PROCEDURES view

 The following query will show the AUTHID status
(DEFINER or CURRENT_USER) for all programs in
the specified package.
– In general, this view is handy because it does list each of
the individual program units in a package specification.
– Previously, this information could only be deduced from
ALL_ARGUMENTS.
SELECT AUTHID
, p.object_name program_name
, procedure_name subprogram_name
FROM all_procedures p, all_objects o
WHERE p.owner = o.owner
AND p.object_name = o.object_name
AND p.object_name LIKE '&1'
ORDER BY AUTHID, procedure_name;
program_start_end.ddl
package_analyzer.*
Copyright 2000-2005 Steven Feuerstein - Page 16
PL/SQL Collections

 Ever wonder why PL/SQL doesn't have
good, old-fashioned arrays?
 It's a good question, and one that Oracle
seems to answer as follows:

Who needs arrays,
when you have collections?

Copyright 2000-2005 Steven Feuerstein - Page 17
What we will cover on collections

 Brief review of basic functionality
 Indexing collections by strings
 Working with collections of collections
 Bulk processing with FORALL and BULK
COLLECT
 Table functions and pipelined functions
 MULTISET operators for nested tables

Copyright 2000-2005 Steven Feuerstein - Page 18
What is a collection?
1 2 3 4 22 23
abc def sf q ... rrr swq

 A collection is an "ordered group of elements,
all of the same type." (PL/SQL User Guide and
Reference)
– That's a very general definition; lists, sets, arrays and similar
data structures are all types of collections.
– Each element of a collection may be addressed by a unique
subscript, usually an integer but in some cases also a string.
– Collections are single-dimensional, but you can create
collections of collections to emulate multi-dimensional
structures.

Copyright 2000-2005 Steven Feuerstein - Page 19
Why use collections?

 Emulate bi-directional cursors, which are not yet
supported within PL/SQL.
 Bypass mutating table restrictions in DB triggers.
 Avoid many scenarios that produce "Snapshot too
old" and "Rollback segment too small" errors.
Using BULK COLLECT and FORALL....
 Parallelize execution of PL/SQL functions inside SQL
statements. With table functions....
 Dramatically improve multi-row querying, inserting,
updating and deleting the contents of tables.
Combined with BULK COLLECT and FORALL....
 Cache data in program memory for faster access.
The difference between PGA and SGA....
Copyright 2000-2005 Steven Feuerstein - Page 20
Refresher: PL/SQL in Shared Memory

System Global Area (SGA) of RDBMS
Instance
Shared Pool Library cache
Shared SQL
Select * Update emp
Reserved Pool
Pre-parsed from emp Set sal=...

Large Pool calc_totals show_emps upd_salaries

emp_rec emp%rowtype; emp_rec emp%rowtype;
Session 1 tot_tab tottabtype; tot_tab tottabtype;

Session 1 memory Session 2 memory Session 2
(PGA/UGA) (PGA/UGA)
mysess.pkg
Copyright 2000-2005 Steven Feuerstein - Page 21 Sess2.sql
Three Types of Collections

 Associative arrays (aka index-by tables)
– Similar to hash tables in other languages, allows you to
access elements via arbitrary subscript values.
 Nested tables
– Can be defined in PL/SQL and SQL. Use to store large
amounts of persistent data in the column of a table.
– Required for some features, such as table functions
 Varrays (aka variable size arrays)
– Can be defined in PL/SQL and SQL; useful for defining
small lists in columns of relational tables.

Copyright 2000-2005 Steven Feuerstein - Page 22
About Associative Arrays

 Unbounded, practically speaking.
– Valid row numbers range from -2,147,483,647 to
2,147,483,647.
– This range allows you to employ the row number as an
intelligent key, such as the primary key or unique index
value, because AAs also are:
 Index values can be integers or strings (Oracle9i R2
and above).
 Sparse
– Data does not have to be stored in consecutive rows, as is
required in traditional 3GL arrays and VARRAYs.
 Try to read a row that doesn't exist, and Oracle raises
NO_DATA_FOUND.
Copyright 2000-2005 Steven Feuerstein - Page 23 assoc_array_example.sql
About Nested Tables

 No pre-defined limit on a nested table.
– Valid row numbers range from 1 to
2,147,483,647.
 Is always dense initially, but can become
sparse after deletes.
 Can be defined as a schema level type and
used as a relational table column type.
 Part of object model, requiring initialization.

Copyright 2000-2005 Steven Feuerstein - Page 24 nested_table_example.sql
About Varrays

 Has a maximum size, associated with its type.
– Can adjust the size at runtime in Oracle10g R2.
 Is always dense; you can only remove
elements from the end of a varray.
 Can be defined as a schema level type and
used as a relational table column type.
 Part of object model, requiring initialization.

Copyright 2000-2005 Steven Feuerstein - Page 25 varray_example.sql
Apply PL/SQL Collections

 We will take a look at the following
applications of PL/SQL collections:
– Mutating table trigger error resolution
– Caching data in the PGA with collections
– Dealing with rollback segment too small and
snapshot too old errors
 Then we will explore advanced features of
collections.

Copyright 2000-2005 Steven Feuerstein - Page 26
Rollback segment too small,
Snapshot too old? Perhaps collections can help.

 Rollback segment too small...
– Cause: so many uncommitted changes, the
rollback segment can't handle it all.
– Solution: incremental commits. You can do this
with normal DML but also with FORALL DML.
 Snapshot too old...
– Cause: a cursor is held open too long and Oracle
can no longer maintain the snapshot information.
– Solution: open-close cursor, or use BULK
COLLECT to retrieve information more rapidly.
forall_incr_commit.sql
Copyright 2000-2005 Steven Feuerstein - Page 27
Dealing with Mutating Table errors
 Database triggers can be associated with both the
DML statement as a whole and individual rows
affected by that statement.
UPDATE emp SET sal = 1000
Statement level
UPDATE row 1

Row level
UPDATE row N

 Row level triggers cannot query from or change
the contents of the table to which it is attached; mutating.sql
it is "mutating".
 But statement level triggers do not have this
restriction. Note: in Oracle8i, you
 So what are you supposed to do when a row- can use autonomous
transactions to relax
level operation needs to "touch" that table? restrictions associated
with queries.
Copyright 2000-2005 Steven Feuerstein - Page 28
A Solution Based on
Associative Arrays Tables
 Since you cannot perform the processing desired in
the row-level trigger, you need to defer the action until
you get to the statement level.
 If you are going to defer the work, you have to
remember what you needed to do.
– An associative array is an ideal repository for this reminder
list.
Writes to list
1st row trigger fires
Work List
(collection)
Writes to list
Nth row trigger fires

Process data
in the list.
mutating_trigger.pkg
ranking.pkg Statement Trigger
Copyright 2000-2005 Steven Feuerstein - Page 29
Data Caching with PL/SQL Tables

Data retrieved
First access Pass Data from cache Data returned
to Cache to application

Database Application
Function
Not in cache; PGA
Request data Application
from database Requests Data

Subsequent accesses Data retrieved Data returned
from cache to application

Data found in
cache. Database Application
Database is not needed.
Function
PGA
Application
Requests Data
emplu.pkg
emplu.tst
Copyright 2000-2005 Steven Feuerstein - Page 30
New indexing capabilities
Oracle9i Release 2
for associative arrays

 Prior to Oracle9iR2, you could only index by
BINARY_INTEGER.
 You can now define the index on your associative
array to be:
– Any sub-type derived from BINARY_INTEGER
– VARCHAR2(n), where n is between 1 and 32767
– %TYPE against a database column that is consistent with
the above rules
– A SUBTYPE against any of the above.
 This means that you can now index on string
values! (and concatenated indexes and...)

Copyright 2000-2005 Steven Feuerstein - Page 31
Examples of New
Oracle9i Release 2
TYPE Variants
 All of the following are now valid TYPE declarations in
Oracle9i Release 2
– You cannot use %TYPE against an INTEGER column,
because INTEGER is not a subtype of BINARY_INTEGER.

DECLARE
TYPE array_t1 IS TABLE OF NUMBER
INDEX BY BINARY_INTEGER;
TYPE array_t2 IS TABLE OF NUMBER
INDEX BY PLS_INTEGER;
TYPE array_t3 IS TABLE OF NUMBER
INDEX BY POSITIVE;
TYPE array_t4 IS TABLE OF NUMBER
INDEX BY NATURAL;
TYPE array_t5 IS TABLE OF NUMBER
INDEX BY VARCHAR2(64);
TYPE array_t6 IS TABLE OF NUMBER
INDEX BY VARCHAR2(32767);
TYPE array_t7 IS TABLE OF
INDEX BY NUMBER
employee.last_name%TYPE;
TYPE array_t8 IS TABLE OF NUMBER INDEX BY
types_pkg.subtype_t;

Copyright 2000-2005 Steven Feuerstein - Page 32
Working with VARCHAR2-Indexed
Oracle9i Release 2
Collections
DECLARE
TYPE population_type IS TABLE OF NUMBER INDEX BY VARCHAR2(64);

country_population population_type;
continent_population population_type;

howmany NUMBER;
BEGIN
country_population('Greenland') := 100000;
country_population('Iceland') := 750000;
assoc_array*.sql
assoc_array_perf.tst
howmany := country_population('Greenland');

continent_population('Australia') := 30000000;
END;

 Specifying a row via a string takes some getting
used to, but if offers some very powerful advantages.
Copyright 2000-2005 Steven Feuerstein - Page 33
Oracle9i Release 2
Rapid Access to Data
Via String Keys
 One of the most powerful applications of this
features is to construct very fast pathways to static
data from within PL/SQL programs.
– If you are repeatedly querying the same data from the
database, why not cache it in your PGA inside
collections?
 Emulate the various indexing mechanisms (primary
key, unique indexes) with collections.

Generate a caching package: Comparison of performance
Demonstration package:
genaa.sql of different approaches:
assoc_array5.sql
genaa.tst vocab*.*

Copyright 2000-2005 Steven Feuerstein - Page 34
Oracle9i Multi-level Collections

 Oracle9i allows you to create collections of
collections, or collections of records that
contain collections, or...
 Applies to all three types of collections.
 Two scenarios to be aware of:
– Named collection columns
– Anonymous collection columns

Copyright 2000-2005 Steven Feuerstein - Page 35
Collections with Named,
Oracle9i
Multi-level Collections

 When a collection is based on a record or
object that in turn contains a collection, that
collection has a name.
CREATE TYPE vet_visit_t IS OBJECT (
visit_date DATE,
reason VARCHAR2 (100));
/
CREATE TYPE vet_visits_t IS TABLE OF vet_visit_t
/
CREATE TYPE pet_t IS OBJECT ( Collection nested inside
tag_no INTEGER, object type
NAME VARCHAR2 (60),
petcare vet_visits_t,
MEMBER FUNCTION set_tag_no (new_tag_no IN INTEGER)
RETURN pet_t);
/
Copyright 2000-2005 Steven Feuerstein - Page 36 multilevel_collections.sql
Continued...
Collections with Named,
Oracle9i
Multi-level Collections, continued

DECLARE
TYPE bunch_of_pets_t IS TABLE OF pet_t INDEX BY BINARY_INTEGER;
my_pets bunch_of_pets_t;
BEGIN
my_pets (1) := Outer collection
pet_t (
100, 'Mercury',
vet_visits_t (
vet_visit_t ( Inner collection
'01-Jan-2001', 'Clip wings'),
vet_visit_t (
'01-Apr-2002', 'Check cholesterol')
)
);
DBMS_OUTPUT.put_line (my_pets (1).petcare (2).reason);
END;

Copyright 2000-2005 Steven Feuerstein - Page 37
Oracle9i Anonymous Collection Columns

 If your nested collections do not rely on "intermediate"
records or objects, you simply string together index
subscripts.
– To demonstrate this syntax, let's take a look at how to
emulate a three-dimensional array using nested collections.
 First, we cannot directly reference or populate an
individual cell, as one would do in a 3GL.
– Instead we have to build an interface between the underlying
arrays and the user of the "three dimensional array."

Can't do this... Have to do something like this instead...
BEGIN BEGIN
gps_info (1, 45, 605) := gps_info (605) (45) (1) :=
l_value; l_value;
Copyright 2000-2005 Steven Feuerstein - Page 38
Oracle9i Multi-dimensional array emulation
CREATE OR REPLACE PACKAGE multdim
IS
TYPE dim1_t IS TABLE OF VARCHAR2 (32767)
INDEX BY BINARY_INTEGER;
 
Three levels of TYPE dim2_t IS TABLE OF dim1_t INDEX BY BINARY_INTEGER;
collections  
TYPE dim3_t IS TABLE OF dim2_t INDEX BY BINARY_INTEGER;
 
PROCEDURE setcell (
array_in IN OUT dim3_t,
dim1_in PLS_INTEGER,
dim2_in PLS_INTEGER,
dim3_in PLS_INTEGER,
Set a cell value value_in IN VARCHAR2
);
 
FUNCTION getcell (
array_in IN dim3_t,
dim1_in PLS_INTEGER,
dim2_in PLS_INTEGER,
dim3_in PLS_INTEGER
Get a cell value
) RETURN VARCHAR2; multdim.*
END multdim; multdim2.*
gen_multcoll.sp
Copyright 2000-2005 Steven Feuerstein - Page 39
Oracle9i Multi-dimensional array emulation

CREATE OR REPLACE PACKAGE BODY multdim
IS
PROCEDURE setcell (
array_in IN OUT dim3_t,
dim1_in PLS_INTEGER,
dim2_in PLS_INTEGER,
dim3_in PLS_INTEGER,
value_in IN VARCHAR2
) IS
BEGIN
array_in(dim3_in )(dim2_in )(dim1_in) := value_in;
END;
 
FUNCTION getcell (
array_in IN dim3_t,
As close as you dim1_in PLS_INTEGER,
can get... dim2_in PLS_INTEGER,
dim3_in PLS_INTEGER)
RETURN VARCHAR2
IS
BEGIN
RETURN array_in(dim3_in )(dim2_in )(dim1_in);
END;
Copyright 2000-2005 Steven Feuerstein - Page 40
Applying multi-level and string-based indexes

 Careful -- and creative! -- application of this
functionality can greatly simplify the code you need to
write to handle complex requirements.
 Let's step through an application of this capability to a
programming challenge.

Codecheck
OverloadCheck: package
overloadings
A QA package
naming_conventions
for PL/SQL
bad_datatypes

Copyright 2000-2005 Steven Feuerstein - Page 41
The problem of
ambiguous package overloadings
 Oddly and sadly, it is possible to compile
overloadings which are not usable.
– You see an obvious example below, but there are many
more subtle circumstances, usually involving defaulted
parameters.
 So I will build a program to identify such ambiguous
overloadings. But how can I do this?
PACKAGE salespkg
IS
BEGIN

?
PROCEDURE calc_total (
salespkg.calc_total ('ABC');
dept_in IN VARCHAR2);
END;
PROCEDURE calc_total (
/
dept_in IN CHAR);
END salespkg;

Copyright 2000-2005 Steven Feuerstein - Page 42 ambig_overloading.sql
ALL_ARGUMENTS to the rescue!

 Parsing is too complicated for me, but the
ALL_ARGUMENTS data dictionary view contains
information about all the arguments of all the
procedures and functions to which I have access.
That sounds pretty good!
 As usual, Oracle offers us a whole lot of pleasure,
mixed with a little bit of pain.
– The organization of data in ALL_ARGUMENTS is a bit
bizarre, plus it is incomplete, necessitating the use also of
DBMS_DESCRIBE.DESCRIBE_COLUMNS.

all_arguments.tst
all_arguments.sql
allargs.*
Copyright 2000-2005 Steven Feuerstein - Page 43
First Inclination:
Same Old, Same Old
 All right then, I will grab all the information from
ALL_ARGUMENTS and dump it into a collection
based on that view! Very easy...
CREATE OR REPLACE PROCEDURE get_all_arguments (
package_in IN VARCHAR2)
IS
TYPE all_arguments_tt IS TABLE OF all_arguments%ROWTYPE
Emulate the
INDEX BY BINARY_INTEGER;
l_arguments all_arguments_tt;
view.
BEGIN
FOR rec IN (
SELECT * FROM all_arguments
WHERE owner = USER AND package_name = package_in)
LOOP
l_arguments (SQL%ROWCOUNT) := rec; Load it up!
END LOOP;
END;

Copyright 2000-2005 Steven Feuerstein - Page 44
Then what? Write lots of code to
interpret the contents...

 Which programs are overloaded? Where
does one overloading end and another start?
l_last_program all_arguments.object_name%TYPE;
l_is_new_program BOOLEAN := FALSE;
l_last_overload PLS_INTEGER := -1; IF l_arguments (indx).overload
BEGIN != l_last_overload
FOR indx IN l_arguments.FIRST .. OR l_last_overload = -1
l_arguments.LAST THEN
LOOP IF l_is_new_program
IF l_arguments (indx).object_name != THEN
l_last_program do_first_overloading_stuff;
OR l_last_program IS NULL ELSE
THEN do_new_overloading_stuff;
l_last_program := END IF;
l_arguments (indx).object_name; END IF;
l_is_new_program := TRUE; END LOOP;
do_new_program_stuff; END;
END IF;
...
Copyright 2000-2005 Steven Feuerstein - Page 45
Discovery: there is a natural hierarchy
to ALL_ARGUMENTS data!
Program name

RUN_TEST
Overloading
SHOW_RESULTS
Overloading 1
RESET_FLAGS
Overloading 2 Breakout
Argument
 Each program has zero or Breakout 1
Argument 1
more overloadings, each
overloading has N Argument 2
Breakout 1
arguments, and each Argument 3

argument can have Argument 4
Breakout 2

multiple "breakouts" (my Argument 5
Breakout 3

term - applies to non-
scalar parameters, such as
records or object types).
Copyright 2000-2005 Steven Feuerstein - Page 46
What if I reflect this hierarchy in
a collection of collections?

 Have to build from the bottom up:
1. Set of rows from TYPE breakouts_t IS TABLE OF all_arguments%ROWTYPE
ALL_ARGUMENTS INDEX BY BINARY_INTEGER;

2. All the "breakout" info TYPE arguments_t IS TABLE OF breakouts_t
for a single argument INDEX BY BINARY_INTEGER;

3. All the argument info TYPE overloadings_t IS TABLE OF arguments_t
for a single overloading INDEX BY BINARY_INTEGER;

TYPE programs_t IS TABLE OF overloadings_t
4. All the overloadings for INDEX BY all_arguments.object_name%type;
a distinct program name

String-based index
Copyright 2000-2005 Steven Feuerstein - Page 47
Then I can populate it very easily

 Assigning a single record to the "lowest" level also
defines each of the upper levels.
 Notice the automatic "SELECT DISTINCT" on program
name that results!
FOR rec IN (SELECT * FROM all_arguments)
LOOP I can still do the
l_arguments (NVL (l_arguments.LAST, 0) + 1) typical sequential
:= rec; load.
 
l_programs
(rec.object_name)
But I will now also
(NVL (rec.overload, 0))
add the multi-level
(rec.position)
load in single
(rec.data_level) := rec;
assignment
END LOOP;
show_all_arguments.sp
Copyright 2000-2005 Steven Feuerstein - Page 48 show_all_arguments.tst
And then I can "query" the contents
with a minimum of code
Is the TOP_SALES
l_programs ('TOP_SALES').COUNT > 1
program overloaded?

Is the 2nd overloading
of TOP_SALES a l_programs ('TOP_SALES') (2).EXISTS (0)
function?

What is the datatype
of the RETURN clause l_programs ('TOP_SALES') (2)(0)(0).datatype
of the 2nd overloading
of TOP_SALES?

And, of course, I know the beginning and end points of
each program, overloading, and argument. I just use the
FIRST and LAST methods on those collections!

Copyright 2000-2005 Steven Feuerstein - Page 49
Encapsulate these complex structures!

 As you can see, you can easily and rapidly
arrive at completely unreadable and un-
maintainable code.
 What' s a developer to do?
– Hide complexity -- and all data structures -- behind
small modules.
– Work with and through tunctions to retrieve
contents and procedures to set contents.

cc_smartargs.pkb:
cc_smartargs.next_overloading
cc_smartargs.add_new_parameter
Copyright 2000-2005 Steven Feuerstein - Page 50
Nested Tables unveil their
Oracle10g
MULTISET-edness

 Oracle10g introduces high-level set operations on
nested tables (only).
– Nested tables are “multisets,” meaning that theoretically
there is no order to their elements. This makes set
operations of critical importance for manipulating nested
tables. .
 You can now…
– Check for equality and inequality
– Obtain UNION, INTERSECT and MINUS of two NTs
– Determine if there are duplicates, remove them, etc.

Copyright 2000-2005 Steven Feuerstein - Page 51
Oracle10g Check for equality and inequality

 Just use the basic operators….
DECLARE
TYPE clientele IS TABLE OF VARCHAR2 (64);
group1 clientele := clientele ('Customer 1', 'Customer 2');
group2 clientele := clientele ('Customer 1', 'Customer 3');
group3 clientele := clientele ('Customer 3', 'Customer 1');
BEGIN
IF group1 = group2 THEN
DBMS_OUTPUT.put_line ('Group 1 = Group 2');
ELSE
DBMS_OUTPUT.put_line ('Group 1 != Group 2');
END IF;

IF group2 != group3 THEN
DBMS_OUTPUT.put_line ('Group 2 != Group 3');
ELSE
DBMS_OUTPUT.put_line ('Group 2 = Group 3');
END IF;
END;

10g_compare.sql
Copyright 2000-2005 Steven Feuerstein - Page 52 10g_compare2.sql
10g_compare_old.sql
Oracle10g UNION, INTERSECT, MINUS

 Straightforward, with the MULTISET keyword.
BEGIN
our_favorites := my_favorites MULTISET UNION dad_favorites;
show_favorites ('MINE then DAD', our_favorites);

our_favorites := dad_favorites MULTISET UNION my_favorites;
show_favorites ('DAD then MINE', our_favorites);

our_favorites := my_favorites MULTISET UNION DISTINCT dad_favorites;
show_favorites ('MINE then DAD with DISTINCT', our_favorites);

our_favorites := my_favorites MULTISET INTERSECT dad_favorites;
show_favorites ('IN COMMON', our_favorites);

our_favorites := dad_favorites MULTISET EXCEPT my_favorites;
show_favorites ('ONLY DAD''S', our_favorites);
END;

10g_setops.sql
10g_string_nt.sql
Copyright 2000-2005 Steven Feuerstein - Page 53 10g_favorites.sql
10g*union*.sql
Oracle10g Distinct sets of values

 Use the SET operator to work with distinct values, and
determine if you have a set of distinct values.
DECLARE
keep_it_simple strings_nt := strings_nt ();
BEGIN
keep_it_simple := SET (favorites_pkg.my_favorites);

favorites_pkg.show_favorites ('FULL SET', favorites_pkg.my_favorites);

p.l (favorites_pkg.my_favorites IS A SET, 'My favorites distinct?');
p.l (favorites_pkg.my_favorites IS NOT A SET, 'My favorites NOT distinct?');

favorites_pkg.show_favorites (
'DISTINCT SET', keep_it_simple);

p.l (keep_it_simple IS A SET, 'Keep_it_simple distinct?');
p.l (keep_it_simple IS NOT A SET, 'Keep_it_simple NOT distinct?');

END;

10g_set.sql
Copyright 2000-2005 Steven Feuerstein - Page 54 10g_favorites.pkg
Oracle10g Determining subsets of data

 Use the SUBMULTISET operator to determine if a
nested table contains only elements that are in
another nested table.
BEGIN
p.l (favorites_pkg.my_favorites
SUBMULTISET OF favorites_pkg.eli_favorites
, 'Father follows son?');

p.l (favorites_pkg.eli_favorites
SUBMULTISET OF favorites_pkg.my_favorites
, 'Son follows father?');

p.l (favorites_pkg.my_favorites
NOT SUBMULTISET OF favorites_pkg.eli_favorites
, 'Father doesn''t follow son?');

p.l (favorites_pkg.eli_favorites
NOT SUBMULTISET OF favorites_pkg.my_favorites
, 'Son doesn''t follow father?');
END; 10g_submultiset.sql
Copyright 2000-2005 Steven Feuerstein - Page 55 10g_favorites.pkg
Oracle8i SQL on Steroids: Bulk Processing
Oracle9i

 Oracle8i and Oracle9i offer groundbreaking new
syntax to improve the performance of both DML and
queries.
 In Oracle8, updating from a collection (or, in
general, performing multi-row DML) meant writing
code like this:
CREATE TYPE dlist_t AS TABLE OF INTEGER;
/
PROCEDURE remove_emps_by_dept (deptlist dlist_t)
IS “Conventio
BEGIN nal binds”
FOR aDept IN deptlist.FIRST..deptlist.LAST
LOOP (and lots of
DELETE emp WHERE deptno = deptlist(aDept); them!)
END LOOP;
END; Steven Feuerstein - Page 56
Copyright 2000-2005
Conventional Bind
Oracle server

PL/SQL Runtime Engine SQL Engine
PL/SQL block
Procedural
statement
FOR aDept IN deptlist.FIRST.. executor
deptlist.LAST
LOOP
SQL
DELETE emp statement
WHERE deptno = deptlist(aDept);
END LOOP; executor

Performance penalty
for many “context
switches”

Copyright 2000-2005 Steven Feuerstein - Page 57
Enter the “Bulk Bind”
Oracle server

PL/SQL Runtime Engine SQL Engine
PL/SQL block
Procedural
statement
FORALL aDept IN deptlist.FIRST.. executor
deptlist.LAST
DELETE emp
SQL
WHERE deptno = deptlist(aDept); statement
executor

Much less overhead for
context switching

Copyright 2000-2005 Steven Feuerstein - Page 58
Use the FORALL Bulk Bind Statement
 Instead of the individual DML operations, you can
do this:
PROCEDURE remove_emps_by_dept (deptlist dlist_t)
IS
BEGIN
FORALL aDept IN deptlist.FIRST..deptlist.LAST
DELETE FROM emp WHERE deptno = deptlist(aDept);
END;

 Things to be aware of:
– Only a single DML statement is allowed. If you want to
INSERT and then UPDATE, two different FORALL
statements.
– SQL%BULK_ROWCOUNT returns the number of rows
affected by each row in the binding array.
Copyright 2000-2005 Steven Feuerstein - Page 59
Use BULK COLLECT INTO for Queries
Oracle8i requires fetching
into individual collections of
Oracle9i R2 supports
scalars.
fetching into a collection of
CREATE OR REPLACE PROCEDURE process_emps records.
(deptno_in IN dept.depno%TYPE)
IS CREATE OR REPLACE PROCEDURE process_emps
TYPE numTab IS TABLE OF NUMBER; (deptno_in IN dept.depno%TYPE)
TYPE charTab IS TABLE OF VARCHAR2(12); IS
TYPE dateTab IS TABLE OF DATE; TYPE three_cols_rt IS RECORD (
enos numTab; empno emp.empno%TYPE,
names charTab; ename emp.ename%TYPE,
hdates dateTab; hiredate emp.hiredate%TYPE);
BEGIN TYPE three_cols_tt IS TABLE OF
SELECT empno, ename, hiredate three_cols_rt INDEX BY PLS_INTEGER;
BULK COLLECT INTO enos, names, hdates three_cols_t three_cols_tt;
FROM emp BEGIN
WHERE deptno = deptno_in; SELECT empno, ename, hiredate
FOR i IN enos.FIRST..enos.LAST BULK COLLECT INTO three_cols_t
LOOP FROM emp
do_stuff (enos(i), WHERE deptno = deptno_in;
names(i), hiredates(i)); ...
END LOOP; END;
END;
Copyright 2000-2005 Steven Feuerstein - Page 60 bulkcoll.sql
Limit the number of rows returned by
BULK COLLECT
CREATE OR REPLACE PROCEDURE bulk_with_limit
(deptno_in IN dept.deptno%TYPE)
IS Use the LIMIT clause with the
CURSOR emps_in_dept_cur IS INTO to manage the amount
SELECT *
FROM emp
of memory used with the
WHERE deptno = deptno_in; BULK COLLECT operation.

TYPE emp_tt IS TABLE OF emp%ROWTYPE;
emps emp_tt;
BEGIN
OPEN three_cols_cur; WARNING!
LOOP
FETCH emps_in_dept_cur BULK COLLECT will not raise
BULK COLLECT INTO emps
LIMIT 100;
NO_DATA_FOUND if no rows
are found.
EXIT WHEN emps.COUNT = 0;
Best to check contents of
process_emps (emps);
END LOOP; collection to confirm that
END bulk_with_limit; something was retrieved.
bulklimit.sql
Copyright 2000-2005 Steven Feuerstein - Page 61
Combining FORALL & BULK COLLECT

 Use the RETURNING clause to obtain information
about each DML statement executed with FORALL
– When executing multiple DML statements, you need to BULK
COLLECT the RETURNING results into one or more collections
– SQL%BULK_ROWCOUNT is a pseudo-collection containing the
numbers of rows modified by each statement.
FUNCTION remove_emps_by_dept (deptlist dlist_t)
RETURN enolist_t
IS
enolist enolist_t;
BEGIN
FORALL aDept IN deptlist.FIRST..deptlist.LAST
DELETE FROM emp WHERE deptno IN deptlist(aDept)
RETURNING empno BULK COLLECT INTO enolist;
RETURN enolist;
END; bulk_rowcount.sql

Copyright 2000-2005 Steven Feuerstein - Page 62
Tips and Fine Points

 Use bulk binds in these circumstances:
– Recurring SQL statement in PL/SQL loop
– Use of a collection as the bind variable, or code that could
be transformed to use a collection containing the bind
variable information
 Bulk bind rules:
– Can be used with any kind of collection; Collection
subscripts cannot be expressions; The collections must be
densely filled (pre-10g); If error occurs, prior successful
DML statements are NOT ROLLED BACK
 Bulk collects:
– Can be used with implicit and explicit cursors
Collection is filled starting at row 1
bulktiming.sql
Copyright 2000-2005 Steven Feuerstein - Page 63
Oracle9i Oracle9i Enhancements

 You can now use dynamic SQL strings in
the bulk bind and collect statements.
– FORALL for bulk DML
– BULK COLLECT for bulk queries.

 This gives you virtually unlimited flexibility
without a tradeoff in performance.

Copyright 2000-2005 Steven Feuerstein - Page 64
Oracle9i
Dynamic FORALL Example
 This example shows the use of bulk binding and
collecting, plus application of the RETURNING clause.
CREATE TYPE NumList IS TABLE OF NUMBER;
CREATE TYPE NameList IS TABLE OF VARCHAR2(15);

PROCEDURE update_emps (
col_in IN VARCHAR2, empnos_in IN numList) IS
enames NameList;
BEGIN
FORALL indx IN empnos_in.FIRST .. empnos_in.LAST
EXECUTE IMMEDIATE
'UPDATE emp SET ' || col_in || ' = ' || col_in
|| ' * 1.1 WHERE empno = :1
RETURNING ename INTO :2'
USING empnos_in (indx ) Notice that empnos_in
RETURNING BULK COLLECT INTO enames; is indexed, but enames
... is not.
END;
Copyright 2000-2005 Steven Feuerstein - Page 65
Oracle9i
Dynamic BULK COLLECT
 Now you can even avoid the OPEN FOR and just
grab your rows in a single pass!
CREATE OR REPLACE PROCEDURE fetch_by_loc (loc_in IN VARCHAR2)
IS
TYPE numlist_t IS TABLE OF NUMBER;
TYPE namelist_t IS TABLE OF employee.name%TYPE;
TYPE employee_t IS TABLE OF employee%ROWTYPE;

emp_cv sys_refcursor;

empnos numlist_t;
enames namelist_t;
l_employees employee_t;
BEGIN
OPEN emp_cv FOR 'SELECT empno, ename FROM emp_' || loc_in;
FETCH emp_cv BULK COLLECT INTO empnos, enames;
CLOSE emp_cv;
With Oracle9iR2
EXECUTE IMMEDIATE 'SELECT * FROM emp_' || loc_in
BULK COLLECT INTO l_employees; you can also fetch
END; into collections of
Copyright 2000-2005 Steven Feuerstein - Page 66 records.
Better Exception Handling
Oracle9i
for Bulk Operations
 Allows you to continue past errors and obtain
error information for each individual
operation (for dynamic and static SQL).
CREATE OR REPLACE PROCEDURE load_books (books_in IN book_obj_list_t)
IS
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT ( bulk_errors, -24381 );
BEGIN
FORALL indx IN books_in.FIRST..books_in.LAST Allows processing of all
SAVE EXCEPTIONS rows, even after an error
INSERT INTO book values (books_in(indx)); occurs.
EXCEPTION
WHEN BULK_ERRORS THEN
FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT New cursor
LOOP attribute, a pseudo-
log_error (SQL%BULK_EXCEPTIONS(indx)); collection
END LOOP;
END;
Copyright 2000-2005 Steven Feuerstein - Page 67 bulkexc.sql
Cursor FOR Loop ... or BULK COLLECT?

 Why would you ever use a cursor FOR loop
(or other LOOP) now that you can perform
a BULK COLLECT?
– If you want to do complex processing on each
row as it is queried – and possibly halt further
fetching.
– You are retrieving many rows and cannot afford
to use up the memory (large numbers of users).
 Otherwise, moving to BULK COLLECT is a
smart move!
cfl_vs_bulkcollect.sql
cfl_to_bulk.sql
Copyright 2000-2005 Steven Feuerstein - Page 68
Oracle10g More flexibility with FORALL

 In Oracle10g, the FORALL driving array no
longer needs to be processed sequentially.
 Use the INDICES OF clause to use only the
row numbers defined in another array.
 Use the VALUES OF clause to use only the
values defined in another array.

Copyright 2000-2005 Steven Feuerstein - Page 69
Oracle10g Using INDICES OF
DECLARE
 It only TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
INDEX BY PLS_INTEGER;
processes l_employees employee_aat;
TYPE boolean_aat IS TABLE OF BOOLEAN
the rows INDEX BY PLS_INTEGER;
l_employee_indices boolean_aat;
with row BEGIN
numbers l_employees (1) := 7839;
l_employees (100) := 7654;
matching l_employees (500) := 7950;
--
the l_employee_indices (1) := TRUE;
l_employee_indices (500) := TRUE;
defined l_employee_indices (799) := TRUE
--
rows of FORALL l_index IN INDICES OF l_employee_indices
the driving BETWEEN 1 AND 500
UPDATE employee
array. SET salary = 10000
WHERE employee_id = l_employees (l_index);
END;
10g_indices_of.sql
Copyright 2000-2005 Steven Feuerstein - Page 70 10g_indices_of2.sql
Oracle10g Using VALUES OF

DECLARE
 It only TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
INDEX BY PLS_INTEGER;
processes l_employees employee_aat;
the rows TYPE values_aat IS TABLE OF PLS_INTEGER
with row INDEX BY PLS_INTEGER;
l_employee_values values_aat;
numbers BEGIN
l_employees (-77) := 7820;
matching l_employees (13067) := 7799;
l_employees (99999999) := 7369;
the content --
of a row in l_employee_values (100) := -77;
l_employee_values (200) := 99999999;
the driving --
FORALL l_index IN VALUES OF l_employee_values
array. UPDATE employee
SET salary = 10000
WHERE employee_id = l_employees (l_index);
END;

Copyright 2000-2005 Steven Feuerstein - Page 71 10g_values_of.sql
Advanced topics in Dynamic SQL

 Dynamic PL/SQL block execution
 Method 4 dynamic SQL
 When DBMS_SQL comes in handy...
– Parse very long strings
– Describe columns in dynamic query
– And method 4...
 First, a brief overview.

Copyright 2000-2005 Steven Feuerstein - Page 72
What is Dynamic SQL?

 Dynamic SQL actually refers, in the world of
PL/SQL, to two things:
– SQL statements, such as a DELETE or CREATE
TABLE, that are constructed and executed at run-
time.
– Anonymous PL/SQL blocks that are constructed,
compiled and executed at run-time.

'DROP ' || 'BEGIN ' ||
l_type || ' ' || l_name l_proc_name || ' (' ||
l_parameters || '); END;'

Copyright 2000-2005 Steven Feuerstein - Page 73
Some of the possibilities with Dynamic SQL

 Build ad-hoc query and update applications.
– The user decides what to do and see.
 Execute DDL statements from within
PL/SQL.
– Not otherwise allowed in a PL/SQL block.
 Soft-code your application logic, placing
business rules in tables and executing them
dynamically.
– Usually implemented through dynamic PL/SQL
Copyright 2000-2005 Steven Feuerstein - Page 74
Oracle8i Two Methods Available

 DBMS_SQL
– A large and complex built-in package that made
dynamic SQL possible in Oracle7 and Oracle8.
 Native Dynamic SQL
– A new (with Oracle8i), native implementation of
dynamic SQL that does almost all of what
DBMS_SQL can do, but much more easily and
usually more efficiently.
 Which should you use?

Copyright 2000-2005 Steven Feuerstein - Page 75
NDS or DBMS_SQL: Which is best?

 Reasons to go with NDS:  Why You'd Use DBMS_SQL:
– Ease of use – Method 4 Dynamic SQL
– Performance (usually – DESCRIBE columns of
faster) cursor
– Works with all SQL – SQL statements larger than
datatypes (including 32K
user-defined object and – Better reuse of parsed SQL
collection types) statements -- persistent
– Fetch into records and cursor handles!
collections of records – Available from client-side
PL/SQL
Bottom line: NDS should be your first choice.
Copyright 2000-2005 Steven Feuerstein - Page 76
Some examples of "typical" dynamic SQL

dropwhatever.sp
creind81.sp  Dynamic DDL
settrig.sp
– Simply execute a string; no
information is returned or "bound"
tabcount81.sf
tabcount.sf  Dynamic queries
showcol*.sp
– Single and multiple rows
updnval_with_eh.sp
 Dynamic DML
– You will often want to bind in
variable values

Copyright 2000-2005 Steven Feuerstein - Page 77
Quiz!

PROCEDURE process_lineitem (
 What's wrong with line_in IN INTEGER)
IS
this code? BEGIN
IF line_in = 1
 How would you fix THEN
process_line1;
it? END IF;

IF line_in = 2
THEN
process_line2;
END IF;
...
IF line_in = 22045
THEN
process_line22045;
END IF;
END;

Copyright 2000-2005 Steven Feuerstein - Page 78
From 22,000 lines of code to 1!
PROCEDURE process_lineitem (
line_in IN INTEGER)
IS
BEGIN
IF line_in = 1
THEN
process_line1; PROCEDURE process_lineitem (
END IF; line_in IN INTEGER)
IS
IF line_in = 2 BEGIN
THEN EXECUTE IMMEDIATE
process_line2; 'BEGIN process_line'||
END IF; line_in ||'; END;';
... END;
IF line_in = 22045
THEN  Identify the pattern and
process_line22045;
END IF; resolve it either with reusable
END; modules or dynamic
abstractions.
Copyright 2000-2005 Steven Feuerstein - Page 79 dynplsql.txt
Oracle8i Dynamic PL/SQL with NDS

 Dynamically construct, compile and run an
anonymous block with EXEC. IMMEDIATE.
– Example: DBMS_JOB uses dynamic PL/SQL to run
stored procedures at scheduled times.
DECLARE
v_jobno INTEGER;
BEGIN
DBMS_JOB.submit (
job => v_jobno,
what => 'DBMS_DDL.ANALYZE_OBJECT ' ||
'(''TABLE'',''LOAD1'',''TENK''' ||
',''ESTIMATE'',null,estimate_percent=>50);',
next_date => TRUNC (SYSDATE + 1),
interval => 'TRUNC(SYSDATE+1)'
);
END;

Copyright 2000-2005 Steven Feuerstein - Page 80
Rules for Dynamic PL/SQL

 You must construct and execute a valid
anonymous block.
– Begins with BEGIN or DECLARE.
– Ends with END;. The trailing semi-colon is required;
otherwise it is parsed as dynamic SQL.
 You can only reference globally-accessible data
structures (declared in a package specification).
 Exceptions can (and should) be trapped in the
block from which the dynamic PL/SQL was
executed. dynplsql8i.sp
dynplsql_nolocal.sql
Copyright 2000-2005 Steven Feuerstein - Page 81
Dynamic PL/SQL Possibilities

 There are so many possibilities....some things I have
done:
– Dramatically reduce code volume, improve performance.
– Generic string parsing engine: parse any string into your
own collection.
– Generic calculator engine.
– Implement support for "indirect referencing": read and
change values of variables whose names are only
determined at run-time.
– Build a unit test harness to automatically find and run your
test code.
 And there are also dangers: code injection
dynvar81.pkg
dyncalc.pkg
code_injection.sql
Copyright 2000-2005 Steven Feuerstein - Page 82
The utPLSQL Architecture

 utPLSQL.test constructs the names of and executes
the setup, unit test and teardown procedures based
on the package name you provide.
Your Test Package
1 Assertion API
ut_Setup
2
utPLSQL.test ut_TestMe Assert EQ
Test Engine
ut_Teardown Assert NULL

Assert EqTable

Report Results Test for Nulls
4 Invalid ID
5 Valid ID 3
Start date too late
Results Table End date too early
Name unique
http://utplsql.sourceforge.net/
Copyright 2000-2005 Steven Feuerstein - Page 83 http://utplsql.oracledeveloper.nl/
How to build dynamic PL/SQL code

 1. Build a static version of the logic you want to
execute dynamically.
– Test it thoroughly.
 2. Identify the portions of the static code which will
need to be made dynamic.
 3. Convert the block, concatenating or binding
those portions which are now dynamic.
 4. Include tight error handling and reporting from
the start to make sure you can quickly identify and
fix issues.
Copyright 2000-2005 Steven Feuerstein - Page 84
1. Write and verify the static block code.
PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim)
IS
v_loc PLS_INTEGER;
 Here is a v_startloc PLS_INTEGER := 1;
v_item VARCHAR2 (2000);
static BEGIN
dirs.DELETE;
program to LOOP
v_loc := INSTR (str, delim, v_startloc);
parse a
IF v_loc = v_startloc
string of THEN
v_item := NULL;
directories ELSIF v_loc = 0
THEN
for the v_item := SUBSTR (str, v_startloc);
path list. ELSE
v_item := SUBSTR (str, v_startloc, v_loc - v_startloc);
END IF;
dirs (dirs.COUNT + 1) := v_item;
IF v_loc = 0
THEN
EXIT;
ELSE
v_startloc := v_loc + 1;
END IF;
END LOOP;
END set_path;
filepath.pkg
Copyright 2000-2005 Steven Feuerstein - Page 85
2. Identify the dynamic elements of the block.
PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim)
IS
v_loc PLS_INTEGER;
v_startloc PLS_INTEGER := 1;
v_item VARCHAR2 (2000);
Dynamic code BEGIN
dirs.DELETE;
LOOP
v_loc := INSTR (str, delim, v_startloc);
IF v_loc = v_startloc
Bind variable THEN
v_item := NULL;
ELSIF v_loc = 0
THEN
v_item := SUBSTR (str, v_startloc);
ELSE
v_item := SUBSTR (str, v_startloc, v_loc - v_startloc);
END IF;
dirs (dirs.COUNT + 1) := v_item;
IF v_loc = 0
THEN
EXIT;
ELSE
v_startloc := v_loc + 1;
END IF;
END LOOP;
END set_path;

Copyright 2000-2005 Steven Feuerstein - Page 86
3a. Convert from static to dynamic block.

dynblock :=
 Assign the 'DECLARE
v_loc PLS_INTEGER;
v_start PLS_INTEGER := 1;
complex v_item ' || datatype || ';
BEGIN ' ||
string to a collname || '.DELETE;
IF :str IS NOT NULL
variable. THEN
LOOP
 Makes it v_loc := INSTR (:str, :delim, v_start);
IF v_loc = v_startloc
THEN
easier to v_item := NULL;
ELSIF v_loc = 0
report THEN
v_item := SUBSTR (:str, v_start);
errors and ELSE
v_item := SUBSTR (:str, v_start, v_loc - v_start);
debug. END IF;' ||
collname || '(' || nextrowstring || ') := v_item;
IF v_loc = 0 THEN EXIT;
ELSE v_start := v_loc + 1;
END IF;
END LOOP;
END IF;
END;';
str2list.pkg
Copyright 2000-2005 Steven Feuerstein - Page 87
3b. Execute the dynamic block.

 With dynamic PL/SQL, even if you reference
the same bind variable more than once, you
only specify it once in the USING clause.
– In other words, PL/SQL is using a variation of
"named notation" rather than the default
positional notation for dynamic SQL statements.

EXECUTE IMMEDIATE dynblock
USING str, delim;

Copyright 2000-2005 Steven Feuerstein - Page 88
4. Build in solid error handling from the start.

EXCEPTION
WHEN OTHERS THEN
disperr (dynblock);
RAISE;
END parse;

 In your error handler, consider calling these
functions to provide useful information:
PROCEDURE disperr (str IN VARCHAR2)
IS Display full error
BEGIN message.
DBMS_OUTPUT.PUT_LINE ('Compilation/Execution Error:');
DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_STACK);
DBMS_OUTPUT.PUT_LINE ('In:');
Show line number
DBMS_OUTPUT.PUT_LINE (str);
on which error was
DBMS_OUTPUT.PUT_LINE ('Backtrack Error Stack:');
raised.
DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
Oracle10g only.
DBMS_OUTPUT.PUT_LINE ('Call Stack:');
DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_CALL_STACK);
END;

Show execution call
Copyright 2000-2005 Steven Feuerstein - Page 89
stack.
Method 4 Dynamic SQL
with DBMS_SQL and NDS

 Method 4 dynamic SQL is the most generalized
and most complex - by far!
– You don't know at compile time either the number of
columns or the number of bind variables.
– With DBMS_SQL, you must put calls to
DBMS_SQL.DEFINE_COLUMN and/or
DBMS_SQL.BIND_VARIABLE into loops.
 With NDS, you must shift from dynamic SQL to
dynamic PL/SQL.
– How else can you have a variable INTO or USING
clause?
Copyright 2000-2005 Steven Feuerstein - Page 90
Dynamic "SELECT * FROM <table>" in PL/SQL

 You provide the table and WHERE clause. I
display all the data.
– I don't know in advance which or how many rows
to query.
 I can obtain the column information from
ALL_TAB_COLUMNS...and from there the
fun begins!
 Let's compare the DBMS_SQL and NDS
implementations. intab.sp
intab9i.sp
intab9i.tst
Copyright 2000-2005 Steven Feuerstein - Page 91
Pseudo-code flow for
DBMS_SQL implementation
BEGIN
Build the FOR each-column-in-table LOOP
SELECT list add-column-to-select-list;
END LOOP;
Parse the
DBMS_SQL.PARSE (cur, select_string, DBMS_SQL.NATIVE);
variable SQL
FOR each-column-in-table LOOP
Define each
DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype);
column
END LOOP;

Execute the fdbk := DBMS_SQL.EXECUTE (cur);
query
LOOP
fetch-a-row;
Extract each FOR each-column-in-table LOOP
value DBMS_SQL.COLUMN_VALUE (cur, nth_col, val);
END LOOP;
END LOOP;
Lots of code, but relatively
END; intab.sp
straightforward
Copyright 2000-2005 Steven Feuerstein - Page 92
The method 4 challenge for NDS

 It's not too difficult to build the SELECT list (I can even
use BULK COLLECT against ALL_TAB_COLUMNS to
speed up the process).
 But I still have to fetch that data back into local
variables. How many? Who knows! And the INTO
clause is static.
How about this?
EXECUTE IMMEDIATE EXECUTE IMMEDIATE
This 'SELECT ' || col_list || 'SELECT ' || col_list ||
won't ' FROM ' || table_in ||
' WHERE ' || where_clause
' FROM ' || table_in ||
' WHERE ' || where_clause
work: INTO col1, col2 ... ? INTO || into_list;

It looks like, it must be...
Copyright 2000-2005 Steven Feuerstein - Page 93 dynamic PL/SQL!
Shifting paradigms to dynamic PL/SQL

 So now I am thinking about building the entire block
to fetch from and display the data.
– In which case, I can put static SQL inside my dynamic
PL/SQL.
Construct a static
BEGIN query inside a CFL
l_block :=
'BEGIN
FOR rec IN (' || query_string || ')
It's all one big LOOP
PL/SQL block DBMS_OUTPUT.PUT_LINE (' || concatenated_values || ');
END LOOP;
END;';
EXECUTE IMMEDIATE l_block;
END; Concatenate all retrieved
values into single string
intab9i.sp
expression.
No need for INTO or
USING clauses
Copyright 2000-2005 Steven Feuerstein - Page 94
Parsing very long strings

 One problem with EXECUTE IMMEDIATE is
that you pass it a single VARCHAR2 string.
– Maximum length 32K.
 So what do you do when your string is longer?
– Very likely to happen when you are generating SQL
statements based on tables with many columns.
– Also when you want to dynamically compile a
program.
 Time to switch to DBMS_SQL!
Copyright 2000-2005 Steven Feuerstein - Page 95
DBMS_SQL.PARSE overloading for collections

 Oracle offers an overloading of
DBMS_SQL.PARSE that accepts a collection
of strings, rather than a single string.
 DBMS_SQL offers two different array types:
– DBMS_SQL.VARCHAR2S - each string max 255
bytes.
– DBMS_SQL.VARCHAR2A - each string max
32,767 bytes (new in Oracle9i).

Copyright 2000-2005 Steven Feuerstein - Page 96
Compile from a file with DBMS_SQL

CREATE OR REPLACE PROCEDURE compile_from_file (
dir_in IN VARCHAR2
, file_in IN VARCHAR2
)
IS
l_file UTL_FILE.file_type;
l_lines DBMS_SQL.varchar2s;
l_cur PLS_INTEGER := DBMS_SQL.open_cursor;

PROCEDURE read_file (lines_out IN OUT DBMS_SQL.varchar2s) IS
BEGIN
... Read each line into array; see compile_from_file.sql
l_file := UTL_FILE.fopen (dir_in, file_in, 'R');
END read_file;
BEGIN
read_file (l_lines);
DBMS_SQL.parse (l_cur
You can specify a , l_lines
subset of lines in , l_lines.FIRST
the array. , l_lines.LAST
, TRUE
, DBMS_SQL.native
);
DBMS_SQL.close_cursor (l_cur); compile_from_file.sql
Copyright 2000-2005 Steven Feuerstein - Page 97
Describe columns in a query

 DBMS_SQL offers the ability to "ask" a
cursor to describe the columns defined in
that cursor.
 By using the DESCRIBE_COLUMNS
procedure, you can sometimes avoid
complex parsing and analysis logic.
– Particularly useful with method 4 dynamic SQL.

desccols.pkg
desccols.tst

Copyright 2000-2005 Steven Feuerstein - Page 98
Table Functions
SELECT c.name, Book.name, Book.author, Book.abstract
FROM Catalogs c,
TABLE (rankings_pkg.most_popular (c.cat)) Book;

 A table function is a function that returns a
collection (nested table or varray) that can be
queried like a relational table or manipulated in
PL/SQL as a collection variable.
 A special form of the table function
(PIPELINED) can be executed in parallel,
offering significant performance improvements.
– Especially useful with data warehouse applications.
Copyright 2000-2005 Steven Feuerstein - Page 99
The importance of table functions

 Table functions allow you to perform arbitrarily
complex transformations of data and then
make that data available through a query.
– Not everything can be done in SQL.
 Combined with REF CURSORs, you can now
more easily transfer data from within PL/SQL
to host environments.
– Java, for example, works very smoothly with cursor
variables
Copyright 2000-2005 Steven Feuerstein - Page 100
Building a table function

 Requirements for a table function:
– Return a nested table or varray based on a
schema-defined type, or type defined in a
PL/SQL package.
– The function header and the way it is called must
be SQL-compatible: all parameters use SQL
types; no named notation.
 Let's go through a construction exercise.
– My company breeds different kinds of animals for
sale as pets.
pet_family.sql

Copyright 2000-2005 Steven Feuerstein - Page 101
Define the nested table and associated type

 You must first define the datatypes you will be
referencing in your table function.
– If you want to return more than a single scalar
value, you will need to define a schema-level
object type.
– Note: you cannot use <table>%ROWTYPE.
CREATE TYPE pet_t IS OBJECT (
NAME VARCHAR2 (60)
, breed VARCHAR2 (100)
, dob DATE
);

CREATE TYPE pet_nt IS TABLE OF pet_t;
pet_family.sql
Copyright 2000-2005 Steven Feuerstein - Page 102
Write the function
CREATE OR REPLACE FUNCTION pet_family (
 Obviously, this dad_in IN pet_t, mom_in IN pet_t) RETURN pet_nt
IS
is very much l_count PLS_INTEGER;
:= pet_nt ();
application- retval
BEGIN
pet_nt

specific. retval.EXTEND (2);
retval (retval.LAST) := dad_in;
retval (retval.LAST) := mom_in;
 The point to
keep in mind is IF mom_in.breed = 'RABBIT' THEN l_count := 12;
ELSIF mom_in.breed = 'DOG' THEN l_count := 4;
that your logic ELSIF mom_in.breed = 'KANGAROO' THEN l_count := 1;
END IF;
can get just as
FOR indx IN 1 .. l_count
wild and crazy LOOP
as necessary retval.EXTEND;
retval (retval.LAST) :=
to create the pet_t ('BABY' || indx
, mom_in.breed, SYSDATE - indx);
data you need. END LOOP;

RETURN retval;
Copyright 2000-2005 Steven Feuerstein - Page 103 END pet_family; pet_family.sql
Call the function inside an SQL query

SELECT pets.NAME, pets.dob
FROM TABLE (pet_family (pet_t ('Hoppy', 'RABBIT', SYSDATE-450)
, pet_t ('Hippy', 'RABBIT', SYSDATE-300)
)
) pets;

 I pass in two objects, dynamically created with a
call to the object constructor function.
 I enclose the function call in the TABLE operator.
 The column names correspond to the names of the
columns in the object type.
– If a table of scalars, then COLUMN_VALUE is the column
name.
pet_family.sql
tabfunc_scalar.sql
Copyright 2000-2005 Steven Feuerstein - Page 104
Return a cursor variable from the function

CREATE OR REPLACE FUNCTION pet_family_cv pet_family.sql
RETURN sys_refcursor
IS
retval sys_refcursor;
BEGIN
OPEN retval FOR
SELECT *
FROM TABLE (pet_family (pet_t ('Hoppy', 'RABBIT', SYSDATE)
, pet_t ('Hippy', 'RABBIT', SYSDATE)
)
); DECLARE
TYPE pet_r IS RECORD (
RETURN retval; NAME VARCHAR2 (60), breed VARCHAR2 (100), dob DATE);
END pet_family_cv;
rec pet_r; cv sys_refcursor;
BEGIN
cv := pet_family_cv;

LOOP
FETCH cv INTO rec;
EXIT WHEN cv%NOTFOUND;
DBMS_OUTPUT.put_line (rec.NAME);
END LOOP;
END;
Copyright 2000-2005 Steven Feuerstein - Page 105
Stream data with table functions

 Data streaming means that you can pass data
from one process or stage to another without
intermediate staging.
– No need to instantiate and fill a local data structure.
– Can improve performance.
 Let's look at a typical data warehouse example:
pivoting of data.
BEGIN
INSERT INTO tickertable
SELECT *
FROM TABLE (stockpivot (CURSOR (SELECT *
FROM stocktable)));
END;
Copyright 2000-2005 Steven Feuerstein - Page 106 tabfunc_streaming.sql
About pipelined table functions

CREATE FUNCTION StockPivot (p refcur_pkg.refcur_t)
RETURN TickerTypeSet PIPELINED

 Pipelined functions allow you to return data iteratively,
asynchronous to termination of the function.
– As data is produced within the function, it is passed back to
the calling process/query.
 Pipelined functions can be defined to support parallel
execution.
– Iterative data processing allows multiple processes to work on
that data simultaneously.

Copyright 2000-2005 Steven Feuerstein - Page 107
Piping rows out from a pipelined function

CREATE FUNCTION stockpivot (p refcur_pkg.refcur_t)
Add PIPELINED RETURN tickertypeset
keyword to header PIPELINED
IS
out_rec tickertype := tickertype (NULL, NULL, NULL);
in_rec p%ROWTYPE;
BEGIN
LOOP
FETCH p INTO in_rec;
EXIT WHEN p%NOTFOUND;
out_rec.ticker := in_rec.ticker;
out_rec.pricetype := 'O';
Pipe a row of data out_rec.price := in_rec.openprice;
back to calling
block or query PIPE ROW (out_rec);
END LOOP;
CLOSE p;
RETURN...nothing
at all! RETURN;
END;

tabfunc_setup.sql
Copyright 2000-2005 Steven Feuerstein - Page 108 tabfunc_pipelined.sql
Parallel Execution and Table Functions

 Prior to Oracle9i, calling a function inside a
SQL statement caused serialization.
– The parallel query mechanism could not be used.
 Now you can enable parallel execution of a
table function.
– This greatly increases the usability of PL/SQL-
enriched SQL in data warehouse applications.

Copyright 2000-2005 Steven Feuerstein - Page 109
Enabling Parallel Execution
 The table function's parameter list must consist only
of a single strongly-typed REF CURSOR.
 Include the PARALLEL_ENABLE hint in the program
header.
– Choose a partition option that specifies how the function's
execution should be partitioned.
– "ANY" means that the results are independent of the order
in which the function receives the input rows (through the
REF CURSOR).
{[ORDER | CLUSTER] BY column_list}
PARALLEL_ENABLE ({PARTITION p BY
[ANY | (HASH | RANGE) column_list]} )

Copyright 2000-2005 Steven Feuerstein - Page 110
Examples of Parallelized Functions

CREATE OR REPLACE FUNCTION Aggregate_Xform (
p_input_rows in My_Types.cur_t) RETURN My_Types.dept_sals_tab
PIPELINED

PARALLEL_ENABLE (
with Partition p_input_rows BY ANY ) Simplest form, results don't vary
from order in which function gets
input rows.

CLUSTER P_INPUT_ROWS BY (dept)
with PARALLEL_ENABLE All rows for a given department
( PARTITION p_input_rows must go to the same slave, and
BY HASH (dept) ) rows are delivered consecutively.

ORDER p_input_rows BY (c1, c2) Rows are delivered to a particular
with PARALLEL_ENABLE slave as directed by partition...
( PARTITION p_input_rows and will be locally sorted by that
BY RANGE (c1) ) slave.
Copyright 2000-2005 Steven Feuerstein - Page 111
Table functions - Summary

 Table functions offer significant new flexibility
for PL/SQL developers.
 Consider using them when you...
– Need to pass back complex result sets of data
through the SQL layer (a query);
– Want to call a user defined function inside a
query and execute it as part of a parallel query.

Copyright 2000-2005 Steven Feuerstein - Page 112
Built-in package enhancements of note

 UTL_FILE
– Not nearly as limited as before
 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
 DBMS_SCHEDULER
 UTL_RECOMP
 UTL_MAIL
 DBMS_OUTPUT.PUT_LINE
– Big strings! Big buffer! (Oracle10g Release 2)

Copyright 2000-2005 Steven Feuerstein - Page 113
Oracle9i Release 2 New and Improved UTL_FILE

 With UTL_FILE, you can now:
– UTL_FILE.FREMOVE - Remove a file
– UTL_FILE.FRENAME - Rename a file, and also in effect
move files
– UTL_FILE.FCOPY - Copy all or part of one file to another
– UTL_FILE.FGETATTR - Retrieves attributes of the file,
such as its length
 You can also use a database DIRECTORY to
specify the location of the file; UTL_FILE_DIR will
be ignored!!!!

Copyright 2000-2005 Steven Feuerstein - Page 114
Working with Database Directories

 A Directory is a database "object", define with
a CREATE statement.
– You need CREATE ANY DIRECTORY privilege.

CREATE OR REPLACE DIRECTORY ERROR_LOG AS '/tmp/apps/log';

View those directories SELECT owner, directory_name, directory_path
to which you have
access.
FROM ALL_DIRECTORIES;

Grant READ or WRITE GRANT READ ON DIRECTORY error_log TO SCOTT;
privileges on a
directory.
utlfile_92.sql
Copyright 2000-2005 Steven Feuerstein - Page 115
Copy a File

DECLARE
file_suffix VARCHAR2 (100)
:= TO_CHAR (SYSDATE, 'YYYYMMDDHHMISS');
BEGIN
-- Copy the entire file...
UTL_FILE.fcopy (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip',
dest_location => 'ARCHIVE_DIR',
dest_filename => 'archive'
|| file_suffix fcopy.sql
|| '.zip' fileIO92.pkg
);
END;

 You can specify an OS directory or a database object
of type DIRECTORY (as shown above)
Copyright 2000-2005 Steven Feuerstein - Page 116
Remove a File

BEGIN
UTL_FILE.fremove (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip'
);
EXCEPTION
-- If you call FREMOVE, you should check explicitly
-- for deletion failures.
WHEN UTL_FILE.delete_failed
THEN
... Deal with failure to remove
END;

 If no error is raised, then you deleted successfully

fremove.sql
fileIO92.pkg
Copyright 2000-2005 Steven Feuerstein - Page 117
Rename/move a File

DECLARE
file_suffix VARCHAR2 (100) := TO_CHAR (SYSDATE, 'YYYYMMDD');
BEGIN
-- Rename/move the entire file in a single step.
UTL_FILE.frename (
src_location => 'DEVELOPMENT_DIR',
src_filename => 'archive.zip',
dest_location => 'ARCHIVE_DIR',
dest_filename => 'archive' || file_suffix || '.zip',
overwrite => FALSE
);
EXCEPTION
WHEN UTL_FILE.rename_failed
frename.sql
THEN fileIO92.pkg
... Deal with failure to rename
END;

 You specify target location and file name
Copyright 2000-2005 Steven Feuerstein - Page 118
Obtaining attributes of a file

CREATE OR REPLACE FUNCTION flength (
location_in IN VARCHAR2,
file_in IN VARCHAR2  How big is a file? What
)
RETURN PLS_INTEGER is its block size? Does
IS
TYPE fgetattr_t IS RECORD ( the file exist?
fexists BOOLEAN,
file_length PLS_INTEGER,  All valuable questions.
block_size PLS_INTEGER
);  All answered with a call
 
fgetattr_rec fgetattr_t; to UTL_FILE.FGETATTR.
BEGIN
UTL_FILE.fgetattr (
location => location_in,
filename => file_in,
fexists => fgetattr_rec.fexists,
file_length => fgetattr_rec.file_length,
block_size => fgetattr_rec.block_size
); flength.sql
RETURN fgetattr_rec.file_length; fileIO92.pkg
END flength;
Copyright 2000-2005 Steven Feuerstein - Page 119
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE

Long-standing challenge in PL/SQL:

How can I find the line number on which an
error was raised in PL/SQL?

 Before Oracle10g, the only way is to let the error go
unhandled in your PL/SQL code!
 DBMS_UTILITY.FORMAT_ERROR_STACK only
gives you the full error message.
– And is recommended by Oracle in place of SQLERRM.

But in Oracle10g, we have "back trace"!
Copyright 2000-2005 Steven Feuerstein - Page 120
Letting the error go unhandled…

CREATE OR REPLACE PROCEDURE proc1 IS
BEGIN
DBMS_OUTPUT.put_line ('running proc1');
RAISE NO_DATA_FOUND;
END;
/
CREATE OR REPLACE PROCEDURE proc2 IS
l_str VARCHAR2(30)
:= 'calling proc1';
BEGIN
DBMS_OUTPUT.put_line (l_str);
proc1;
END;
/
CREATE OR REPLACE PROCEDURE proc3 IS ERROR at line 1:
BEGIN ORA-01403: no data found
DBMS_OUTPUT.put_line ('calling proc2'); ORA-06512: at "SCOTT.PROC1", line 7
proc2; ORA-06512: at "SCOTT.PROC2", line 8
END; ORA-06512: at "SCOTT.PROC3", line 5
/ ORA-06512: at line 3

Backtrace.sql
Copyright 2000-2005 Steven Feuerstein - Page 121
Displaying the “error stack” inside PL/SQL

CREATE OR REPLACE PROCEDURE proc3
IS
BEGIN
DBMS_OUTPUT.put_line ('calling proc2');
proc2;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line (
DBMS_UTILITY.FORMAT_ERROR_STACK);
END;
/

SQL> exec proc3
calling proc2
calling proc1
running proc1
backtrace.sql ORA-01403: no data found

Copyright 2000-2005 Steven Feuerstein - Page 122
Displaying the contents of BACKTRACE

CREATE OR REPLACE PROCEDURE proc3
IS
BEGIN
DBMS_OUTPUT.put_line ('calling proc2');
proc2;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error stack at top level:');
DBMS_OUTPUT.put_line (DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/

SQL> exec proc3
calling proc2
calling proc1
running proc1
Error stack at top level:
ORA-06512: at "SCOTT.PROC1", line 5
ORA-06512: at "SCOTT.PROC2", line 7
ORA-06512: at "SCOTT.PROC3", line 5

backtrace.sql
Copyright 2000-2005 Steven Feuerstein - Page 123 bt.pkg
The BACKTRACE stack with re-RAISEs

CREATE OR REPLACE PROCEDURE proc1 IS
BEGIN
… SQL> exec proc3
EXCEPTION calling proc2
WHEN OTHERS THEN calling proc1
DBMS_OUTPUT.put_line ( running proc1
'Error stack in block where raised:'); Error stack in block where raised:
DBMS_OUTPUT.put_line ( ORA-06512: at "SCOTT.PROC1", line 5
DBMS_UTILITY.format_error_backtrace);
RAISE; Error stack at top level:
END; ORA-06512: at "SCOTT.PROC1", line
/ 11
CREATE OR REPLACE PROCEDURE proc3 IS ORA-06512: at "SCOTT.PROC2", line 7
BEGIN ORA-06512: at "SCOTT.PROC3", line 5
DBMS_OUTPUT.put_line ('calling proc2');
proc2; Program owner = SCOTT
EXCEPTION Program name = PROC1
WHEN OTHERS Line number = 11
THEN
DBMS_OUTPUT.put_line ('Error stack at top level:');
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_backtrace);
bt.show (DBMS_UTILITY.format_error_backtrace);END;
/
Copyright 2000-2005 Steven Feuerstein - Page 124
The BACKTRACE stack with new RAISE

CREATE OR REPLACE PROCEDURE proc1
IS
BEGIN
DBMS_OUTPUT.put_line ('running proc1');
RAISE NO_DATA_FOUND;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line ('Error stack in block where raised:');
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_backtrace);
RAISE;
END; SQL> exec proc3
/ calling proc2
calling proc1
CREATE OR REPLACE PROCEDURE proc2 running proc1
IS Error stack in block where raised:
BEGIN ORA-06512: at "SCOTT.PROC1", line 5
DBMS_OUTPUT.put_line ('calling proc1');
proc1; Error stack at top level:
EXCEPTION ORA-06512: at "SCOTT.PROC2", line 9
WHEN OTHERS ORA-06512: at "SCOTT.PROC3", line 5
THEN
RAISE VALUE_ERROR; Program owner = SCOTT
END; Program name = PROC2
Copyright
/ 2000-2005 Steven Feuerstein - Page 125 Line number = 9
DBMS_OUTPUT: relief in sight!

 Oracle10g Release 2 offers some long-
awaited enhancements...
– DBMS_OUTPUT.PUT_LINE will now
accept and display strings up to 32K in 32K!
length.
– You can set the buffer size to UNLIMITED.

SET SEVEROUTPUT ON SIZE UNLIMITED

Copyright 2000-2005 Steven Feuerstein - Page 126
Oracle10g DBMS_SCHEDULER

 Oracle10g offers a new, built-in job scheduler
to help you take care of routine tasks.
– Much improved over DBMS_JOB.
– Lets you control database resources and assign
priorities.
 Work with the scheduler either through the
Oracle10g Enterprise Manager Database
Control or the DBMS_SCHEDULER package.
 We will take a brief look at some of the
features of the built-in package. Some graphics and content
Copyright 2000-2005 Steven Feuerstein - Page 127 courtesy of Oracle Magazine.
Job scheduler components

 A job combines a program and schedule.
– The program defines what is to be run.
– For example, a program can be a PL/SQL block, a stored procedure, or
an operating system script.
– The schedule defines when the program runs.
 Jobs, schedules, and programs are the three core components
you must master to begin using the scheduler.
Copyright 2000-2005 Steven Feuerstein - Page 128
Job scheduler resource management

 Job classes connect the scheduling system with the resource
management system, giving you control over how database
resources are allocated to running jobs.
 Scheduler windows can control when different resource plans
take effect. Window groups group together related windows.
– Windows and window groups give you a great deal of control over how
you can allocate database resources to different job classes.

Copyright 2000-2005 Steven Feuerstein - Page 129
Create a program

 The program defines the stored procedure,
PL/SQL block or operating system program
that you want to execute on a schedule.
BEGIN
dbms_scheduler.create_program
(program_name => 'EMP_MAINT.LOAD_HISTORY'
, program_action =>
'BEGIN
load_history_data;
END;'
, program_type => 'PLSQL_BLOCK'
, number_of_arguments => 0
, comments => 'Load employee history'
, enabled => TRUE
);
END;

Copyright 2000-2005 Steven Feuerstein - Page 130
Create a schedule

 Define the schedule. You can specify....
– Start date, end date, frequency and duration.
– For a one-off job, a schedule will include only a start time.
For repeating jobs, you can specify a start time; a
repetition schedule; and, optionally, an end time.
BEGIN
dbms_scheduler.create_schedule
(repeat_interval =>
'FREQ=DAILY;INTERVAL=2;BYHOUR=18;BYMINUTE=0;BYSECOND=0'
, start_date =>
TO_TIMESTAMP_TZ ('2004-03-22 US/Eastern', 'YYYY-MM-DD TZR')
, comments =>
'Schedule for periodic loads of customer-related data'
, schedule_name => '"EMP_MAINT"."HISTORY_LOADS"'
);
END;

Copyright 2000-2005 Steven Feuerstein - Page 131
Create a window

 A job scheduler window lets you specify...
– Start date, end date, frequency and duration.
– Database resources and priorities.
BEGIN
dbms_scheduler.create_window
(window_name => 'night'
, resource_plan => 'BATCH_PROCESSING'
, start_date =>
TO_TIMESTAMP_TZ ('2004-03-22 US/Eastern', 'YYYY-MM-DD TZR')
, DURATION => NUMTODSINTERVAL (840, 'minute')
, repeat_interval =>
'FREQ=DAILY;BYHOUR=18;BYMINUTE=0;BYSECOND=0'
, end_date => NULL
, window_priority => 'LOW'
, comments => ''
);
END;

Copyright 2000-2005 Steven Feuerstein - Page 132
Use OEM Database Control

 If you don't need to define your database jobs
programmatically, you would be best off using the
graphical interface available in OEM Database Control.

Copyright 2000-2005 Steven Feuerstein - Page 133
Oracle10g UTL_RECOMP

 The UTL_RECOMP built-in package offers
two programs that you can use to recompile
any invalid objects in your schema:
RECOMP_SERIAL and RECOMP_PARALLEL.
– Must connect as SYSDBA account to use
UTL_RECOMP.
– Parallel version uses DBMS_JOB and will temporarily
disable all other jobs in the queue to avoid conflicts with
the recompilation.
CALL utl_recomp.recomp_serial ('SCOTT');
CALL utl_recomp.recomp_parallel ('SCOTT', 4); recompile.sql

Copyright 2000-2005 Steven Feuerstein - Page 134
Oracle10g UTL_MAIL

 UTL_MAIL makes it much easier to send
email from within PL/SQL by hiding some of
the complexities of UTL_SMTP.
 To use UTL_MAIL...
– Set the SMTP_OUTPUT_SERVER parameter.
– Install the utlmail.sql and prvtmail.plb files under
SYS. That's right - it is not installed by default.
– Grant EXECUTE on UTL_MAIL as desired.

Copyright 2000-2005 Steven Feuerstein - Page 135
Send an email message from PL/SQL

 The interface to the SEND program mimics
the basic "send email" form of Outlook and
other email programs.
BEGIN /* Requires Oracle10g */
UTL_MAIL.send (
sender => 'me@mydomain.com'
,recipients => 'you@yourdomain.com, him@hisdomain.com'
,cc => 'mom@momdomain.com'
,bcc => 'me@mydomain.com'
,subject => 'Cool new API for sending email'
,message =>
'Hi Ya''ll,
Sending email in PL/SQL is *much* easier with UTL_MAIL in 10g.
Give it a try!
Mailfully Yours,
Bill'
);
END;
Copyright 2000-2005 Steven Feuerstein - Page 136
Attachments and UTL_MAIL

 You can attach RAW or VARCHAR2 content
as an attachment (up to 32K).
BEGIN /* Requires Oracle10g */
UTL_MAIL.send_attachment_raw (
sender => 'me@mydomain.com'
,recipients => 'you@yourdomain.com, him@hisdomain.com'
,cc => 'mom@momdomain.com'
,bcc => 'me@mydomain.com'
,subject => 'Cool new API for sending email'
,message => '...'
,attachment => '...' /* Content of the attachment */
,att_inline => TRUE /* Attachment in-line? */
,att_filename => '...'
/* Name of file to hold the attachment after the mail
is received. */
);
END;

Copyright 2000-2005 Steven Feuerstein - Page 137
Acknowledgements and Resources

 Very few of my ideas are truly
original. I have learned from
every one of these books and
authors – and you can, too!

Copyright 2000-2005 Steven Feuerstein - Page 138
A guide to my mentors/resources

 A Timeless Way of Building – a beautiful and deeply spiritual book on
architecture that changed the way many developers approach writing software.
 Peopleware – a classic text on the human element behind writing software.
 Refactoring – formalized techniques for improving the internals of one's code
without affect its behavior.
 Code Complete – another classic programming book covering many aspects of
code construction.
 The Cult of Information – thought-provoking analysis of some of the down-
sides of our information age.
 Patterns of Software – a book that wrestles with the realities and problems with
code reuse and design patterns.
 Extreme Programming Explained – excellent introduction to XP.
 Code and Other Laws of Cyberspace – a groundbreaking book that recasts
the role of software developers as law-writers, and questions the direction that
software is today taking us.

Copyright 2000-2005 Steven Feuerstein - Page 139
So Much to Learn...

 Don't panic -- but don't stick your head in the
sand, either.
– You won't thrive as an Oracle7, Oracle8 or Oracle8i
developer!
 You can do so much more from within PL/SQL
than you ever could before.
– Familiarity with new features will greatly ease the
challenges you face.
Copyright 2000-2005 Steven Feuerstein - Page 140