Copyright 2000-2005 Steven Feuerstein - Page 1
21st Century PL/SQL
Breaking out of Oracle7, Oracle8 and Oracle8i
programming ruts
Steven [email protected]
www.oracleplsqlprogramming.com
All non-technical views expressed are those of Steven Feuerstein and do not necessarily (and not likely!) reflect those of Oracle Corporation.
Copyright 2000-2005 Steven Feuerstein - Page 2
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 3
"All" about Steven Feuerstein
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).
www.stevenfeuerstein.com
Copyright 2000-2005 Steven Feuerstein - Page 4
Ten Years of Writing on the Oracle PL/SQL Language
Copyright 2000-2005 Steven Feuerstein - Page 5
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 6
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 7
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 8
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 9
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
Oracle10g
Copyright 2000-2005 Steven Feuerstein - Page 10
Learn about the PL/SQL optimizer
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.
http://www.oracle.com/technology/tech/pl_sql/htdocs/new_in_10gr1.htm
Copyright 2000-2005 Steven Feuerstein - Page 11
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:
and then:
ALTER PROCEDURE bigproc COMPILE PLSQL_OPTIMIZE_LEVEL = 1;
ALTER PROCEDURE bigproc COMPILE REUSE SETTINGS;
Copyright 2000-2005 Steven Feuerstein - Page 12
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';
Oracle10g
Copyright 2000-2005 Steven Feuerstein - Page 13
Compiler time warnings - example
Check for “unreachable end” code….SQL> CREATE OR REPLACE PROCEDURE unreachable_code IS2 x NUMBER := 10;3 BEGIN4 IF x = 10 THEN5 x := 20;6 ELSE7 x := 100; -- unreachable code8 END IF;9 END unreachable_code;10 / SP2-0804: Procedure created with compilation warnings SQL> show errErrors for PROCEDURE UNREACHABLE_CODE: LINE/COL ERROR-------- -------------------------------------7/7 PLW-06002: Unreachable code
plw*.sql
Copyright 2000-2005 Steven Feuerstein - Page 14
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 15
Using the object settings view
Show all the program units that are not fully leveraging code optimization:
Show all objects which have had one or more compile-time warnings disabled:
SELECT owner, name FROM all_plsql_object_settings WHERE plsql_optimize_level IN (1,0);
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 16
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.ddlpackage_analyzer.*
Copyright 2000-2005 Steven Feuerstein - Page 17
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 18
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 19
What is a collection?
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.
abc def sf q rrr swq...1 2 3 4 22 23
Copyright 2000-2005 Steven Feuerstein - Page 20
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 21
System Global Area (SGA) of RDBMS Instance
Refresher: PL/SQL in Shared Memory
Shared Pool
Large Pool
Reserved Pool
show_empscalc_totals upd_salaries
Select * from emp
Shared SQL
Pre-parsedUpdate emp Set sal=...
Library cache
Session 1 memory (PGA/UGA)
emp_rec emp%rowtype;tot_tab tottabtype;
Session 2 memory (PGA/UGA)
emp_rec emp%rowtype;tot_tab tottabtype;Session 1
Session 2
mysess.pkgSess2.sql
Copyright 2000-2005 Steven Feuerstein - Page 22
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 23
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.assoc_array_example.sql
Copyright 2000-2005 Steven Feuerstein - Page 24
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.
nested_table_example.sql
Copyright 2000-2005 Steven Feuerstein - Page 25
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.
varray_example.sql
Copyright 2000-2005 Steven Feuerstein - Page 26
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 27
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 28
Dealing with Mutating Table errors
Row level triggers cannot query from or change the contents of the table to which it is attached; it is "mutating".
But statement level triggers do not have this restriction.
So what are you supposed to do when a row-level operation needs to "touch" that table?
UPDATE row 1
UPDATE row N
UPDATE emp SET sal = 1000
Database triggers can be associated with both the DML statement as a whole and individual rows affected by that statement.
Note: in Oracle8i, you can use autonomous transactions to relax
restrictions associated with queries.
mutating.sql
Statement level
Row level
Copyright 2000-2005 Steven Feuerstein - Page 29
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.
1st row trigger fires
Nth row trigger fires
Work List(collection)
Statement Trigger
Writes to list
Writes to list
Process datain the list.
mutating_trigger.pkgranking.pkg
Copyright 2000-2005 Steven Feuerstein - Page 30
FunctionPGA
Data Caching with PL/SQL Tables
First access
Subsequent accesses
PGAFunction
Database
Not in cache;Request datafrom database
Pass Datato Cache
Application
Application Requests Data
Data retrieved from cache Data returned
to application
Application
Application Requests Data
Data returned to application
Data retrieved from cache
DatabaseData found in
cache. Databaseis not needed.
emplu.pkgemplu.tst
Copyright 2000-2005 Steven Feuerstein - Page 31
New indexing capabilities 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...)
Oracle9i Release 2
Copyright 2000-2005 Steven Feuerstein - Page 32
Examples of New 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 NUMBER INDEX BY employee.last_name%TYPE; TYPE array_t8 IS TABLE OF NUMBER INDEX BY types_pkg.subtype_t;
Oracle9i Release 2
Copyright 2000-2005 Steven Feuerstein - Page 33
Working with VARCHAR2-Indexed Collections
Specifying a row via a string takes some getting used to, but if offers some very powerful advantages.
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;
howmany := country_population('Greenland');
continent_population('Australia') := 30000000;END;
assoc_array*.sqlassoc_array_perf.tst
Oracle9i Release 2
Copyright 2000-2005 Steven Feuerstein - Page 34
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.
Demonstration package:assoc_array5.sql
Oracle9i Release 2
Comparison of performance of different approaches:
vocab*.*
Generate a caching package:genaa.sqlgenaa.tst
Copyright 2000-2005 Steven Feuerstein - Page 35
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
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 36
Collections with Named, 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 ( tag_no INTEGER, NAME VARCHAR2 (60), petcare vet_visits_t, MEMBER FUNCTION set_tag_no (new_tag_no IN INTEGER) RETURN pet_t);/
Continued...multilevel_collections.sql
Collection nested inside object type
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 37
Collections with Named, 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) := pet_t ( 100, 'Mercury', vet_visits_t ( vet_visit_t ( '01-Jan-2001', 'Clip wings'), vet_visit_t ( '01-Apr-2002', 'Check cholesterol') ) ); DBMS_OUTPUT.put_line (my_pets (1).petcare (2).reason);END;
Outer collection
Inner collection
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 38
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."
Oracle9i
BEGIN gps_info (1, 45, 605) := l_value;
BEGIN gps_info (605) (45) (1) := l_value;
Can't do this... Have to do something like this instead...
Copyright 2000-2005 Steven Feuerstein - Page 39
Multi-dimensional array emulationCREATE OR REPLACE PACKAGE multdim IS TYPE dim1_t IS TABLE OF VARCHAR2 (32767) INDEX BY BINARY_INTEGER; TYPE dim2_t IS TABLE OF dim1_t INDEX BY BINARY_INTEGER; 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, value_in IN VARCHAR2 ); FUNCTION getcell ( array_in IN dim3_t, dim1_in PLS_INTEGER, dim2_in PLS_INTEGER, dim3_in PLS_INTEGER ) RETURN VARCHAR2;END multdim;
multdim.*multdim2.*
gen_multcoll.sp
Oracle9i
Three levels of collections
Set a cell value
Get a cell value
Copyright 2000-2005 Steven Feuerstein - Page 40
Multi-dimensional array emulation
CREATE OR REPLACE PACKAGE BODY multdimIS 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, dim1_in PLS_INTEGER, dim2_in PLS_INTEGER, dim3_in PLS_INTEGER) RETURN VARCHAR2 IS BEGIN RETURN array_in(dim3_in )(dim2_in )(dim1_in); END;
Oracle9i
As close as you can get...
Copyright 2000-2005 Steven Feuerstein - Page 41
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.
overloadings
naming_conventions
bad_datatypes
CodecheckpackageOverloadCheck:
A QA packagefor PL/SQL
Copyright 2000-2005 Steven Feuerstein - Page 42
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?
BEGIN salespkg.calc_total ('ABC');END;/
PACKAGE salespkgIS PROCEDURE calc_total ( dept_in IN VARCHAR2); PROCEDURE calc_total ( dept_in IN CHAR);END salespkg;
?ambig_overloading.sql
Copyright 2000-2005 Steven Feuerstein - Page 43
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.tstall_arguments.sql
allargs.*
Copyright 2000-2005 Steven Feuerstein - Page 44
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 INDEX BY BINARY_INTEGER; l_arguments all_arguments_tt;BEGIN FOR rec IN ( SELECT * FROM all_arguments WHERE owner = USER AND package_name = package_in) LOOP l_arguments (SQL%ROWCOUNT) := rec; END LOOP;END;
Load it up!
Emulate the view.
Copyright 2000-2005 Steven Feuerstein - Page 45
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;BEGIN FOR indx IN l_arguments.FIRST .. l_arguments.LAST LOOP IF l_arguments (indx).object_name != l_last_program OR l_last_program IS NULL THEN l_last_program := l_arguments (indx).object_name; l_is_new_program := TRUE; do_new_program_stuff; END IF; ...
IF l_arguments (indx).overload != l_last_overload OR l_last_overload = -1 THEN IF l_is_new_program THEN do_first_overloading_stuff; ELSE do_new_overloading_stuff; END IF; END IF; END LOOP;END;
Copyright 2000-2005 Steven Feuerstein - Page 46
Discovery: there is a natural hierarchy to ALL_ARGUMENTS data!
Each program has zero or more overloadings, each overloading has N arguments, and each argument can have multiple "breakouts" (my term - applies to non-scalar parameters, such as records or object types).
RUN_TEST
SHOW_RESULTS
RESET_FLAGS
Program name
Overloading 1
Overloading 2
Overloading
Argument 1
Argument 2
Argument 3
Argument 4
Argument 5
ArgumentBreakout 1
Breakout 1
Breakout 2
Breakout 3
Breakout
Copyright 2000-2005 Steven Feuerstein - Page 47
What if I reflect this hierarchy in a collection of collections?
Have to build from the bottom up:
TYPE breakouts_t IS TABLE OF all_arguments%ROWTYPE INDEX BY BINARY_INTEGER;
TYPE arguments_t IS TABLE OF breakouts_t INDEX BY BINARY_INTEGER;
TYPE overloadings_t IS TABLE OF arguments_t INDEX BY BINARY_INTEGER;
TYPE programs_t IS TABLE OF overloadings_t INDEX BY all_arguments.object_name%type;
1. Set of rows from ALL_ARGUMENTS
String-based index
2. All the "breakout" info for a single argument
3. All the argument info for a single overloading
4. All the overloadings for a distinct program name
Copyright 2000-2005 Steven Feuerstein - Page 48
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 l_arguments (NVL (l_arguments.LAST, 0) + 1) := rec; l_programs (rec.object_name) (NVL (rec.overload, 0)) (rec.position) (rec.data_level) := rec;END LOOP;
I can still do the typical sequential
load.
But I will now also add the multi-level
load in single assignment
show_all_arguments.spshow_all_arguments.tst
Copyright 2000-2005 Steven Feuerstein - Page 49
And then I can "query" the contents with a minimum of code
l_programs ('TOP_SALES') (2).EXISTS (0)
Is the TOP_SALES program overloaded?
l_programs ('TOP_SALES') (2)(0)(0).datatype
l_programs ('TOP_SALES').COUNT > 1
Is the 2nd overloading of TOP_SALES a
function?
What is the datatype of the RETURN clause 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 50
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 51
Nested Tables unveil their 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.
Oracle10g
Copyright 2000-2005 Steven Feuerstein - Page 52
Check for equality and inequality
Just use the basic operators….
Oracle10g
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.sql10g_compare2.sql
10g_compare_old.sql
Copyright 2000-2005 Steven Feuerstein - Page 53
UNION, INTERSECT, MINUS
Straightforward, with the MULTISET keyword.
Oracle10g
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.sql10g_string_nt.sql10g_favorites.sql
10g*union*.sql
Copyright 2000-2005 Steven Feuerstein - Page 54
Distinct sets of values
Use the SET operator to work with distinct values, and determine if you have a set of distinct values.
Oracle10g
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.sql10g_favorites.pkg
Copyright 2000-2005 Steven Feuerstein - Page 55
Determining subsets of data
Use the SUBMULTISET operator to determine if a nested table contains only elements that are in another nested table.
Oracle10g
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
10g_favorites.pkg
Copyright 2000-2005 Steven Feuerstein - Page 56
SQL on Steroids: Bulk Processing
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)ISBEGIN FOR aDept IN deptlist.FIRST..deptlist.LAST LOOP DELETE emp WHERE deptno = deptlist(aDept); END LOOP;END;
“Conventional binds” (and lots of them!)
Oracle8iOracle9i
Copyright 2000-2005 Steven Feuerstein - Page 57
Oracle server
PL/SQL Runtime Engine SQL Engine
PL/SQL blockProcedural statement executor
SQL statement executor
FOR aDept IN deptlist.FIRST.. deptlist.LASTLOOP DELETE emp WHERE deptno = deptlist(aDept);END LOOP;
Performance penalty Performance penalty for many “context for many “context switches”switches”
Conventional Bind
Copyright 2000-2005 Steven Feuerstein - Page 58
Enter the “Bulk Bind”
Oracle server
PL/SQL Runtime Engine SQL Engine
PL/SQL blockProcedural statement executor
SQL statement executor
FORALL aDept IN deptlist.FIRST.. deptlist.LAST DELETE emp WHERE deptno = deptlist(aDept);
Much less overhead for Much less overhead for context switchingcontext switching
Copyright 2000-2005 Steven Feuerstein - Page 59
Use the FORALL Bulk Bind Statement
Instead of the individual DML operations, you can do this:
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.
PROCEDURE remove_emps_by_dept (deptlist dlist_t)ISBEGIN FORALL aDept IN deptlist.FIRST..deptlist.LAST DELETE FROM emp WHERE deptno = deptlist(aDept);END;
Copyright 2000-2005 Steven Feuerstein - Page 60
CREATE OR REPLACE PROCEDURE process_emps (deptno_in IN dept.depno%TYPE)IS TYPE three_cols_rt IS RECORD ( empno emp.empno%TYPE, ename emp.ename%TYPE, hiredate emp.hiredate%TYPE); TYPE three_cols_tt IS TABLE OF three_cols_rt INDEX BY PLS_INTEGER; three_cols_t three_cols_tt;BEGIN SELECT empno, ename, hiredate BULK COLLECT INTO three_cols_t FROM emp WHERE deptno = deptno_in; ...END;
Use BULK COLLECT INTO for Queries
CREATE OR REPLACE PROCEDURE process_emps (deptno_in IN dept.depno%TYPE)IS TYPE numTab IS TABLE OF NUMBER; TYPE charTab IS TABLE OF VARCHAR2(12); TYPE dateTab IS TABLE OF DATE; enos numTab; names charTab; hdates dateTab;BEGIN SELECT empno, ename, hiredate BULK COLLECT INTO enos, names, hdates FROM emp WHERE deptno = deptno_in; FOR i IN enos.FIRST..enos.LAST LOOP do_stuff (enos(i), names(i), hiredates(i)); END LOOP;END;
Oracle9i R2 supports fetching into a collection of
records.
Oracle8i requires fetching into individual collections of
scalars.
bulkcoll.sql
Copyright 2000-2005 Steven Feuerstein - Page 61
Limit the number of rows returned by BULK COLLECT
CREATE OR REPLACE PROCEDURE bulk_with_limit (deptno_in IN dept.deptno%TYPE)IS CURSOR emps_in_dept_cur IS SELECT * FROM emp WHERE deptno = deptno_in;
TYPE emp_tt IS TABLE OF emp%ROWTYPE; emps emp_tt;BEGIN OPEN three_cols_cur; LOOP FETCH emps_in_dept_cur BULK COLLECT INTO emps LIMIT 100;
EXIT WHEN emps.COUNT = 0;
process_emps (emps); END LOOP;END bulk_with_limit;
Use the LIMIT clause with the INTO to manage the amount
of memory used with the BULK COLLECT operation.
WARNING!
BULK COLLECT will not raise NO_DATA_FOUND if no rows
are found.
Best to check contents of collection to confirm that something was retrieved.
bulklimit.sql
Copyright 2000-2005 Steven Feuerstein - Page 62
Combining FORALL & BULK COLLECT
FUNCTION remove_emps_by_dept (deptlist dlist_t) RETURN enolist_tIS 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;
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.
bulk_rowcount.sql
Copyright 2000-2005 Steven Feuerstein - Page 63
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 1bulktiming.sql
Copyright 2000-2005 Steven Feuerstein - Page 64
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.
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 65
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 ) RETURNING BULK COLLECT INTO enames; ...END;
Notice that empnos_in is indexed, but enames
is not.
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 66
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;
EXECUTE IMMEDIATE 'SELECT * FROM emp_' || loc_in BULK COLLECT INTO l_employees;END;
With Oracle9iR2 you can also fetch into collections of
records.
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 67
Better Exception Handlingfor 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 SAVE EXCEPTIONS INSERT INTO book values (books_in(indx));EXCEPTION WHEN BULK_ERRORS THEN FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT LOOP log_error (SQL%BULK_EXCEPTIONS(indx)); END LOOP;END;
Allows processing of all rows, even after an error
occurs.
New cursor attribute, a pseudo-
collection
bulkexc.sql
Oracle9i
Copyright 2000-2005 Steven Feuerstein - Page 68
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.sqlcfl_to_bulk.sql
Copyright 2000-2005 Steven Feuerstein - Page 69
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.
Oracle10g
Copyright 2000-2005 Steven Feuerstein - Page 70
Using INDICES OF
It only processes the rows with row numbers matching the defined rows of the driving array.
Oracle10g
10g_indices_of.sql10g_indices_of2.sql
DECLARE TYPE employee_aat IS TABLE OF employee.employee_id%TYPE INDEX BY PLS_INTEGER; l_employees employee_aat; TYPE boolean_aat IS TABLE OF BOOLEAN INDEX BY PLS_INTEGER; l_employee_indices boolean_aat;BEGIN l_employees (1) := 7839; l_employees (100) := 7654; l_employees (500) := 7950; -- l_employee_indices (1) := TRUE; l_employee_indices (500) := TRUE; l_employee_indices (799) := TRUE -- FORALL l_index IN INDICES OF l_employee_indices BETWEEN 1 AND 500 UPDATE employee SET salary = 10000 WHERE employee_id = l_employees (l_index);END;
Copyright 2000-2005 Steven Feuerstein - Page 71
Using VALUES OF
It only processes the rows with row numbers matching the content of a row in the driving array.
Oracle10g
DECLARE TYPE employee_aat IS TABLE OF employee.employee_id%TYPE INDEX BY PLS_INTEGER; l_employees employee_aat;
TYPE values_aat IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER; l_employee_values values_aat;BEGIN l_employees (-77) := 7820; l_employees (13067) := 7799; l_employees (99999999) := 7369; -- l_employee_values (100) := -77; l_employee_values (200) := 99999999; -- FORALL l_index IN VALUES OF l_employee_values UPDATE employee SET salary = 10000 WHERE employee_id = l_employees (l_index);END;
10g_values_of.sql
Copyright 2000-2005 Steven Feuerstein - Page 72
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 73
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 ' || l_type || ' ' || l_name
'BEGIN ' || l_proc_name || ' (' || l_parameters || '); END;'
Copyright 2000-2005 Steven Feuerstein - Page 74
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 75
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?
Oracle8i
Copyright 2000-2005 Steven Feuerstein - Page 76
NDS or DBMS_SQL: Which is best?
Reasons to go with NDS:– Ease of use– Performance (usually
faster)– Works with all SQL
datatypes (including user-defined object and collection types)
– Fetch into records and collections of records
Why You'd Use DBMS_SQL:– Method 4 Dynamic SQL– DESCRIBE columns of cursor– SQL statements larger than
32K– Better reuse of parsed SQL
statements -- persistent cursor handles!
– Available from client-side PL/SQL
Bottom line: NDS should be your first choice.
Copyright 2000-2005 Steven Feuerstein - Page 77
Some examples of "typical" dynamic SQL
Dynamic DDL– Simply execute a string; no
information is returned or "bound"
Dynamic queries– Single and multiple rows
Dynamic DML– You will often want to bind in
variable values
dropwhatever.spcreind81.spsettrig.sp
tabcount81.sftabcount.sfshowcol*.sp
updnval_with_eh.sp
Copyright 2000-2005 Steven Feuerstein - Page 78
Quiz!
PROCEDURE process_lineitem ( line_in IN INTEGER)ISBEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 22045 THEN process_line22045; END IF;END;
What's wrong with this code?
How would you fix it?
Copyright 2000-2005 Steven Feuerstein - Page 79
From 22,000 lines of code to 1!
Identify the pattern and resolve it either with reusable modules or dynamic abstractions.
PROCEDURE process_lineitem ( line_in IN INTEGER)ISBEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 22045 THEN process_line22045; END IF;END;
PROCEDURE process_lineitem ( line_in IN INTEGER)ISBEGIN EXECUTE IMMEDIATE 'BEGIN process_line'|| line_in ||'; END;';END;
dynplsql.txt
Copyright 2000-2005 Steven Feuerstein - Page 80
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.
Oracle8i
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 81
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.spdynplsql_nolocal.sql
Copyright 2000-2005 Steven Feuerstein - Page 82
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 injectiondynvar81.pkgdyncalc.pkg
code_injection.sql
Copyright 2000-2005 Steven Feuerstein - Page 83
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.
ut_Setup
ut_TestMe
ut_Teardown
utPLSQL.testTest Engine
Your Test Package
Results Table
Test for Nulls
Invalid ID
Valid ID
Start date too late
End date too early
Name unique
Report Results
1
2
34
Assert EQ
Assert NULL
Assert EqTable
Assertion API
5
http://utplsql.sourceforge.net/http://utplsql.oracledeveloper.nl/
Copyright 2000-2005 Steven Feuerstein - Page 84
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 85
1. Write and verify the static block code.
Here is a static program to parse a string of directories for the path list.
PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim)IS v_loc PLS_INTEGER; v_startloc PLS_INTEGER := 1; v_item VARCHAR2 (2000);BEGIN dirs.DELETE; LOOP v_loc := INSTR (str, delim, v_startloc);
IF v_loc = v_startloc 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;
filepath.pkg
Copyright 2000-2005 Steven Feuerstein - Page 86
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);BEGIN dirs.DELETE; LOOP v_loc := INSTR (str, delim, v_startloc);
IF v_loc = v_startloc 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;
Dynamic code
Bind variable
Copyright 2000-2005 Steven Feuerstein - Page 87
3a. Convert from static to dynamic block.
Assign the complex string to a variable.
Makes it easier to report errors and debug.
dynblock := 'DECLARE v_loc PLS_INTEGER; v_start PLS_INTEGER := 1; v_item ' || datatype || '; BEGIN ' || collname || '.DELETE; IF :str IS NOT NULL THEN LOOP v_loc := INSTR (:str, :delim, v_start); IF v_loc = v_startloc THEN v_item := NULL; ELSIF v_loc = 0 THEN v_item := SUBSTR (:str, v_start); ELSE v_item := SUBSTR (:str, v_start, v_loc - v_start); 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 88
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 89
4. Build in solid error handling from the start.
In your error handler, consider calling these functions to provide useful information:
EXCEPTION WHEN OTHERS THEN disperr (dynblock); RAISE; END parse;
PROCEDURE disperr (str IN VARCHAR2)ISBEGIN DBMS_OUTPUT.PUT_LINE ('Compilation/Execution Error:'); DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_STACK); DBMS_OUTPUT.PUT_LINE ('In:'); DBMS_OUTPUT.PUT_LINE (str); DBMS_OUTPUT.PUT_LINE ('Backtrack Error Stack:'); DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_ERROR_BACKTRACE); DBMS_OUTPUT.PUT_LINE ('Call Stack:'); DBMS_OUTPUT.PUT_LINE (DBMS_UTILITY.FORMAT_CALL_STACK);END;
Display full error message.
Show line number on which error was
raised.Oracle10g only.
Show execution call stack.
Copyright 2000-2005 Steven Feuerstein - Page 90
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 91
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.spintab9i.spintab9i.tst
Copyright 2000-2005 Steven Feuerstein - Page 92
Pseudo-code flow for DBMS_SQL implementation
BEGIN FOR each-column-in-table LOOP add-column-to-select-list; END LOOP;
DBMS_SQL.PARSE (cur, select_string, DBMS_SQL.NATIVE);
FOR each-column-in-table LOOP DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype); END LOOP;
fdbk := DBMS_SQL.EXECUTE (cur); LOOP fetch-a-row; FOR each-column-in-table LOOP DBMS_SQL.COLUMN_VALUE (cur, nth_col, val); END LOOP; END LOOP;END;
Build the SELECT list
Define each column
Extract each value
Parse the variable SQL
Execute the query
Lots of code, but relatively straightforwardintab.sp
Copyright 2000-2005 Steven Feuerstein - Page 93
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.
EXECUTE IMMEDIATE 'SELECT ' || col_list || ' FROM ' || table_in || ' WHERE ' || where_clauseINTO col1, col2 ... ?
Thiswon'twork:
EXECUTE IMMEDIATE 'SELECT ' || col_list || ' FROM ' || table_in || ' WHERE ' || where_clauseINTO || into_list;
How about this?
It looks like, it must be...dynamic PL/SQL!
Copyright 2000-2005 Steven Feuerstein - Page 94
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.
BEGIN l_block := 'BEGIN FOR rec IN (' || query_string || ') LOOP DBMS_OUTPUT.PUT_LINE (' || concatenated_values || '); END LOOP; END;'; EXECUTE IMMEDIATE l_block;END;
No need for INTO or USING clauses
Construct a static query inside a CFL
Concatenate all retrieved values into single string
expression.
It's all one big PL/SQL block
intab9i.sp
Copyright 2000-2005 Steven Feuerstein - Page 95
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 96
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 97
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 , l_lines , l_lines.FIRST , l_lines.LAST , TRUE , DBMS_SQL.native ); DBMS_SQL.close_cursor (l_cur); compile_from_file.sql
You can specify a subset of lines in
the array.
Copyright 2000-2005 Steven Feuerstein - Page 98
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.pkgdesccols.tst
Copyright 2000-2005 Steven Feuerstein - Page 99
Table Functions
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.
SELECT c.name, Book.name, Book.author, Book.abstract FROM Catalogs c, TABLE (rankings_pkg.most_popular (c.cat)) Book;
Copyright 2000-2005 Steven Feuerstein - Page 100
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 101
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 102
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 103
Write the function
Obviously, this is very much application-specific.
The point to keep in mind is that your logic can get just as wild and crazy as necessary to create the data you need.
CREATE OR REPLACE FUNCTION pet_family ( dad_in IN pet_t, mom_in IN pet_t) RETURN pet_ntIS l_count PLS_INTEGER; retval pet_nt := pet_nt ();BEGIN retval.EXTEND (2); retval (retval.LAST) := dad_in; retval (retval.LAST) := mom_in;
IF mom_in.breed = 'RABBIT' THEN l_count := 12; ELSIF mom_in.breed = 'DOG' THEN l_count := 4; ELSIF mom_in.breed = 'KANGAROO' THEN l_count := 1; END IF;
FOR indx IN 1 .. l_count LOOP retval.EXTEND; retval (retval.LAST) := pet_t ('BABY' || indx , mom_in.breed, SYSDATE - indx); END LOOP;
RETURN retval;END pet_family; pet_family.sql
Copyright 2000-2005 Steven Feuerstein - Page 104
Call the function inside an SQL query
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.
SELECT pets.NAME, pets.dob FROM TABLE (pet_family (pet_t ('Hoppy', 'RABBIT', SYSDATE-450) , pet_t ('Hippy', 'RABBIT', SYSDATE-300) ) ) pets;
pet_family.sqltabfunc_scalar.sql
Copyright 2000-2005 Steven Feuerstein - Page 105
Return a cursor variable from the function
CREATE OR REPLACE FUNCTION pet_family_cv RETURN sys_refcursorIS retval sys_refcursor;BEGIN OPEN retval FOR SELECT * FROM TABLE (pet_family (pet_t ('Hoppy', 'RABBIT', SYSDATE) , pet_t ('Hippy', 'RABBIT', SYSDATE) ) );
RETURN retval;END pet_family_cv;
pet_family.sql
DECLARE TYPE pet_r IS RECORD ( NAME VARCHAR2 (60), breed VARCHAR2 (100), dob DATE);
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 106
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;
tabfunc_streaming.sql
Copyright 2000-2005 Steven Feuerstein - Page 107
About pipelined table functions
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.
CREATE FUNCTION StockPivot (p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED
Copyright 2000-2005 Steven Feuerstein - Page 108
Piping rows out from a pipelined function
CREATE FUNCTION stockpivot (p refcur_pkg.refcur_t) RETURN tickertypeset 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'; out_rec.price := in_rec.openprice;
PIPE ROW (out_rec); END LOOP; CLOSE p;
RETURN;END;
tabfunc_setup.sqltabfunc_pipelined.sql
Add PIPELINED keyword to header
Pipe a row of data back to calling block or query
RETURN...nothing at all!
Copyright 2000-2005 Steven Feuerstein - Page 109
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 110
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 111
Examples of Parallelized Functions
PARALLEL_ENABLE ( Partition p_input_rows BY ANY )
CREATE OR REPLACE FUNCTION Aggregate_Xform ( p_input_rows in My_Types.cur_t) RETURN My_Types.dept_sals_tab PIPELINED
CLUSTER P_INPUT_ROWS BY (dept) PARALLEL_ENABLE ( PARTITION p_input_rows BY HASH (dept) )
ORDER p_input_rows BY (c1, c2) PARALLEL_ENABLE ( PARTITION p_input_rows BY RANGE (c1) )
with
with
with
Simplest form, results don't vary from order in which function gets input rows.
All rows for a given department must go to the same slave, and rows are delivered consecutively.
Rows are delivered to a particular slave as directed by partition... and will be locally sorted by that slave.
Copyright 2000-2005 Steven Feuerstein - Page 112
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 113
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 114
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!!!!
Oracle9i Release 2
Copyright 2000-2005 Steven Feuerstein - Page 115
Working with Database Directories
A Directory is a database "object", define with a CREATE statement.– You need CREATE ANY DIRECTORY privilege.
utlfile_92.sql
CREATE OR REPLACE DIRECTORY ERROR_LOG AS '/tmp/apps/log';
SELECT owner, directory_name, directory_path FROM ALL_DIRECTORIES;
View those directories to which you have
access.
GRANT READ ON DIRECTORY error_log TO SCOTT;Grant READ or WRITE privileges on a
directory.
Copyright 2000-2005 Steven Feuerstein - Page 116
Copy a File
You can specify an OS directory or a database object of type DIRECTORY (as shown above)
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 || '.zip' );END;
fcopy.sqlfileIO92.pkg
Copyright 2000-2005 Steven Feuerstein - Page 117
Remove a File
If no error is raised, then you deleted successfully
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 removeEND;
fremove.sqlfileIO92.pkg
Copyright 2000-2005 Steven Feuerstein - Page 118
Rename/move a File
You specify target location and file name
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 THEN ... Deal with failure to renameEND;
frename.sqlfileIO92.pkg
Copyright 2000-2005 Steven Feuerstein - Page 119
Obtaining attributes of a file
CREATE OR REPLACE FUNCTION flength ( location_in IN VARCHAR2, file_in IN VARCHAR2) RETURN PLS_INTEGERIS TYPE fgetattr_t IS RECORD ( fexists BOOLEAN, file_length PLS_INTEGER, block_size PLS_INTEGER ); fgetattr_rec fgetattr_t;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 ); RETURN fgetattr_rec.file_length;END flength;
How big is a file? What is its block size? Does the file exist?
All valuable questions. All answered with a call
to UTL_FILE.FGETATTR.
flength.sqlfileIO92.pkg
Copyright 2000-2005 Steven Feuerstein - Page 120
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
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.
Long-standing challenge in PL/SQL:
How can I find the line number on which an error was raised in PL/SQL?
But in Oracle10g, we have "back trace"!
Copyright 2000-2005 Steven Feuerstein - Page 121
Letting the error go unhandled…
CREATE OR REPLACE PROCEDURE proc1 ISBEGIN 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 ISBEGIN DBMS_OUTPUT.put_line ('calling proc2'); proc2;END;/
ERROR at line 1:ORA-01403: no data foundORA-06512: at "SCOTT.PROC1", line 7ORA-06512: at "SCOTT.PROC2", line 8ORA-06512: at "SCOTT.PROC3", line 5ORA-06512: at line 3
Backtrace.sql
Copyright 2000-2005 Steven Feuerstein - Page 122
Displaying the “error stack” inside PL/SQL
CREATE OR REPLACE PROCEDURE proc3ISBEGIN DBMS_OUTPUT.put_line ('calling proc2'); proc2;EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line ( DBMS_UTILITY.FORMAT_ERROR_STACK);END;/
SQL> exec proc3calling proc2calling proc1running proc1ORA-01403: no data foundbacktrace.sql
Copyright 2000-2005 Steven Feuerstein - Page 123
Displaying the contents of BACKTRACE
CREATE OR REPLACE PROCEDURE proc3ISBEGIN 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 proc3calling proc2calling proc1running proc1Error stack at top level:ORA-06512: at "SCOTT.PROC1", line 5ORA-06512: at "SCOTT.PROC2", line 7ORA-06512: at "SCOTT.PROC3", line 5
backtrace.sqlbt.pkg
Copyright 2000-2005 Steven Feuerstein - Page 124
The BACKTRACE stack with re-RAISEs
CREATE OR REPLACE PROCEDURE proc1 ISBEGIN …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;/CREATE OR REPLACE PROCEDURE proc3 ISBEGIN 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); bt.show (DBMS_UTILITY.format_error_backtrace);END;/
SQL> exec proc3calling proc2calling proc1running proc1Error stack in block where raised:ORA-06512: at "SCOTT.PROC1", line 5
Error stack at top level:ORA-06512: at "SCOTT.PROC1", line 11ORA-06512: at "SCOTT.PROC2", line 7ORA-06512: at "SCOTT.PROC3", line 5
Program owner = SCOTTProgram name = PROC1Line number = 11
Copyright 2000-2005 Steven Feuerstein - Page 125
The BACKTRACE stack with new RAISE
CREATE OR REPLACE PROCEDURE proc1ISBEGIN 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;/
CREATE OR REPLACE PROCEDURE proc2ISBEGIN DBMS_OUTPUT.put_line ('calling proc1'); proc1;EXCEPTION WHEN OTHERS THEN RAISE VALUE_ERROR;END;/
SQL> exec proc3calling proc2calling proc1running proc1Error stack in block where raised:ORA-06512: at "SCOTT.PROC1", line 5
Error stack at top level:ORA-06512: at "SCOTT.PROC2", line 9ORA-06512: at "SCOTT.PROC3", line 5
Program owner = SCOTTProgram name = PROC2Line number = 9
Copyright 2000-2005 Steven Feuerstein - Page 126
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 length.
– You can set the buffer size to UNLIMITED.
SET SEVEROUTPUT ON SIZE UNLIMITED
32K!
Copyright 2000-2005 Steven Feuerstein - Page 127
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.
Oracle10g
Some graphics and content courtesy of Oracle Magazine.
Copyright 2000-2005 Steven Feuerstein - Page 128
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 129
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 130
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 131
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 132
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 133
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 134
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.
Oracle10g
CALL utl_recomp.recomp_serial ('SCOTT'); CALL utl_recomp.recomp_parallel ('SCOTT', 4); recompile.sql
Copyright 2000-2005 Steven Feuerstein - Page 135
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.
Oracle10g
Copyright 2000-2005 Steven Feuerstein - Page 136
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 => '[email protected]' ,recipients => '[email protected], [email protected]' ,cc => '[email protected]' ,bcc => '[email protected]' ,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 137
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 => '[email protected]' ,recipients => '[email protected], [email protected]' ,cc => '[email protected]' ,bcc => '[email protected]' ,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 138
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 139
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 140
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.