Anda di halaman 1dari 269

Achieving PL/SQL Excellence

Oracle PL/SQL
Advanced Techniques
Oracle7 thru Oracle8i
Steven Feuerstein
www.StevenFeuerstein.com
www.Quest.com
www.OReilly.com
and contributions most excellent from Bill Pribyl and Dick Bolz
PL/SQL Advanced Techniques - page 1

Objective & Outline

Objective
Expand your knowledge and awareness of important and new
features of the PL/SQL language.

Outline

Building with Packages (Oracle7+)


PL/SQL Collections (Oracle7 and Oracle8+)
Cursor Variables (Oracle7 and Oracle8+)
Dynamic SQL: DBMS_SQL and Native Dynamic SQL (8i)
Calling Java from PL/SQL (Oracle8i) and C (Oracle8)
Oracle Advanced Queuing with DBMS_AQ (Oracle8)
Managing Large Objects with DBMS_LOB (Oracle8)
Other Oracle8i New Features
Autonomous Transactions (Oracle8i)
Invoker Rights Model (Oracle8i)
Row Level Security: DBMS_RLS
PL/SQL Advanced Techniques - page 2

Software Used in Training

PL/Vision: a library of packages installed on top of PL/SQL.


PL/Vision Lite - use it, copy, change it for free -- unless you build
software to be sold commercially.
Active PL/SQL Knowledge Base: contains PL/Vision Professional, the
fully supported and enhanced version.

Demonstration scripts executed in the training can be


found on the RevealNet PL/SQL Pipeline:
http://www.revealnet.com/Pipelines/PLSQL/index.htm
Archives surfboard, Miscellaneous, PL/SQL Seminar Files
See filedesc.doc for a listing of many of the files.

plsql_ides.txt

The PL/SQL IDE (Integrated Development Environment).


You no longer have to use SQL*Plus and a crude editor! Choose from
among the many listed in plsql_ides.txt.
PL/SQL Advanced Techniques - page 3

Achieving PL/SQL Excellence

Building with
PL/SQL Packages

Overview

Initialization section

Overloading

PL/SQL Advanced Techniques - page 4

What is a Package?

A collection of code elements, from procedures and


functions to TYPE, variable and cursor declarations.
Single-most important structure within PL/SQL, and almost certainly
one of the most under-utilized.
Conceptually very simple, it can take some time to fully grasp the
implications and potential of the package.

The method of choice by Oracle and other software


developers for extending the PL/SQL language.
You will find packages in the database, in Oracle Developer/2000, in
Oracle Application Server.

Lets review some of the benefits of packages.


tmr.pkg
dbparm.pkg

PL/SQL Advanced Techniques - page 5

When to Build a Package

Join physically logically-related code.


Can lead to performance improvements.
Puts more structure and organization in your body of code.

custrules.pkg
insga.pkg

Improve transaction integrity by hiding data structures


behind the package interface.
Instead of writing SQL directly in your programs, you call the
packaged procedures and functions instead.
te_employee.pks
te_employee.pkb

Construct very flexible and usable utilities for developers.


There's a big difference between a bunch of separate programs and
a coherent, package-based "component".
watch.pkg

PL/SQL Advanced Techniques - page 6

Package Initialization

The initialization section is a block of code at the end of


the package body that is executed once per session, the
first time any package element is referenced.
The PL/SQL runtime engine determines when and if this code
should be run.

Program references
package element
the first time in
each session.

Does the
package have an
init section?

no

yes

Run initialization
code.

Complete request
for packaged
element.

PL/SQL Advanced Techniques - page 7

Package Initialization Structure

The initialization section:


Is defined after and outside of any programs
in the package.
Is not required. In fact, most packages you
build won't have one.
Can have its own exception handling
section.

Useful for:
Performing complex setting of default or
initial values.
Setting up package data which does not
change for the duration of a session.
Confirming that package is properly
instantiated.

PACKAGE BODY pkg


IS
PROCEDURE proc IS
BEGIN
END;
FUNCTION func RETURN
BEGIN
END;
BEGIN
END pkg;

BEGIN after/outside
of any program
defined in the pkg.

PL/SQL Advanced Techniques - page 8

Configure Session with Init. Section

An unusual package!
Specification contains
only variables.
Body contains only
initialization section.

PACKAGE sessinit
IS
show_lov CHAR(1);
show_toolbar CHAR(1);
printer VARCHAR2(60);
END sessinit;

Also a package with


many design flaws...
init.pkg
init.tst

PACKAGE BODY sessinit IS


/* No declared package elements at all! */
BEGIN
/* Get user preferences for this user. */
SELECT lov_flag, tb_flag, defprinter
INTO show_lov, show_toolbar, printer
FROM user_config
WHERE user_id = USER;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
/* No record for
show_lov
:=
show_toolbar :=
printer
:=

this user. */
'Y';
'Y';
'lpt1';

WHEN OTHERS
THEN
RAISE_APPLICATION_ERROR
(-20000,
'No profile for ' || USER);
END sessinit;
PL/SQL Advanced Techniques - page 9

Populate Collections

The PL/Vision Date package, PLVdate, employs several


PL/SQL tables to convert strings and to perform date
arithmetic.
This increases the flexibility of the date conversion process.
The datemgr.pkg file demonstrates the basic technique (and the
reliance on an initialization section) used to achieve this flexibility.

datemgr.pkg
dates.sql

BEGIN
fmts(1) := 'DD-MON-RR';
fmts(2) := 'DD-MON-YYYY';
fmts(3) := 'DD-MON';
fmts(4) := 'MM/DD';
...
fmts(9) := 'MM/DD/YYYY';
fmts(10) := 'MMDDYYYY';
fmts(11) := 'YYYYMMDD';
fmts(12) := 'RRMMDD';
fmt_count := 12;
END dt;

Initialization section
populates a
PL/SQL table.

PL/SQL Advanced Techniques - page 10

Program Overloading

When you overload programs, you give


two or more programs the same name.
You can overload modules in any declaration
section and in packages.

Overloading is a critical feature when


building comprehensive programmatic
interfaces (APIs) or components using
packages.

myproc
myproc
myproc

If you want others to use your code, you need


to make that code as smart and as easy to use
as possible.
Overloading transfers the "need to know" from
the user to the overloaded program.
PL/SQL Advanced Techniques - page 11

Overloading in PL/SQL Built-ins

PL/SQL uses overloading in many common functions.


You just probably never gave it a thought, and took functions like
TO_CHAR and TO_DATE totally for granted.
But Oracle couldn't offer that level of convenience without overloading.

date_string := TO_CHAR (SYSDATE, 'MMDDYY');


number_string := TO_CHAR (10000);

Without overloading, you would have to deal with something


like this:
date_string :=
TO_CHAR_FROM_DATE (SYSDATE, 'MMDDYY');
number_string :=
TO_CHAR_FROM_NUMBER (10000);
PL/SQL Advanced Techniques - page 12

How Overloading Works

For two or more modules to be overloaded, the compiler must be able to


distinguish between the two calls at compile-time. There are two different
"compile times":
1. When you compile the package or block containing the overloaded code.
2. When you compile programs that use the overloaded code.

Distinguishing characteristics:
The formal parameters of overloaded modules must differ in number, order or
datatype family (CHAR vs. VARCHAR2 is not different enough).
The programs are of different types: procedure and function.

Undistinguishing characteristics:
Functions differ only in their RETURN datatype.
Arguments differ only in their mode (IN, OUT, IN OUT).
Their formal parameters differ only in datatype and the datatypes are in the same
family.

PL/SQL Advanced Techniques - page 13

Examples of Invalid Overloadings


PACKAGE too_similar
IS
PROCEDURE calc (reg_in IN CHAR);
PROCEDURE calc (reg_in IN VARCHAR2);
END too_many_cals;

which one?

Parameter
data types
cause conflict.

too_similar.calc ('123');

PACKAGE only_returns
IS
FUNCTION func1 (val IN VARCHAR2) RETURN DATE;
FUNCTION func1 (val IN VARCHAR2) RETURN VARCHAR2;
END only_returns;

which one?

DBMS_OUTPUT.PUT_LINE (only_returns.func1 (v_value));

PACKAGE param_modes
IS
PROCEDURE proc1 (val IN VARCHAR2);
PROCEDURE proc1 (val IN OUT VARCHAR2);
END param_modes;

which one?

Only difference
is function
RETURN type.

Only difference
is parameter
mode.

param_modes.proc1 (v_value);
PL/SQL Advanced Techniques - page 14

Overload Wherever Possible

Supporting Many Data Combinations


Apply the same action to different kinds or combinations of data. In this case,

the overloading does not provide a single name for different activities, so
much as providing different ways of requesting the same activity.
The DBMS_OUTPUT.PUT_LINE procedure illustrates this technique -- and the
PL/Vision p.l substitute does an even better job.

Fitting the Program to the User


To make your code as useful as possible, you may construct different
versions of the same program which correspond to different patterns of
use.

Overloading by Type, not Value


A less common application of overloading. You use the type of data and not
its value to determine which of the overloaded programs should be executed.

PL/SQL Advanced Techniques - page 15

A "Classic" Overloaded Package

Many different datatype combinations, allowing the user to pass data to the "display
engine" without writing "pre-processing" code.
PACKAGE p
IS
PROCEDURE l
(date_in IN DATE,
mask_in IN VARCHAR2 := Month DD, YYYY - HH:MI:SS PM');
PROCEDURE l (number_in IN NUMBER);
PROCEDURE l (char_in IN VARCHAR2);
PROCEDURE l (char_in IN VARCHAR2, number_in IN NUMBER);
PROCEDURE l
(char_in IN VARCHAR2, date_in IN DATE,
mask_in IN VARCHAR2 := 'Month DD, YYYY - HH:MI:SS PM');
p.sps
p.spb

PROCEDURE l (boolean_in IN BOOLEAN);


PROCEDURE l (char_in IN VARCHAR2, boolean_in IN BOOLEAN);
END p;
PL/SQL Advanced Techniques - page 16

Advantage of Extended Overloading


Minimal overloading means lots of
extra coding...

Extended overloadings are


more likely to meet user needs.

DBMS_OUTPUT.PUT_LINE
('So what is different?');

p.l ('So what is different?');

DBMS_OUTPUT.PUT_LINE
(TO_CHAR (SYSDATE,
'MM/DD/YY HH:MI:SS'));

p.l (SYSDATE);

DBMS_OUTPUT.PUT_LINE
(SQLERRM || ': ' ||
TO_CHAR (SQLCODE));

p.l (SQLERRM, SQLCODE);

IF print_report_fl
THEN
DBMS_OUTPUT.PUT_LINE ('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE ('FALSE');

p.l (print_report_fl);

END IF;
PL/SQL Advanced Techniques - page 17

Fitting the Program to the User


Writing "unnecessary" code? Time to overload!

A single piece of functionality, such as "display data" or "create


a file", can be applied or needed under very different
circumstances.

If you take these different circumstances into account when you


design your package specification, the user of your package can
benefit from writing less code.
Your code is a a more natural "fit" under a variety of
requirements.

In my experience, few developers are considerate enough of


their users to try to anticipate their needs.
If you want to write software that is admired,
appreciated...and taken completely for granted, think about
the way it will be used.
PL/SQL Advanced Techniques - page 18

Creating a File for a Quick Touch

Suppose a developer needs to create a file to be used as a "flag" in the operating system.
She doesn't care what's in it. It just needs to be present.
Here is the code required by UTL_FILE:

DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('tmp/flags', 'exists.flg', 'W');
UTL_FILE.PUT_LINE (fid, 'blah');
UTL_FILE.FCLOSE (fid);
END;

In other words, you have to declare the record to hold the file handle, even
though you are simply going to close the file immediately after opening it.

Of course, sometimes you will want to create a file and then perform
additional operations, so this is just the way it has to be, right? WRONG!

PL/SQL Advanced Techniques - page 19

Procedure and Function Overloaded

Why not overload a "create file" program so that you can pick the one that most closely fits your situation?

Consider the PLVfile package of PL/Vision.

Overloading of FCreate
PACKAGE PLVfile
IS
/* Procedure */
PROCEDURE fcreate
(file_in IN VARCHAR2,
line_in IN VARCHAR2 := NULL);
/* Function */
FUNCTION fcreate
(file_in IN VARCHAR2,
line_in IN VARCHAR2 := NULL)
RETURN UTL_FILE.FILE_TYPE;
END PLVfile;

Use as Function
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := PLVfile.fcreate
('temp.ini', v_user);
PLVfile.put_line
(fid, TO_CHAR (SYSDATE));

Use as Procedure
BEGIN
PLVfile.fcreate ('exists.flg');
END;
custrules.pkg

PL/SQL Advanced Techniques - page 20

"By-Type" Overloading

In some situations, the user does not need to pass data,


but the type of data.
For example, when you use DBMS_SQL to set up a dynamic
query, you must call the DEFINE_COLUMN procedure to define
the datatype of the Nth column in the cursor.

There are three ways to accomplish this:


Don't overload. Define a different program name for each
datatype.
Pass a string name of the datatype.
Pass a piece of data of the right type.

PL/SQL Advanced Techniques - page 21

Options for Specifying Column Type

Don't even bother overloading...

BEGIN
DBMS_SQL.DEFINE_INTEGER_COLUMN (cur, 1);
DBMS_SQL.DEFINE_VARCHAR2_COLUMN (cur, 2, 30);

Pass a literal value...

BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, 'NUMBER');
DBMS_SQL.DEFINE_COLUMN (cur, 2, 'STRING', 30);

Pass a named constant...

So many program
names to remember!

Nasty
hard-coding...

Lotsa typing,
lotsa names...

BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, DBMS_SQL.NUMBER_TYPE);
DBMS_SQL.DEFINE_COLUMN (cur, 2, DBMS_SQL.VARCHAR2_TYPE, 30);

Now let's look at two examples of overloading by datatype...


DBMS_SQL.DEFINE_COLUMN
PLVgen.func
PL/SQL Advanced Techniques - page 22

Defining Dynamic SQL Columns

The DBMS_SQL.DEFINE_COLUMN procedure defines the datatype of a column.


To make it easier to accomplish this task, you only need to pass a value -- any value -- of the correct type.
The three code blocks below are equivalent, from the perspective of DBMS_SQL.DEFINE_COLUMN.

BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, 1);
DBMS_SQL.DEFINE_COLUMN (cur, 2, 'a', 30);
BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, DBMS_UTILITY.GET_TIME);
DBMS_SQL.DEFINE_COLUMN (cur, 2, USER, 30);
BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, v_empno);
DBMS_SQL.DEFINE_COLUMN (cur, 2, v_ename, 30);
PL/SQL Advanced Techniques - page 23

Generating Functions by Value

In PLVgen, the user indicates the type of function to be generated by providing a value.
The particular value itself is of no importance. Any number, any date, any string, any
Boolean will do.

PACKAGE PLVgen
IS
PROCEDURE func (name_in IN VARCHAR2, type_in IN VARCHAR2);
PROCEDURE func (name_in IN VARCHAR2, type_in IN NUMBER);
PROCEDURE func (name_in IN VARCHAR2, type_in IN DATE);
SQL> exec plvgen.func
('last_date', SYSDATE)
SQL> exec plvgen.func
('total_salary', 1)

A date function, please!

A number function, please!

PL/SQL Advanced Techniques - page 24

The Frustrations of Overloading

Watch out! An overloading can compile successfully, but


you might later found out that you cannot actually call any of
the overloaded programs.
PACKAGE profits
IS
PROCEDURE calc (comp_id_IN IN NUMBER);
PROCEDURE calc (comp_id_IN IN company.comp_id%TYPE);
END;

In the above example, I rely on an anchored type (%TYPE)


to establish the datatype of the second calcs parameter.
When I compile profits, PL/SQL does not sense a conflict with
above overloading even though comp_id is a numeric column.
PL/SQL Advanced Techniques - page 25

Quiz! Nuances of Overloading


PACKAGE sales
IS
PROCEDURE calc_total (zone_in IN VARCHAR2);
PROCEDURE calc_total (reg_in IN VARCHAR2);

END sales;
Can I overload two programs which have parameters that differ only by name, like
calc_totals shown above?
If not, why not?
If so, how would you do it? (Don't peek at the next page!)

sales.pkg

BEGIN
sales.calc_total ('NORTHWEST');

sales.calc_total ('ZONE2');
END;
PL/SQL Advanced Techniques - page 26

Using Named Notation


<formal parameter name> => <expression>

Explicit association between the formal parameter (the "name")


with the actual parameter (the "value").

Advantages of named notation include:


Code is more "self-documenting". This is especially useful when working
with infrequently used built-in programs.
You can skip over (not specify values for) any IN parameters that have
default values. That way you don't have to know and pass default values.

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)');

namednot.sql

PL/SQL Advanced Techniques - page 27

Achieving PL/SQL Excellence

PL/SQL
Collections

Collections are single-dimensioned lists of information.

Three types of collections:


Index-by tables (Oracle7 only, originally called PL/SQL tables)
Nested tables (Oracle8 and above)
Variable arrays (VARRAYs, Oracle8 and above)
PL/SQL Advanced Techniques - page 28

When to Use Collections

Maintain any kind of list of related information for use in your


programs.

Emulate bi-directional cursors, which are otherwise not


supported in PL/SQL

Cache data in session-level memory for faster access.

Build hash tables (custom indexing structures).

Improve query performance by avoiding joins.

Avoid mutating table errors in database triggers.


PL/SQL Advanced Techniques - page 29

Index-By Tables
TYPE <table_type> IS TABLE OF <datatype>
INDEX BY BINARY_INTEGER;
DECLARE
TYPE inmem_emp_t IS TABLE OF emp%ROWTYPE
INDEX BY BINARY_INTEGER;
emp_copy inmem_emp_t;

Characteristics of an index-by table:


Unbounded
Practically speaking. Valid row numbers range: -2,147,483,647 to 2,147,483,647
You will not actually create tables this large. Instead, this broad range allows you to employ the row number as an
intelligent key.

Sparse
Data does not have to be stored in consecutive rows of information.

Homogeneous
Data in each row has the same structure.

Available only in PL/SQL

PL/SQL Advanced Techniques - page 30

Index_by Tables
TYPE
declaration

PACKAGE BODY family IS


TYPE child_list_type IS
TABLE OF VARCHAR2 (30)
INDEX BY BINARY_INTEGER;

Variable
declaration

children
6306

Barbara Anne

6412

Gary Richard

6904

Lisa Marie

children

child_list_type;

children (6810) := Adam Russell;


children (6904) := 'Lisa Nadezhka';

Component
Selection
kid := children (4);

Error:
NO_DATA_FOUND

children
6306

Barbara Anne

6412

Gary Richard

6810

Adam Russell

6904

Lisa Nadezhka
datemgr.pkg

PL/SQL Advanced Techniques - page 31

Nested Tables
[CREATE OR REPLACE] TYPE <table_type> IS
TABLE OF <datatype> [NOT NULL];
DECLARE
TYPE when_t IS TABLE OF DATE;
birthdays when_t;

Nested table characteristics


Homogeneous
Each row contains the same structure of data.

Unbounded, but only with explicit EXTEND requests


Practically speaking. Valid row numbers range: 1 to 2,147,483,647

Initially dense, but can become sparse if you DELETE inner rows
Available both in PL/SQL and SQL (as a column in a table)
The order of elements is not preserved in the database

PL/SQL Advanced Techniques - page 32

Nested Tables
CREATE OR REPLACE
TYPE child_table_type IS TABLE OF VARCHAR2 (30);
CREATE TABLE db_family (surname VARCHAR2 (30),
kids child_table_type)
NESTED TABLE kids STORE AS kids_ntab;

For ORACLE's use only


db_family
surname kids
BOLZ

Barbara Anne
Gary Richard
Lisa Marie

BOND
Eric Thomas
Max Richard

ntdemo.sql

PL/SQL Advanced Techniques - page 33

Variable Arrays
[CREATE OR REPLACE] TYPE <table_type> IS
VARRAY (N) OF <datatype> [NOT NULL];
DECLARE
TYPE numbers_t IS VARRAY (10) OF NUMBER;
salaries numbers_t;

Characteristics of variable arrays:


Homogeneous
Each row contains the same structure of data.

Bounded
Upper limit established when the TYPE is defined. Maximum value: 2,147,483,647

Dense
Never any gaps between defined rows, can be EXTENDed or TRIMmed.

Available both in PL/SQL and SQL (as a column in a table)


And the order of elements are preserved in the database.

Variable arrays are actually stored in the DB table

PL/SQL Advanced Techniques - page 34

Variable Arrays
CREATE OR REPLACE
TYPE child_va_type IS VARRAY (8) OF VARCHAR2 (30);
CREATE TABLE db_family (surname VARCHAR2 (30), kids child_va_type);

db_family
surname kids
BOLZ
BOND

1 Barbara Anne
2 Gary Richard
3 Lisa Marie
1 Eric Thomas
2 Max Richard
vademo.sql

PL/SQL Advanced Techniques - page 35

Defining Collections

First, you define the TYPE of the collection.


For index-by tables, this can only occur in a PL/SQL declaration
section. Best option: package specification.
For nested tables and VARRAYs, you can define the TYPE in the
database with a CREATE statement, or in a PL/SQL declaration
section.

Then you declare an instance of that type, a collection, from


the TYPE.
You can declare multiple collections from that TYPE.
CREATE OR REPLACE PACKAGE tabtypes
IS
TYPE integer_ibt IS TABLE OF INTEGER INDEX BY BINARY_INTEGER;
TYPE integer_nt IS TABLE OF INTEGER;
TYPE integer_vat IS VARRAY(10) OF INTEGER;
...
END tabtypes;
PL/SQL Advanced Techniques - page 36

Obtaining Collection Information


SELECT
FROM
WHERE
AND
AND
AND

A.attr_name || ' - ' || A.attr_type_name Attributes


all_coll_types T, all_type_attrs A
T.owner = USER
T.owner = A.owner
T.type_name IN ('NAMES_VT', 'TMRS_VT')
T.elem_type_name = A.type_name;

ALL_COLL_TYPES
The types you have created (or have access to) in the database

ALL_TYPE_ATTRS
Attributes of the data type used in the TYPE definition.
The code used to define the collection TYPE

There is no information in the data dictionary available for index-by tables.

colldd.sql

PL/SQL Advanced Techniques - page 37

Initializing Collections

Before you can use a collection, it must be initialized.


Index-by tables are initialized automatically, empty when declared.
Nested tables and VARRAYs are atomically null. You must initialize
them explicitly with a constructor.
DECLARE
TYPE numbers_t IS VARRAY (10) OF NUMBER;
salaries numbers_t := numbers_t (100, 200, 300);
BEGIN

TYPE defined in
PL/SQL

CREATE TYPE numbers_t IS VARRAY (10) OF NUMBER;


/
DECLARE -- Initialize the collection.
salaries numbers_t := numbers_t (100, 200, 300);
BEGIN

TYPE defined in
the database

CREATE TABLE employee_denorm (


employee_id INTEGER,
salary_history numbers_t);

Collection used
in a table
PL/SQL Advanced Techniques - page 38

Collections of Composites

Starting with Oracle 7.3, the homogeneous contents of an index-by table's row can be a record .
Can easily create an index-by table with the same structure as a database table by declaring a record with
%ROWTYPE.

Starting with Oracle8, the datatype for any of the collection types can also be an object.
But you cannot have nested composite datatypes.

DECLARE
TYPE comp_rectype IS RECORD
(comp_id company.company_id%TYPE, total_rev NUMBER);
TYPE comp_tabtype IS TABLE OF comp_rectype
INDEX BY BINARY_INTEGER;
comp_tab comp_tabtype;
BEGIN
comp_tab(1).comp_id := 1005;

Here we have a three


step process. Again,
consider putting
TYPEs in database or
packages.

PL/SQL Advanced Techniques - page 39

Sparse is Nice

The sparse characteristic of index-by tables and nested


tables can be put to good use.
In an index-by table, a row exists in the table only when a value is
assigned to that row. In this way, it is very similar to a database table.

Rows do not have to be defined sequentially.


You should not fill sequentially, unless the order in which items are
selected is of importance.
Instead, consider using the row value as "smart data" for your
application (primary key, order by date, etc.).

Especially handy when caching data from relational tables in


user memory.
In almost every case, your collections will contain a row's worth of
information.

PL/SQL Advanced Techniques - page 40

Transferring DB Table to Collection


CREATE OR REPLACE PACKAGE psemp
IS
TYPE emp_tabtype IS
TABLE OF emp%ROWTYPE
INDEX BY BINARY_INTEGER;
emp_tab emp_tabtype;
END;
CREATE OR REPLACE PACKAGE BODY psemp
IS
BEGIN
FOR rec IN (SELECT * FROM emp)
LOOP
emp_tab (rec.empno) := rec;
END LOOP;
END;
Initialization
section of
package

This package moves the entire


contents of the emp table into
its corresponding collection.

Some questions:
Why would I put this collection
table in a package?
When is the collection loaded
with the data?
What rows in that collection
are utilized?

psemp.pkg
psemp.tst

PL/SQL Advanced Techniques - page 41

Collection Gotchas
CREATE TYPE names_t IS TABLE OF VARCHAR2(30);
/
DECLARE
greedy_ceos names_t := names_t ();
BEGIN
greedy_ceos(1) := 'Hamilton, Jordan';
END;
/

Error -6533!
You've got to
EXTEND first!

EXTEND before assigning a value to a row.


Not necessary for index-by tables, but you must do it for VARRAYs and nested tables.

For index-by tables, you must reference existing rows or a NO_DATA_FOUND


exception is raised.
Use the EXISTS method to determine if a row existed.
For VARRAYs and nested tables, once extended, the row exists, even if you haven't assigned a
value explicitly.

PL/SQL Advanced Techniques - page 42

Handling Collection Gotchas


BEGIN
-- Extend by 10 since I know I will need that many.
-- Set the value of each new row to the contents of the first row.
salaries.EXTEND (10, salaries(salaries.FIRST));

You can EXTEND one or more rows.


Assign a default value with a second, optional argument.
Pre-extending a large number of rows in advance can
improve performance.

preextend.tst

Include a handler for NO_DATA_FOUND or use the EXISTS


method to avoid these exceptions.
BEGIN
IF salaries.EXISTS (v_employee_id)
THEN
-- We are OK.
ELSE
DBMS_OUTPUT.PUT_LINE ('Data for employee not available.');
END IF;
PL/SQL Advanced Techniques - page 43

Collection Methods

Obtain information about the collection

COUNT returns number of rows currently defined in the table.


EXISTS returns TRUE if the specified row is defined.
FIRST/LAST return lowest/highest numbers of defined rows.
NEXT/PRIOR return the closest defined row after/before the specified
row.
LIMIT tells you the max. number of elements allowed in a VARRAY.

Modify the contents of the collection


DELETE deletes one or more rows from the index-by table.
EXTEND adds rows to a nested table or VARRAY.
TRIM removes rows from a VARRAY.

The built-in package plitblm (PL/sql Index-TaBLe Methods)


defines these methods.
PL/SQL Advanced Techniques - page 44

The DELETE Method

You can delete one or more rows from a collection using


DELETE:
BEGIN
-- Delete all rows
myCollection.DELETE;
-- Delete one (the last) row
myCollection.DELETE (myCollection.LAST);
-- Delete a range of rows
myCollection.DELETE (1400, 17255);
END;

DELETE releases memory, but you may also want to call


DBMS_SESSION.FREE_UNUSED_USER_MEMORY.
PL/SQL Advanced Techniques - page 45

Navigating Through Collections

Use FIRST and NEXT to move from beginning to end.

Use LAST and PRIOR to move from end to beginning.


rowind PLS_INTEGER :=
birthdays.FIRST; -- birthdays.LAST
BEGIN
LOOP
EXIT WHEN rowind IS NULL;
DBMS_OUTPUT.PUT_LINE
(birthdays(rowind).best_present);
rowind := birthdays.NEXT (rowind); -- birthdays.PRIOR
END LOOP;
END;

PL/SQL Advanced Techniques - page 46

Using Collections Inside SQL

Nested tables and VARRAYs can be defined as columns of a


table and referenced directly within SQL.

You can also apply SQL operations to the contents of nested


tables and VARRAYs with these operators:
THE - Maps a single column value in a single row to a virtual
database table
CAST - Maps a collection of one type to a collection of another type
MULTISET - Maps a database table to a collection
TABLE - Maps a collection to a database table

Index-by tables are programmatic constructs only.


You cannot make a direct reference to an index-by table in SQL.
Instead, so do indirectly with a PL/SQL function.

PL/SQL Advanced Techniques - page 47

Using Collections inside SQL


SELECT column_value
FROM
TABLE (SELECT children FROM db_family
WHERE surname = 'BOLZ');
db_family
surname children
BOLZ

Barbara Anne
Gary Richard
Lisa Marie

BOND

column_value
Barbara Anne
Gary Richard
Lisa Marie

Eric Thomas
Max Richard
UPDATE TABLE
(SELECT children FROM db_family WHERE SURNAME = 'BOLZ)
SET
column_value = 'Lisa Nadezhka'
WHERE column_value = 'Lisa Marie');
db_family
surname children
BOLZ

Barbara Anne
Gary Richard
Lisa Nadezhka

...
PL/SQL Advanced Techniques - page 48

Using the THE Operator

Use THE to manipulate (retrieve, INSERT, UPDATE, DELETE) contents of a nested table in a
database table.
Can only use with nested tables, not VARRAYs or index-by tables.
Only accessible from within SQL statements in PL/SQL.

CREATE TYPE action_list_t IS TABLE OF VARCHAR2(100);


/
CREATE TABLE inflation_beater (
focus_area VARCHAR2(100),
activities action_list_t)
NESTED TABLE activities STORE AS activities_tab;
SELECT VALUE (act)
FROM THE (SELECT activities FROM inflation_beater
WHERE focus_area = 'FORTUNE 100') act;
UPDATE THE (SELECT activities FROM inflation_beater
WHERE focus_area = 'FORTUNE 100')
SET COLUMN_VALUE = 'DISBAND OSHA'
WHERE COLUMN_VALUE = 'SIDESTEP OSHA';

the.sql

PL/SQL Advanced Techniques - page 49

Using the TABLE and CAST Operators

Use CAST to convert a collection from one type to another, TABLE to convert a TYPE into a database
table.
Cannot use with index-by tables.
Useful when you would like to apply SQL operations against a PL/SQL collection (ie, one not stored in a database table).

DECLARE
nyc_devolution cutbacks_for_taxcuts :=
cutbacks_for_taxcuts ('Stop rat extermination programs',
'Fire building inspectors',
'Close public hospitals');
BEGIN
DBMS_OUTPUT.PUT_LINE (
'How to Make the NYC Rich Much, Much Richer:');
FOR rec IN (SELECT COLUMN_VALUE ohmy
FROM TABLE (CAST (nyc_devolution AS cutbacks_for_taxcuts)))
LOOP
DBMS_OUTPUT.PUT_LINE (rec.ohmy);
END LOOP;
cast.sql
END;
PL/SQL Advanced Techniques - page 50

Using the MULTISET Operator

MULTISET is the inverse of TABLE, converting a set of data (table, view, query) into a VARRAY or nested table.
Cannot use with index-by tables.
You can use MULTISET to emulate or transform relational joins into collections, with potential client-server performance impact.

DECLARE
CURSOR bird_curs IS
SELECT b.genus, b.species,
CAST(MULTISET(SELECT bh.country FROM bird_habitats bh
WHERE bh.genus = b.genus
AND bh.species = b.species)
AS country_tab_t)
FROM birds b;
Retrieves all detail
bird_row bird_curs%ROWTYPE;
information for the
BEGIN
master in one trip.
OPEN bird_curs;
FETCH bird_curs into bird_row;
END;
multiset.sql
PL/SQL Advanced Techniques - page 51

Referencing IB Tables inside SQL

You can't directly reference an index-by table's contents


inside SQL.

Instead, call functions that retrieve the table's data, but hide
the index-by table structure.

CREATE OR REPLACE PACKAGE ibtab IS


FUNCTION rowval (indx IN PLS_INTEGER) RETURN DATE;
PRAGMA RESTRICT_REFERENCES (rowval, WNPS, WNDS);
END;
CREATE OR REPLACE PACKAGE BODY ibtab IS
TYPE date_tab IS TABLE OF DATE INDEX BY BINARY_INTEGER;
hiredates date_tab;
FUNCTION rowval (indx IN PLS_INTEGER) RETURN DATE
IS BEGIN
RETURN hiredates (indx);
END;
END;

Make accessible
in SQL for
Oracle8 and
below.

ibtab_in_sql.sql

PL/SQL Advanced Techniques - page 52

Examples of Collections in Action

Emulation of bi-directional cursor operations

Avoid mutating table problems in database


triggers.

PL/SQL Advanced Techniques - page 53

Bi-Directional Cursor Emulation

Oracle does not yet support the ability to move back and
forth (and at random) through a cursor's result set.
A talked-about feature for Oracle9i -- nope, didn't make it!

Instead, deposit your data in a collection and then provide


programs to access that data in the necessary fashion.

This is particularly useful (read: efficient) when you need to


perform multiple passes against the data.
CREATE OR REPLACE PACKAGE bidir
IS
/* Iterate through rows in the result set */
PROCEDURE setRow (nth IN PLS_INTEGER);
FUNCTION getRow RETURN employee_plus%ROWTYPE;
PROCEDURE nextRow;
PROCEDURE prevRow;
END;

Notice that the


collection itself is
hidden.

bidir.pkg
bidir.tst

PL/SQL Advanced Techniques - page 54

The Mutating Table Problem

Database triggers can be attached to the SQL statement


and/or the individual row operations on a table.
Statement Level

UPDATE emp SET sal = 1000


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; it is "mutating".
So what are you supposed to do when a rowlevel operation needs to "touch" that table?

Note: in Oracle8i, you


can use autonomous
transactions to relax
restrictions
associated with
queries.

mutating.sql

PL/SQL Advanced Techniques - page 55

A Solution Based on Index-by Tables

Since you cannot perform the processing desired in the rowlevel 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 index-by table is an ideal repository for this reminder list.

1st row trigger fires


Nth row trigger fires

Writes to list

Work List
(PL/SQL Table)

Writes to list

Process data
in the list.

Statement Trigger
PL/SQL Advanced Techniques - page 56

An Example: Ranking Salespeople

A table holds the rankings based on the amount of annual


sales of salespeople within a department.
As the sales amount is updated in this table, the rankings must also
change to show the new standings for that department only.
Department ID

Salesperson ID

Sales Amount

Rank

1055

64333

74055.88

1055

65709

144533.91

1055

65706

109000.25

1047

70904

65011.25

"Deferred work" is not only necessary, but preferable.


By storing the salespersons department ids as they change, we then
know which departments to re-rank.
We might update more than one department within a statement so we
must be able to retain multiple department numbers.
PL/SQL Advanced Techniques - page 57

Trigger Logic Required


Row Level Trigger
CREATE OR REPLACE TRIGGER Rank_Sales_Rtrg
AFTER insert OR update OF sales_amt
ON rank_sales FOR EACH ROW
WHEN (OLD.sales_amt != NEW.sales_amt)
BEGIN
rank.add_dept (:new.dept_id);
END;

Doesn't fire unless


the sales_amt
is actually changed.

Statement Level Trigger


CREATE OR REPLACE TRIGGER Rank_Sales_Strg
AFTER INSERT OR UPDATE OR DELETE
ON rank_sales
BEGIN
rank.rank_depts;
END;

All details of the


ranking are hidden
in the package body.

ranking.pkg

PL/SQL Advanced Techniques - page 58

The Ranking Package


PACKAGE rank
IS
PROCEDURE add_dept (dept_id_in IN INTEGER);
PROCEDURE rank_depts;
END rank;
PACKAGE BODY rank
IS
in_process BOOLEAN := FALSE;

Table holds indicator


that department
needs re-ranking.

TYPE dept_tabtype IS TABLE OF BOOLEAN INDEX BY BINARY_INTEGER;


dept_tab dept_tabtype;
PROCEDURE add_dept (dept_id_in IN INTEGER) IS
BEGIN
IF NOT in_process
THEN
dept_tab (dept_id_in) := TRUE;
END IF;
END add_dept

Create row to indicate


department to be ranked.

PL/SQL Advanced Techniques - page 59

The Ranking Package, Continued


PROCEDURE rank_depts
IS
v_deptid PLS_INTEGER := dept_tab.FIRST;
BEGIN
IF NOT in_process
THEN
in_process := TRUE;
LOOP
EXIT WHEN v_deptid IS NULL;
perform_ranking (v_deptid);
v_deptid := dept_tab.NEXT (v_deptid);
END LOOP;
END IF;
in_process := FALSE;
dept_tab.DELETE;
END rank_dept;
END rank;

Avoid recursive
execution of logic

Row number is
department number.

Clean up for
next time.
PL/SQL Advanced Techniques - page 60

Which Collection Type Should I Use?

Index-by tables
Need to use in both Oracle7 and Oracle8 applications
Want to take advantage of sparse nature for "intelligent keys".

Nested tables
You want to store large amounts of persistent data in a column.
You want to use inside SQL.

VARRAYs

You want to preserve the order in which elements are stored.


Set of data is relatively small (avoid row chaining).
You want to use inside SQL.
You don't want to have to worry about sparseness.

PL/SQL Advanced Techniques - page 61

Tips for Using Collections

Wrap access to your collections.


In many cases, you will want to avoid direct access to (assigning
and retrieving) rows in your collections.
This will give you the flexibility to change your implementation.
You can also hide complex rules for setting the row number.

Get creative!
Don't always fill and use the index-by table sequentially.
If you can somehow translate your application data to an integer, it
can be used as a row number, and therefore offers indexed access.
Julian date formats and DBMS_UTILITY.GET_HASH_VALUE offer
two different methods.

PL/SQL Advanced Techniques - page 62

Achieving PL/SQL Excellence

Cursor Variables

PL/SQL Advanced Techniques - page 63

Architecture of Cursor Variables


With Hard-Coded Cursors

Hard-Coded
Hard-Coded
Cursor
Cursor

PGA

Result
Result
Set
Set

Shared
Global
Area

With Cursor Variables


Cursor
Cursor
Variable
Variable

Cursor
Cursor
Object
Object

Cursor
Cursor
Variable
Variable

Result
Result
Set
Set

A cursor variable points to an underlying cursor object in the


database.
The cursor object in turns points to (and keeps its place in) a result set.

The cursor variable can be passed between programs (even


between, say, a Java servlet and a PL/SQL stored procedure).
Static SQL only -- until Oracle8i.
PL/SQL Advanced Techniques - page 64

Benefits of Cursor Variables

Share cursor management between programs, even across


the client-server divide.
You don't have to pass the result sets of a cursor in order to allow
the client-side program to have direct access to the data in the result
set.
Oracle Developer 2.1 utilizes cursor variables when you choose to
construct a "base table block" around stored procedures instead of
a database table.

Share the same code across multiple, different queries.


Since the cursor name is no longer hard-coded, you can use a single
block of code (say, a reporting program) against different queries.
We will try out this technique at the end of the section.

PL/SQL Advanced Techniques - page 65

Cursor Variable Example


DECLARE
TYPE company_curtype
IS
REF CURSOR RETURN company%ROWTYPE;

Declare cursor variable


based on that type.

company_curvar company_curtype;

Declare a record
from cursor variable.

company_rec company_curvar%ROWTYPE;
BEGIN
OPEN company_curvar FOR
SELECT * FROM company;

Declare a variable
cursor TYPE.

OPEN cursor variable,


specifying the query.
FETCH from the
cursor variable.

FETCH company_curvar INTO company_rec;


CLOSE company_curvar;
END;

Close the
cursor variable.

PL/SQL Advanced Techniques - page 66

Explicit Cursors and Cursor Variables

Both hard-coded cursors and cursor variables work with


static SQL.
The SQL is fixed at compile-time.
The difference is that with cursor variables, you get to decide which
static query is opened.

Many cursor operations are the same:


Close a variable cursor with the same syntax as that for static
cursors.
Use cursor attributes (%ISOPEN, %FOUND, %NOTFOUND,
%ROWCOUNT) with cursor variables (as of Release 2.3).
Fetch data from the cursor result set through a cursor variable with
the same syntax as that of static cursors.

Lets focus on the new and different capabilities of cursor


variables.
PL/SQL Advanced Techniques - page 67

Declaring Cursor Types and Variables

Cursors are declared in two steps, just like programmerdefined records and PL/SQL tables.
1. Define a cursor TYPE -- either "weak" or "strong".
2. Define a cursor variable.
Declare a WEAK
referenced cursor TYPE.

DECLARE
TYPE weak_curtype IS REF CURSOR;

TYPE comp_curtype IS REF CURSOR RETURN company%ROWTYPE;


Declare a STRONG
referenced cursor TYPE.

curcmp_new comp_curtype;
cur_any weak_curtype;
BEGIN
Declare cursor variables
from the TYPEs.

PL/SQL Advanced Techniques - page 68

Strong vs. Weak Cursor Types

A strong (or constrained) cursor type has a defined


return data specification.
Can only reference cursor objects which return the same data
specification, which can be any single SQL datatype or any
previously defined record structure.
Datatype mismatches are identified at compile time.

TYPE cur_typ_name IS REF CURSOR [ RETURN return_type ];


TYPE cur_typ_name IS REF CURSOR RETURN emp%ROWTYPE; /* Strong */

The weak (or unconstrained) cursor type does not have a


RETURN clause.
It can reference any cursor object, be opened FOR any query.
Datatype mismatches can only be identified at runtime.

TYPE cur_typ_name IS REF CURSOR; /* Weak */


PL/SQL Advanced Techniques - page 69

Opening with the Cursor Variable


OPEN cursor_name FOR select_statement;

When you open a cursor variable (whether of the weak or


strong variety), you must provide the SQL query that
identifies the result set.
If the variable has not yet been assigned to cursor object, the
OPEN FOR statement implicitly creates an object for the variable.
If the variable is already pointing to a cursor object, the OPEN
FOR reuses the existing object and attaches the new query to
that cursor object.

Remember, the cursor object is nothing more than a


memory location.
It is maintained independently of the query itself.

PL/SQL Advanced Techniques - page 70

Opening with Strong Cursor Types


DECLARE
TYPE emp_curtype IS REF CURSOR RETURN emp%ROWTYPE;
emp_curvar emp_curtype;

Match Needed

BEGIN
OPEN emp_curvar FOR SELECT * from emp;
...
END;

STRONG cursor data specifications must match or be


compatible with the structure of the SELECT statement.
You can establish the return type based on a database table, a
cursor or a programmer-defined record.

PL/SQL Advanced Techniques - page 71

Opening with Weak Cursor Types


PACKAGE pkg IS
TYPE cv_type IS REF CURSOR;
END;

REF TYPE placed in


package so that it
is "globally" available.

FUNCTION open_emp_or_dept (get_type_in IN VARCHAR2)


RETURN pkg.cv_type
IS
retval pkg.cv_type;
BEGIN
IF get_type_in = EMP
THEN
Either query will "do".
OPEN retval FOR SELECT * FROM emp;
Verification will take
ELSIF get_type_in = DEPT
place at the FETCH.
THEN
OPEN retval FOR SELECT * FROM dept;
END IF;
RETURN retval;
END;

A weak cursor TYPE doesn't define the RETURN structure; you can
associate any SELECT statement with a weak cursor variable.
PL/SQL Advanced Techniques - page 72

Fetching from the Cursor Variable


FETCH cursor_var_name INTO record_name;
FETCH cursor_var_name INTO var_name, var_name, ...;

Fetching with cursor variables follows the same rules as


those with static cursors.

The INTO structure must match in number and datatype to:


FOR STRONG cursor types, it match the cursor type data
specification.
FOR WEAK cursor types, it match the OPEN FOR statement
structure.

Compatibility checks are performed prior to fetching row.


The ROWTYPE_MISMATCH exception is raised on failure.
Fetching can continue with a different INTO clause.
mismatch.sql

PL/SQL Advanced Techniques - page 73

When to Use Cursor Variables

Make it easier for calling programs (especially non-PL/SQL


programs) to manipulate result sets.
JDBC recognizes cursor variables.

Define a base table block in Forms Builder (formerly Oracle


Forms) on stored procedures rather than a table directly.

Use a single block of code to manipulate multiple queries.


With explicit cursors, you have to repeat the code for each cursor,
since cursor names are "hard coded".
You could use dynamic SQL to achieve this effect, but static cursors
are more efficient than dynamic SQL and cursor variables are less
complicated than DBMS_SQL.
hccursor.sql

PL/SQL Advanced Techniques - page 74

Consolidating Different Cursors

The following package specification hides the SQL behind a single open function.
It also creates the data structures you will need to call the function.

CREATE OR REPLACE PACKAGE allcurs


IS
bydept CONSTANT INTEGER := 1;
bysal CONSTANT INTEGER := 2;
TYPE int_rt IS RECORD (key INTEGER);
TYPE cv_t IS REF CURSOR RETURN int_rt;
FUNCTION open (type_in IN INTEGER) RETURN cv_t;
END;
/
allcurrs.pkg
allcurs.tst
explcv.sql

PL/SQL Advanced Techniques - page 75

Consolidating Different Cursors

The open function simply opens FOR a different SELECT based on the
criteria passed to it.
CREATE OR REPLACE PACKAGE BODY allcurs
IS
FUNCTION open (type_in IN INTEGER) RETURN cv_t
IS
retval cv_t;
BEGIN
IF type_in = bydept
THEN
OPEN retval FOR SELECT empno FROM emp ORDER BY deptno;
ELSIF type_in = bysal
THEN
OPEN retval FOR SELECT empno FROM emp ORDER BY SAL;
END IF;
RETURN retval;
END;
END;
/
PL/SQL Advanced Techniques - page 76

Hiding SQL Variations in Resulting Code

The following block demonstrates that you can alter which SQL statement to query -- in
this case, change the ORDER BY clause -- without having to change the code you write.

DECLARE
cv allcurs.cv_t;
v_empno emp.empno%TYPE;
BEGIN
cv := allcurs.open (&1);
LOOP
FETCH cv INTO v_empno;
EXIT WHEN cv%NOTFOUND;
p.l (v_empno);
END LOOP;

"Report processor"
is independent of
the particular SELECT

CLOSE cv;
END;
/
PL/SQL Advanced Techniques - page 77

Cursor Variables
Let's
summarize

Flexibility
Choose which static SQL statement is executed at run-time.

Strong and Weak Types


Create REF CURSORs for specific queries, or a more general,
unconstrained type.

Hide Variations in Underlying SQL


You no longer have to repeat the same code for different cursors.

Improve Client-Side Access to Data


At least in Oracle Developer 2.1, you can build screens that access data
using a cursor variable-based procedural API.
PL/SQL Advanced Techniques - page 78

Achieving PL/SQL Excellence

Dynamic SQL

Dynamic SQL and dynamic PL/SQL:


The DBMS_SQL package
Native dynamic SQL in Oracle8i

PL/SQL Advanced Techniques - page 79

Dynamic SQL and PL/SQL Execution

"Dynamic SQL" mean that you construct the SQL statement or


PL/SQL block at runtime and then execute it.
Available in PL/SQL since Release 2.1 and DBMS_SQL.
Also supported with "native dynamic SQL" in Oracle8i.

What can you do with Dynamic SQL?

Build ad-hoc query and update applications.


Very common requirement on the Web.

Execute DDL inside PL/SQL programs.


Construct powerful DBA utilities; you no longer have to write SQL
to generate SQL to get your job done.

Execute dynamically-constructed PL/SQL programs.


One example: implement indirect referencing in PL/SQL.
PL/SQL Advanced Techniques - page 80

Four Methods of Dynamic SQL

Method 1: non-queries without host variables, executed a single time.

Method 2: non-queries with a fixed number of host variables, execute


one or more times.

Method 3: queries with a fixed number of items in the SELECT list and a
fixed number of host variables.

Method 4: queries with a variable number of items in the SELECT list


and/or non-queries with a variable number of host variables.

These methods are in increasing order of complexity. If you can


recognize the types, you can more quickly figure out how to code your
solution.
Different methods require the use of different programs in DBMS_SQL.
NDS does not support method 4.

PL/SQL Advanced Techniques - page 81

Native Dynamic SQL

Prior to Oracle8i, you would use the DBMS_SQL built-in


package to execute dynamic SQL.
But this package is very complex, difficult to use, and relatively slow
(performance did improve significantly as of Oracle8).

The new "native dynamic SQL" or NDS of Oracle8i offers


two native statements in the PL/SQL language to implement
most of your dynamic SQL requirements:
EXECUTE IMMEDIATE <sql string>, used for DDL, DML and single
row fetches.
OPEN FOR <sql string>, used for multi-row queries.

PL/SQL Advanced Techniques - page 82

EXECUTE IMMEDIATE
EXECUTE IMMEDIATE sql-string
[INTO {define_variable[, define_variables]... | record }]
[USING {IN | OUT | IN OUT] bind argument
[, {IN | OUT | IN OUT] bind argument]...];

Use this statement to execute any dynamic SQL statement


(including a PL/SQL block) except for multi-row queries.

The INTO clause allows you to pass values from the select
list of a single row query into local variables, including
objects, collections and records.

The USING clause allows you to specify bind arguments or


variables to be passed into the SQL string before execution.
PL/SQL Advanced Techniques - page 83

COUNT(*) For Any Table

Here's a handy and simple utility based on NDS:

CREATE OR REPLACE FUNCTION tabCount (


tab IN VARCHAR2, whr IN VARCHAR2 := NULL, sch IN VARCHAR2 := NULL)
RETURN INTEGER
IS
Specify schema, table and
retval INTEGER;
WHERE clause...
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' || NVL (sch, USER) ||
'.' || tab || ' WHERE ' || NVL (whr, '1=1')
INTO retval;
RETURN retval;
END;
IF tabCount ('citizens', 'insured = ''NO''') > 40,000,000
THEN
DBMS_OUTPUT.PUT_LINE (
'Not the best health care system in the world...
and not much of a democracy either!');
END IF;

tabcount81.sf
compare with:
tabcount.sf

PL/SQL Advanced Techniques - page 84

Other Execute Immediate Examples

Perform an update...

CREATE OR REPLACE PROCEDURE updnumval (


tab_in IN VARCHAR2, col_in IN VARCHAR2,
start_in IN DATE, end_in IN DATE,
val_in IN NUMBER) IS
BEGIN
EXECUTE IMMEDIATE
'UPDATE ' || tab_in ||
' SET ' || col_in || ' = :val
WHERE hiredate BETWEEN :lodate AND :hidate'
USING val_in, start_in, end_in;
END;

Pass in bind
variables with
USING clause.

Execute a stored procedure...

PROCEDURE runprog (pkg_in IN VARCHAR2, name_in IN VARCHAR2) IS


v_str VARCHAR2 (100);
BEGIN
v_str := 'BEGIN ' || pkg_in || '.' || name_in || '; END;';
EXECUTE IMMEDIATE v_str;
EXCEPTION
WHEN OTHERS THEN
pl ('Compile Error "' || SQLERRM || '" on: ' || v_str); END;
PL/SQL Advanced Techniques - page 85

Using Objects and Collections in NDS

One of the key advantages to NDS over DBMS_SQL is that it


works with Oracle8 datatypes, including objects and
collections.
No special syntax needed...
In the following example, the USING clause allows me to pass an object
and nested table to an INSERT statement with a variable table name.

PROCEDURE add_profit_source (
hosp_name IN VARCHAR2,
pers IN Person,
cond IN preexisting_conditions)
IS
BEGIN
EXECUTE IMMEDIATE
'INSERT INTO ' || tabname (hosp_name) ||
' VALUES (:revenue_generator, :revenue_inhibitors)'
USING pers, cond;
END;

health$.pkg

PL/SQL Advanced Techniques - page 86

Multiple Row Queries and NDS

Oracle extends the cursor variable feature of Oracle7 to


support multi-row dynamic queries.
Here is a simple utility the displays the values of any date, number or
string column in any table.
CREATE OR REPLACE PROCEDURE showcol (
tab IN VARCHAR2, col IN VARCHAR2, whr IN VARCHAR2 := NULL)
IS
TYPE cv_type IS REF CURSOR;
cv cv_type;
val VARCHAR2(32767);
BEGIN
OPEN cv FOR 'SELECT ' || col || ' FROM ' || tab ||
' WHERE ' || NVL (whr, '1 = 1');
LOOP
Familiar cursor
FETCH cv INTO val;
variable syntax!
EXIT WHEN cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (val);
END LOOP;
CLOSE cv;
showcol.sp
END;
ndsutil.pkg

PL/SQL Advanced Techniques - page 87

Some Fine Print for NDS

You cannot pass schema elements (table names, column


names, etc.) through the USING clause.

You cannot pass the NULL literal directly in the USING


clause. Instead, pass a variable with a NULL value.

The USING clause for a query can only have IN bind


arguments.

You can have duplicate placeholders (for bind arguments).


If dynamic SQL, then you provide a value for each placeholder (by
position).
If dynamic PL/SQL, provide a value for each distinct placeholder (by
name).
str2list.pkg

PL/SQL Advanced Techniques - page 88

Dynamic SQL using DBMS_SQL

Prior to Oracle8i, the only way to perform dynamic SQL was


with the DBMS_SQL package.

DBMS_SQL is a very large and complex package, with many


rules to follow and lots of code to write.

Supports all four methods of dynamic SQL, in particular


method 4.

The overhead for using DBMS_SQL has decreased


significantly in Oracle8 and again in Oracle8i.

You should only use DBMS_SQL when you cannot use NDS.

PL/SQL Advanced Techniques - page 89

Learning Through Examples

DDL
Create an index from within PL/SQL

DML
Update rows in a table

DML with binding


Update rows using bind variables

Queries
Method 3 and a dynamic WHERE clause

PL/SQL Version of "SELECT *"


Example of Method 4

PL/SQL
Create a generic calculation program

PL/SQL Advanced Techniques - page 90

DDL with Dynamic SQL


PROCEDURE create_index
(index_in IN VARCHAR2, tab_in IN VARCHAR2, col_in IN VARCHAR2)
IS
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk INTEGER;
DDL_statement VARCHAR2(200)
:= 'CREATE INDEX ' || index_in || ' ON ' || tab_in ||
' ( ' || col_in || ')';
BEGIN
DBMS_SQL.PARSE (cur, DDL_statement, DBMS_SQL.NATIVE);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_SQL.CLOSE_CURSOR (cur);
END;

creind.sp

Creates an index on any column(s) in any table in


your schema.
Open a cursor, which will be used to execute the DDL
statement.
Construct the DDL statement as a string.
Parse and execute that DDL statement.
PL/SQL Advanced Techniques - page 91

Updates with Dynamic SQL

Update numeric column for specified employees.


CREATE OR REPLACE PROCEDURE updnumval (
col_in IN VARCHAR2,
ename_in IN emp.ename%TYPE,
val_in IN NUMBER)
IS
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.PARSE (cur,
'UPDATE emp SET ' || col_in || ' = ' || val_in ||
' WHERE ename LIKE UPPER (''' || ename_in || ''')',
DBMS_SQL.NATIVE);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_OUTPUT.PUT_LINE ('Rows updated: ' || TO_CHAR (fdbk));
DBMS_SQL.CLOSE_CURSOR (cur);
END;

updnval1.sp

PL/SQL Advanced Techniques - page 92

Updates with Bind Variables

Update salaries for date range using binding.


CREATE OR REPLACE PROCEDURE updnumval (
col_in IN VARCHAR2,
start_in IN DATE, end_in IN DATE, val_in IN NUMBER)
IS
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk PLS_INTEGER;
BEGIN
DBMS_SQL.PARSE (cur, 'UPDATE emp SET ' ||
col_in || ' = ' || val_in ||
' WHERE hiredate BETWEEN :lodate AND :hidate',
DBMS_SQL.NATIVE);

updnval2.sp
updnval3.sp

DBMS_SQL.BIND_VARIABLE (cur, 'lodate', start_in);


DBMS_SQL.BIND_VARIABLE (cur, 'hidate', end_in);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_OUTPUT.PUT_LINE ('Rows updated: ' || TO_CHAR (fdbk));
DBMS_SQL.CLOSE_CURSOR (cur);
END;
PL/SQL Advanced Techniques - page 93

Processing Flow for a Dynamic Query


Allocate
Allocatecursor
cursormemory
memory
(OPEN_CURSOR)
(OPEN_CURSOR)

Fill
Fillcursor
cursorwith
withdata
data
(EXECUTE)
(EXECUTE)

Make
Makesure
sureSELECT
SELECTisis
well
wellformed
formed
(PARSE)
(PARSE)

Fill
Fillbuffer
bufferwith
withdata
data
(FETCH_ROWS)
(FETCH_ROWS)
(EXECUTE_AND_FETCH)
(EXECUTE_AND_FETCH)

Bind
Bindany
anyvariables
variables
(BIND_VARIABLE)
(BIND_VARIABLE)
(BIND_ARRAY)
(BIND_ARRAY)

Retrieve
Retrievethe
thedata
data
(COLUMN_VALUE)
(COLUMN_VALUE)

Give
Givecursor
cursorstructure
structure
(DEFINE_COLUMN)
(DEFINE_COLUMN)
(DEFINE_ARRAY)
(DEFINE_ARRAY)

Release
Releasecursor
cursormemory
memory
(CLOSE_CURSOR)
(CLOSE_CURSOR)

PL/SQL Advanced Techniques - page 94

Queries with Dynamic SQL

Show employees using a dynamic WHERE clause...

CREATE OR REPLACE PROCEDURE showemps (where_in IN VARCHAR2 := NULL)


IS
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
rec emp%ROWTYPE; fdbk INTEGER;
BEGIN
DBMS_SQL.PARSE (cur, 'SELECT empno, ename FROM emp ' ||
' WHERE ' || NVL (where_in, '1=1'), DBMS_SQL.NATIVE);
DBMS_SQL.DEFINE_COLUMN (cur, 1, 1);
DBMS_SQL.DEFINE_COLUMN (cur, 2, 'a', 60);

showemps.sp
showemp2.sp
showemps.tst

fdbk := DBMS_SQL.EXECUTE (cur);


LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS (cur) = 0;
DBMS_SQL.COLUMN_VALUE (cur, 1, rec.empno);
DBMS_SQL.COLUMN_VALUE (cur, 2, rec.ename);
DBMS_OUTPUT.PUT_LINE (TO_CHAR (rec.empno) || '=' || rec.ename);
END LOOP;
DBMS_SQL.CLOSE_CURSOR (cur);
END;

PL/SQL Advanced Techniques - page 95

Dynamic SELECT * FROM Any Table

Method 4 example: the number of columns queried changes with each table.
The resulting code is much more complicated.

Very simplified
pseudo-code
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;

intab.sp

LOOP
fetch-a-row;
FOR each-column-in-table LOOP
DBMS_SQL.COLUMN_VALUE (cur, nth_col, val);
END LOOP;
END LOOP;
END;

PL/SQL Advanced Techniques - page 96

Using EXECUTE_AND_FETCH
FUNCTION execute_and_fetch
(cursor_in IN INTEGER,
exact_match IN BOOLEAN DEFAULT FALSE)
RETURN INTEGER;
numrows := DBMS_SQL.EXECUTE_AND_FETCH (cur);
numrows := DBMS_SQL.EXECUTE_AND_FETCH (cur, TRUE);

Makes it easy to execute and fetch a single row from a query.


Very similar to the implicit SELECT cursor in native PL/SQL, which returns a
single row, raises NO_DATA_FOUND or raises the TOO_MANY_ROWS exception.
If exact_match is TRUE, then EXECUTE_AND_FETCH will raise the
TOO_MANY_ROWS exception if more than one row is fetched by the SELECT.
Even if the exception is raised, the first row will still be fetched and available.

PL/SQL Advanced Techniques - page 97

Dynamic Formula Execution

Suppose I am building a user interface that allows a user to


select a formula for execution, and enter the arguments.
Using static PL/SQL, I would have to modify my screen every time a
new formula was added.
With DBMS_SQL, a single function will do the trick.
FUNCTION dyncalc (
oper_in IN VARCHAR2,
nargs_in IN INTEGER :=
arg1_in IN VARCHAR2 :=
arg3_in IN VARCHAR2 :=
arg5_in IN VARCHAR2 :=
arg7_in IN VARCHAR2 :=
arg9_in IN VARCHAR2 :=
)
RETURN VARCHAR2;

0,
NULL,
NULL,
NULL,
NULL,
NULL,

arg2_in IN VARCHAR2 := NULL,


arg4_in IN VARCHAR2 := NULL,
arg6_in IN VARCHAR2 := NULL,
arg8_in IN VARCHAR2 := NULL,
arg10_in IN VARCHAR2 := NULL

dyncalc.sf
dyncalc.pkg

PL/SQL Advanced Techniques - page 98

More on Dynamic PL/SQL


BEGIN
cur := open_and_parse
('BEGIN get_max_sal (:deptin, :salout); END;');
DBMS_SQL.BIND_VARIABLE (cur, deptin, v_deptin);
DBMS_SQL.BIND_VARIABLE (cur, salout, my_salary);
fdbk := DBMS_SQL.EXECUTE (cur);
DBMS_SQL.VARIABLE_VALUE (cur, salout, my_salary);
END;

Use BIND_VARIABLE to bind any placeholders in the string -- even OUT


arguments which are not being bound to any values.

Use VARIABLE_VALUE to extract a value from any variable you have bound.

You must have a BEGIN-END around the code.

Possibilities inherent in dynamic PL/SQL are mind-boggling!


dynplsql.sql
dynplsql.sp
dynplsql.tst

PL/SQL Advanced Techniques - page 99

Indirect Referencing with Dyn PL/SQL

Oracle Forms offers support for indirect referencing with the


NAME_IN and COPY built-ins.

PL/SQL does not support indirect referencing, but you can


accomplish much of the same thing with dynamic PL/SQL
execution. Here is an example of a "PL/SQL NAME_IN":

dynvar.pkg
dynvar.tst

FUNCTION valbyname (nm IN VARCHAR2) RETURN VARCHAR2 IS


v_cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
fdbk PLS_INTEGER;
retval PLV.dbmaxvc2;
BEGIN
DBMS_SQL.PARSE (v_cur,
'BEGIN :val := ' || nm || '; END;',
DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE (v_cur, 'val', 'a', 2000);
fdbk := DBMS_SQL.EXECUTE (v_cur);
DBMS_SQL.VARIABLE_VALUE (v_cur, 'val', retval);
DBMS_SQL.CLOSE_CURSOR (v_cur);
RETURN retval;
END;
PL/SQL Advanced Techniques - page 100

DBMS_SQL Status Built-ins

The package offers a set of modules to return information about the lastoperated cursor in your session.
Call these immediately after your usage to make sure they refer to your cursor.

IS_OPEN
Is the cursor already open?

LAST_ERROR_POSITION
Returns relative column position in cursor of text causing error condition.

LAST_ROW_COUNT
Returns the cumulative count of rows fetched from the cursor.

LAST_ROW_ID
Returns the ROWID of last row processed.

LAST_SQL_FUNCTION_CODE
Returns SQL function code of cursor.

PL/SQL Advanced Techniques - page 101

Working with LONG Values

DBMS_SQL provides special procedures so that you can


extract values from a LONG column in a table.
DBMS_SQL.DEFINE_COLUMN_LONG
DBMS_SQL.COLUMN_VALUE_LONG

First, you define the column as a LONG,

Then you retrieve the column using the special


COLUMN_VALUE_LONG variant.
Transfer the LONG contents into an index-by table so that you can
transfer a value of more than 32K bytes into your PL/SQL program.

dumplong.pkg
dumplong.tst

PL/SQL Advanced Techniques - page 102

New DBMS_SQL Features

PL/SQL8
Extensions to
DBMS_SQL

PL/SQL Advanced Techniques - page 103

New Features in DBMS_SQL

The functionality of DBMS_SQL has been


extended in Oracle8 in several ways:
Parse very long SQL strings
Describe cursor columns
Use "array processing" to perform bulk updates,
inserts, deletes and fetches.
Support for RETURNING clause to avoid unnecessary
queries.

PL/SQL Advanced Techniques - page 104

Describing Cursor Columns


PROCEDURE DBMS_SQL.DESCRIBE_COLUMNS
(c IN INTEGER,
col_cnt OUT INTEGER,
desc_t OUT DBMS_SQL.DESC_TAB);

Before PL/SQL8, it was not possible to determine


the datatypes of the columns defined in a cursor.
Now you can call the DBMS_SQL.DESCRIBE_COLUMNS.

Returns all of the column information in an index


table of records.
The record TYPE is also defined in DBMS_SQL.

PL/SQL Advanced Techniques - page 105

Basic Steps to Describe Columns

The following script shows the individual steps you will need to perform in order to
use this feature.
CREATE OR REPLACE PROCEDURE show_columns
IS
cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR;
cols DBMS_SQL.DESC_TAB;
ncols PLS_INTEGER;
BEGIN
DBMS_SQL.PARSE (cur,
'SELECT hiredate, empno FROM emp', DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS (cur, ncols, cols);
FOR colind IN 1 .. ncols
LOOP
DBMS_OUTPUT.PUT_LINE (cols(colind).col_name);
END LOOP;
DBMS_SQL.CLOSE_CURSOR (cur);
END;

desccols.pkg
desccols.tst
showcols.sp

PL/SQL Advanced Techniques - page 106

"Array Processing" in DBMS_SQL

PL/SQL8 now allows you to specify the use of "arrays", i.e., index
tables, when you perform updates, inserts, deletes and fetches.

Instead of providing a scalar value for an operation, you specify


an index table. DBMS_SQL then repeats your action for every row
in the table.

It really isn't "array processing".


In actuality, DBMS_SQL is executing the specified SQL statement N times,
where N is the number of rows in the table.

This technique still, however, can offer a significant performance


boost over Oracle7 dynamic SQL.

PL/SQL Advanced Techniques - page 107

Recommendations for Dynamic SQL

Bind (vs. concatenate) whenever possible.


Increased chance of reusing parsed SQL, and easier code to write.
But remember: you cannot pass schema elements (table names,
column names, etc.) through the USING clause.

Encapsulate statements to improve error handling.


With NDS, only possible for statements that do not need USING and
INTO clauses, though you could write variations for those as well.
Encapsulate DBMS_SQL.PARSE so that you include the trace in that
program.

Use the Oracle8i invoker rights model whenever you want to


share your dynamic SQL programs among multiple
schemas.
Otherwise that SQL will be executed under the authority
of the owner of the code, not the invoker of the code.

effdsql.sql
openprse.pkg
whichsch.sql

PL/SQL Advanced Techniques - page 108

NDS or DBMS_SQL: Which is Best?

Dynamic SQL and PL/SQL is very useful, but DBMS_SQL is hard to use. Both
implementations will still come in handy...
If, of course, you have upgraded to Oracle8i!

Major Advantages of NDS:


Ease of use
Performance
Works with all SQL
datatypes (including userdefined object and
collection types)
Fetch into records

When You'd Use DBMS_SQL:

Method 4 Dynamic SQL


DESCRIBE columns of cursor
SQL statements larger than 32K
RETURNING into an array
Reuse of parsed SQL statements
Bulk dynamic SQL
Available from client-side PL/SQL

PL/SQL Advanced Techniques - page 109

Achieving PL/SQL Excellence

Oracle
Advanced Queuing

The high performance, asynchronous,


persistent messaging subsytem of
Oracle

PL/SQL Advanced Techniques - page 110

Who Needs Messaging? Everyone!

In the distributed world of the Internet, systems are now


heavily reliant on the ability of components to communicate
with each other in a dependable, consistent manner.
Distribution
Center
Coffee Beans
Producer

Retailer
Shipping
Service

Consumer
PL/SQL Advanced Techniques - page 111

Applications Relying on Messaging

Stock trading system

Airline reservation system

Auction portals

Any e-commerce application

PL/SQL Advanced Techniques - page 112

Key Features of Oracle AQ

Leverage full power of SQL


Messages are stored in database tables

Database high availability, scalability and reliability all carry


over to queues
Strong history and retention
Backup and recovery
Comprehensive journaliing

Rich message content increases usefulness of queueing


Use object types to define highly structured payloads

New to Oracle8i, AQ now offers a publish/subscribe style of


messaging between applications.
Rule-based subscribers, message propagation, the listen feature and
notification capabilities.

PL/SQL Advanced Techniques - page 113

AQ architectural overview
Queue Monitor process
Queue table

Queue
Producers

Consumers
Enqueued
messages

Message 4
Message 3
Message 2

Dequeued
messages

Message1

Messages include both


control information and
payload (content)
PL/SQL Advanced Techniques - page 114

Oracle AQ Highlights

In 8.0, AQ supports:

Multiple queues
Resetting order and priority of queued items
Queue management using only SQL & PL/SQL
Multiple message recipients
Propagation of queue to remote servers

Oracle8i adds:

Rules-based publish & subscribe


Listening on multiple queues
Easier monitoring
Native Java interface
PL/SQL Advanced Techniques - page 115

AQ Components

The DBMS_AQ package offers enqueue and


dequeue capabilities

The DBMS_AQADM package provides


administrative functionality to manage queues and
queue tables.

Underlying database tables and views

The queue monitor (background process)


Set # of processes with the AQ_TM_PROCESSES
initialization parameter.

PL/SQL Advanced Techniques - page 116

DBMS_AQADM Highlights
CREATE_QUEUE_TABLE

Assigns name, payload type, storage clause, sort


column, whether multiple consumers

DROP_QUEUE_TABLE

Drops table if all queues in the table have been


stopped

CREATE_QUEUE

Associates queue table with queue; assigns retry


and retention properties to queue

DROP_QUEUE

Drops a stopped queue

START_QUEUE

Can also turn on/off enqueue and dequeue


operations

STOP_QUEUE

Stops queue, optionally waiting for outstanding


transactions

ADD_SUBSCRIBER

Adds an agent as a subscriber

PL/SQL Advanced Techniques - page 117

Creating Queue Tables and Queues


CREATE TYPE message_type AS OBJECT
(title VARCHAR2(30),
text VARCHAR2(2000));
/

Define the "payload"

BEGIN
DBMS_AQADM.CREATE_QUEUE_TABLE
(queue_table => 'msg',
queue_payload_type => 'message_type');

Create the queue table

Define a queue in the


queue table

DBMS_AQADM.CREATE_QUEUE
(queue_name => 'msgqueue',
queue_table => 'msg');

DBMS_AQADM.START_QUEUE (queue_name => 'msgqueue');


END;

Start the queue


aq.pkg

PL/SQL Advanced Techniques - page 118

The "operational package": DBMS_AQ

DBMS_AQ is deceptively simple.


Only two procedures, but lots of complexity buried inside the
parameters of these procedures.

ENQUEUE puts a message into a specified queue, and returns


a RAW message handle

DEQUEUE extracts a message from a specified queue

Parameters control message properties such as:

aq.sql

Visibility (ON_COMMIT or IMMEDIATE)


Priority
Delay
Expiration
Locking behavior

PL/SQL Advanced Techniques - page 119

Simple Enqueue Example


DECLARE
queueopts DBMS_AQ.ENQUEUE_OPTIONS_T;
msgprops DBMS_AQ.MESSAGE_PROPERTIES_T;
msgid aq.msgid_type;
my_msg message_type;
BEGIN
my_msg :=
message_type (
'First Enqueue',
'May there be many more...');
DBMS_AQ.ENQUEUE (
'msgqueue',
queueopts,
msgprops,
my_msg,
msgid);
END;

aqenq*.*

Declare records to
hold various enqueue
and msg properties.

Set up the payload


with an object
constructor.

Place the message on the


specified queue and get a
msg ID in return.
PL/SQL Advanced Techniques - page 120

More Interesting Enqueue Example


DECLARE
... Same setup as previous page ...
BEGIN
my_msg := message_type (
'First Enqueue', 'May there be many more...');
msgprops.delay := 3 * 60 * 60

Specify a delay
before the payload
is available.

* 24;

DBMS_AQ.ENQUEUE ('msgqueue',
queueopts, msgprops, my_msg, msgid1);
my_msg := message_type (
'Second Enqueue',
'And this one goes first...');
queueopts.sequence_deviation := DBMS_AQ.BEFORE;
queueopts.relative_msgid := msgid1;
DBMS_AQ.ENQUEUE (
'msgqueue',
queueopts, msgprops, my_msg, msgid2);
END;

Modify the dequeue


sequence by changing the
deviation field and relative
msg ID.

PL/SQL Advanced Techniques - page 121

Dequeue Example
DECLARE
queueopts DBMS_AQ.DEQUEUE_OPTIONS_T;
msgprops DBMS_AQ.MESSAGE_PROPERTIES_T;
msgid aq.msgid_type;
/* defined in aq.pkg */
my_msg message_type;
PROCEDURE getmsg (mode_in IN INTEGER) IS
BEGIN
queueopts.dequeue_mode := mode_in;
DBMS_AQ.DEQUEUE (
'msgqueue', queueopts,
msgprops, my_msg, msgid);
END;
BEGIN
getmsg (DBMS_AQ.BROWSE);
getmsg (DBMS_AQ.REMOVE);
getmsg (DBMS_AQ.REMOVE);
END;

Declare records to
hold various dequeue
and msg properties.

Dequeue operation
isolated in local
module.

Demonstrates destructive
and non-destructive
dequeuing.
aqdeq*.*
PL/SQL Advanced Techniques - page 122

Prioritized Payloads

You can assign priorities to individual payloads and then dequeue


according to those priorities.
The lower the numeric priority value, the higher the priority.

A stack implementation using AQ demonstrates this well.


PROCEDURE push (item IN VARCHAR2) IS
queueopts DBMS_AQ.ENQUEUE_OPTIONS_T;
msgprops DBMS_AQ.MESSAGE_PROPERTIES_T;
msgid aq.msgid_type;
item_obj aqstk_objtype;
BEGIN
item_obj := aqstk_objtype (item);
msgprops.priority := g_priority;
queueopts.visibility := DBMS_AQ.IMMEDIATE;
g_priority := g_priority - 1;
DBMS_AQ.ENQUEUE (
c_queue, queueopts, msgprops, item_obj, msgid);
END;

aqstk.pkg
aqstk2.pkg
priority.*

PL/SQL Advanced Techniques - page 123

Defining Message Subscribers

You can specify that a message is to be enqueued for a list


of subscribers.
The message is then not removed from the queue until all
subscribers have dequeued the message.

Steps to working with a subscriber list:


1. The queue table must be defined to support multiple subscribers
or consumers.
BEGIN
DBMS_AQADM.CREATE_QUEUE_TABLE (
queue_table => 'major_qtable',
queue_payload_type => 'student_major_t',
multiple_consumers => TRUE);

2. Add subscribers for the queue.


DBMS_AQADM.ADD_SUBSCRIBER (
c_queue, SYS.AQ$_AGENT (name_in, NULL, NULL));

aqmult*.*

PL/SQL Advanced Techniques - page 124

Oracle AQ - Summary

Very powerful and flexible architecture.


Much more robust that DBMS_PIPE.
Significant enhancements in Oracle8i, supporting a
publish-subscribe model, improved security, LISTEN
capability.

Considered by Oracle to be a core component of its


overall solution.
Crucial for Oracle to have a message-oriented
middleware in order to offer an all-Oracle solution.
Should be strongly supported "down the road".

PL/SQL Advanced Techniques - page 125

Achieving PL/SQL Excellence

Managing
Large Objects
with DBMS_LOB
PL/SQL Advanced Techniques - page 126

LOB Terms

LOB = Large OBject: a category of datatype allowing


storage of unstructured data up to 4 gigabytes

LOB datatype can be:


Column in table
Attribute in object type
Element in nested table

Possible applications include office documents, Images,


sounds, video, etc.

LOB datatypes are the successor to LONGs.


Oracle has announced deprecation of LONGs; they should no
longer be used, though they will probably not be actually desupported.
PL/SQL Advanced Techniques - page 127

Types of Large Objects

Internal LOBs

BLOB: unstructured binary


data

BFILE: pointer to an
operating system file

CLOB: single-byte fixed-width


character data
NCLOB: multi-byte fixed-width
character data (or varying width
in
)

External LOBs

Temporary LOBs
Internal LOBs that do not
participate in transactions,
improving performance.

Key programming differences:


Internal LOBs participate in transactions; external do not
External LOBs are read-only from within Oracle
PL/SQL Advanced Techniques - page 128

PL/SQL built-ins for LOBs

Package DBMS_LOB
Supports for all LOBs: Reading, substring and instring searches,
comparison and length checking
For internal LOBs: Write, append, copy, erase, trim
For external LOBs: File existence test, open, close

Other built-in functions


LOADFROMFILE (load an external BFILE into a BLOB)
EMPTY_LOB(), EMPTY_CLOB()
BFILENAME

PL/SQL Advanced Techniques - page 129

Deceptively simple example

BLOB-typed column in a table


CREATE TABLE incoming_faxes
(fax_id INTEGER,
received DATE,
fax BLOB);

Retrieve record with BLOB into PL/SQL variable


DECLARE
CURSOR fax_cur IS
SELECT fax FROM incoming_faxes;
the_fax BLOB;
BEGIN
OPEN fax_cur;
FETCH fax_cur INTO the_fax;
CLOSE fax_cur;
END;

Question:
Whats in the_fax?

PL/SQL Advanced Techniques - page 130

The LOB locator


LOB

columns [usually] contain pointers, not the LOBs themselves


<lob segment>

fax_id

received

fax

281937

12-JAN-98

<lob locator>

Updating
This

the LOB changes the pointer

complicates use of LOBs

LOB locators cannot span transactions


Programs must lock records containing LOBs before updating
Programs performing DML must accommodate dynamic LOB locator
values

PL/SQL Advanced Techniques - page 131

Example: Piecewise CLOB programmatic insert

1 of 2

Create a table
CREATE TABLE web_pages (
url VARCHAR2(512) PRIMARY KEY,
htmlloc CLOB);

Must get a new LOB locator before writing into the LOB:
DECLARE
the_url web_pages.url%TYPE := 'http://www.oodb.com';
the_loc CLOB;
BEGIN
INSERT INTO web_pages VALUES (the_url, EMPTY_CLOB())
RETURNING htmlloc INTO the_loc;
...

PL/SQL Advanced Techniques - page 132

Example: Piecewise CLOB programmatic insert


DECLARE
the_url web_pages.url%TYPE := 'http://www.oodb.com';
the_loc CLOB;
html_tab UTL_HTTP.HTML_PIECES;
piece_length PLS_INTEGER;
running_total PLS_INTEGER := 1;

BEGIN

This block retrieves INSERT INTO web_pages VALUES (the_url, EMPTY_CLOB())


and loads a web page
RETURNING htmlloc INTO the_loc;
into a CLOB column
html_tab := UTL_HTTP.REQUEST_PIECES(url => the_url);
FOR the_piece_no IN 1..html_tab.COUNT
LOOP
piece_length := LENGTH(html_tab(the_piece_no));
DBMS_LOB.WRITE(lob_loc => the_loc,
Note: We are ignoring
amount => piece_length,
several likely exceptions
offset => running_total,
buffer => html_tab(the_piece_no));
running_total := running_total + piece_length;
END LOOP;
END;
PL/SQL Advanced Techniques - page 133

Example: Piecewise CLOB programmatic update


DECLARE
Program must lock the record explicitly
CURSOR hcur IS
SELECT htmlloc
FROM web_pages
WHERE url = 'http://www.oodb.com'
FOR UPDATE;
the_loc CLOB;
str_offset INTEGER;
BEGIN
OPEN hcur; FETCH hcur INTO the_loc; CLOSE hcur;
str_offset := DBMS_LOB.INSTR(lob_loc => the_loc,
pattern => 'oodb');
IF str_offset != 0
THEN
DBMS_LOB.WRITE(lob_loc => the_loc,
amount => 4,
offset => str_offset,
buffer => 'cool');
END IF;
END;

PL/SQL Advanced Techniques - page 134

Preparing to use BFILEs

You must first create an Oracle DIRECTORY


Creates logical alias for full directory path
Similar to LIBRARY (used for C "external procedures"), but
DIRECTORY namespace is global

Syntax:

Example:

CREATE OR REPLACE DIRECTORY <directory name>


AS
'<full path to directory>';

CREATE DIRECTORY web_pix


AS 'D:\bill\training\pix';

PL/SQL Advanced Techniques - page 135

Using BFILE datatype in DDL

In a table...
CREATE TABLE web_graphics (
image_id INTEGER,
image BFILE);

In an object type...
CREATE TYPE Business_card_t AS OBJECT (
name Name_t,
addresses Address_tab_t,
phones Phone_tab_t,
scanned_card_image BFILE
);
/

PL/SQL Advanced Techniques - page 136

The built-in BFILENAME function

Returns value of datatype BFILE. Spec:

Example:

FUNCTION BFILENAME(directory_exprn, file_exprn)


RETURN BFILE;

DECLARE
picture BFILE := BFILENAME('WEB_PIX', 'prodicon.gif');
BEGIN
INSERT INTO web_graphics VALUES (100015, picture);
END;
/

Notes:
No automatic file checking or synchronization
No participation in transactions
PL/SQL Advanced Techniques - page 137

Loading File into Database BLOB


CREATE TABLE web_graphic_blobs (
image_id INTEGER, image BLOB);
DECLARE
pic_file BFILE := BFILENAME('WEB_PIX', 'prodicon.gif');
pic_blob_loc BLOB := EMPTY_BLOB();
BEGIN
INSERT INTO web_graphic_blobs
VALUES (1, pic_blob_loc)
RETURNING image INTO pic_blob_loc;
DBMS_LOB.FILEOPEN(pic_file, DBMS_LOB.FILE_READONLY);
DBMS_LOB.LOADFROMFILE(dest_lob => pic_blob_loc,
src_lob => pic_file,
amount => DBMS_LOB.GETLENGTH(pic_file));
DBMS_LOB.FILECLOSE(pic_file);
END;
/

loadblob.sql

PL/SQL Advanced Techniques - page 138

New DBMS_LOB APIs in 8i

Support for temporary LOBs


No logging or rollback faster!
Lifespan: session, call, or transaction

Ability to retrieve LOB chunk size


Allows programmer to tune READs and WRITEs

For all internal LOBs


OPEN & CLOSE allow control over timing; that can mean less
I/O
Trigger will not fire until CLOSE
Indexes will not update until CLOSE

ISOPEN
PL/SQL Advanced Techniques - page 139

Large Object - Summary

LOB support in Oracle is now significantly


improved.

HOT

Complete support in PL/SQL (and other APIs)


Much better performance and control than with
LONGs
Usable in object types

Some weaknesses...
LOB locator behavior slightly abstruse
Inability to modify BFILEs directly from within
PL/SQL.

COLD

PL/SQL Advanced Techniques - page 140

Achieving PL/SQL Excellence

Leveraging Java
Inside PL/SQL

PL/SQL Advanced Techniques - page 141

Overview of Java interoperability

Java inside or outside 8i server can call PL/SQL


Standard JDBC and SQLJ calls with Oracle extensions
Same Java on client, mid-tier, or server
Not covered in this seminar

PL/SQL can call Java inside 8i server

Command-line tools load Java classes


DDL extensions publish Java classes
Writing stored procedures, functions, triggers in Java
Distinct Java & PL/SQL namespaces

But first...a BRIEF introduction to Java...


PL/SQL Advanced Techniques - page 142

Question 1: What is Java?

Could it be...
The end of programming history as we know it?
The easiest, fastest, slickest piece of software ever designed by
human beings?
Just the latest in a series of "silver bullets" promoted by software
vendors in order to prop up quarterly sales?
The first and only successful O-O language?
None of the above?

We don't really need to take a vote.


We just need to keep a firm grip on common sense and stay focused
on delivering solutions.

PL/SQL Advanced Techniques - page 143

Question 2: Will Java Replace PL/SQL?

While that scenario is certainly possible, it is very unlikely


and totally unthinkable for years to come.

PL/SQL will still be:


Faster and more productive than Java for database operations.
A language in which hundreds of thousands of developers are
trained.
Ubiquitous in thousands of production applications and millions of
lines of code.
Supported and improved by Oracle -- and very aggressively, to boot.

PL/SQL Advanced Techniques - page 144

Some Important Things to Remember

Java is a case sensitive language...


string is definitely not the same as String.

Everything is a class (or an object instantiated from a


class)...
Before you can call a (non-static) class method, you have to
instantiate an object from that class.
Well, everything exception the primitive datatypes.

You don't have to know how to do everything with Java to


get lots of value out of it...
Don't get overwhelmed by all the classes and all the strange quirks.

PL/SQL Advanced Techniques - page 145

Building a Java Class


Let's start from the very beginning...

public class Hello {


public static void main (String[] args) {
System.out.println ("Hello world!");
}
}

No members, no methods, except the "special" main method.


Used to test or run stand-alone a class,

CREATE OR REPLACE PROCEDURE hello IS


BEGIN
DBMS_OUTPUT.PUT_LINE ('Hello world!');
END;

Oh, by the way:


the PL/SQL
version

PL/SQL Advanced Techniques - page 146

Compiling Classes

Before you can use a class, you must compile it with the
javac command.
You must also either have set the CLASSPATH or include it in your
javac call.
This will convert the .java file to a .class file.
It will also automatically recompile any classes used by your class
that has not been compiled since last change.

SET CLASSPATH = d:\Oracle\Ora81\jdbc\lib\classes111.zip;


e:\jdk1.1.7b\lib\classes.zip;d:\java
D:> javac Hello.java -classpath d:\java

PL/SQL Advanced Techniques - page 147

Running a Class
Run a class? What does that mean? It means that if your
class contains a method with this header:

public static void main (String[] args)

then you can "run" the main method with the java
command:
d:\java> java Hello

You can also pass one or more arguments on the


command line:
d:\java> java Hello mom
Hello2.java
PL/SQL Advanced Techniques - page 148

Using a Class

The main method is handy for providing a built-in test


mechanism of your class.

Usually, however, you will use a class by instantiating


objects from the class and then invoking class methods on
the object.

Let's build a performance analyzer class to explore these


ideas.

PL/SQL Advanced Techniques - page 149

Compare Performance of Methods

Use System.currentTimeMillis to calculate elapsed time to


nearest thousandth of a second.
class Tmr {
private long Gstart = 0;
public void capture () {
Gstart = System.currentTimeMillis(); }

p.java
Tmr.java
InFile.java

public void showElapsed () {


p.l ("Elapsed time ",
System.currentTimeMillis() - Gstart); }
public long elapsed () {
return (System.currentTimeMillis() - Gstart); }
public void showElapsed (String context) {
p.l ("Elapsed time for " + context, elapsed()); }
}
PL/SQL Advanced Techniques - page 150

Types of Classes

There are several different kinds of classes in Java, besides


the "regular" kind we just saw...

Abstract class
The class can contain members and methods, but at least one
method remains unimplemented.

Interface class
The class only contains unimplemented methods.

Inner class
Classes that are defined inside other classes.

PL/SQL Advanced Techniques - page 151

And Now for Some Scary Buzzwords!

Inheritance
Subclasses inherit methods and variables from extended superclasses.

Polymorphism
An object of a class can have "multiple" forms, either as its own class or as
any superclass.
Dynamic polymorphism: form is determined at run-time.
Static polymorphism: form is chosen at compile-time (PL/SQL overloading).
supertype/"wider"

subtype/"narrower"
Citizen

War Criminal

Employee

Hourly Worker

Corporation

Salaried
Worker

Person.java

Person

A Class
Hierarchy

Management
NonManagement

PL/SQL Advanced Techniques - page 152

Language Basics

Comments

// Single line comment


/* Block
comment */
/** Documentation comment */

Primitive datatypes
So I lied; these are not objects
instantiated from classes.

Strings
A String object is a read-only; if you
assign a new value to it, you are
actually allocating a new object.
Can't do a direct == comparison.

boolean
char
byte
short

int
long
float
double

String myName;
myName = "Steven";
myName = "Feuerstein";
if (myName.equals(YourName))
foundFamily();

PL/SQL Advanced Techniques - page 153

Writing Loops in Java

Three kinds of loops: while, do-while and for


Note: you need squiggly brackets only if there is more than one statement in the loop body.

for (initialize; expression; step) {


lotsaStuff
}
while (expression) {
lostsaStuff
}
do {
lostsaStuff
} while (expression);

Examples:
for (indx indx=0;
indx < args.length;
indx++)
System.out.println (args[indx]);

static void processAll (Enumeration enum) {


while (enum.hasMoreElements ()) {
processIt (enum.NextElement());
System.out.println (
(String)enum.nextElement()) } }
PL/SQL Advanced Techniques - page 154

Passing Parameters

Some important differences from PL/SQL...


No default values for arguments; you must supply a value for each
parameter.
Only positional notation is supported.
If a method has no arguments, you still include the open and close
parentheses.
class PuterLingo {
private String mname;
public PuterLingo (String name) { mname = name; }
public String name () { return mname; }
}
class LotsALingos {
public static void main (String args[]) {
PuterLingo Java = new PuterLingo("Java");
System.out.println (Java.name()); }
}
PL/SQL Advanced Techniques - page 155

Exception Handling in Java

Very similar to PL/SQL; you "throw" and "catch" exceptions,


rather than raise and handle.

public static int numBytes (String filename) {


try {
if (filename.equals(""))
throw new Exception ("Filename is NULL");
Put inside
try clause
File myFile = new File (filename);

Throwing my own
exception

return myFile.length();
}
catch (SecurityException e) {
return -1;
}
catch (Exception e) {
System.out.println (e.toString());
}
}

File-related
exceptions

"WHEN OTHERS"
in Java

PL/SQL Advanced Techniques - page 156

Differences Between Java & PL/SQL

Exceptions are objects derived directly or indirectly from the


Exception class.
So you can pass them as arguments and do anything and everything
else you can do with objects.

You must inform users of your method of which exceptions


may be thrown.
Use the throws clause in your specification, as in:

public static int numBytes (String filename)


throws SecurityException, NoSuchFile {
...
}

PL/SQL Advanced Techniques - page 157

Java's Not So Tough!

You can learn enough Java in less than a week to:


Build simple classes
Leverage Java inside PL/SQL

Moving to the next level of expertise will be more of a


challenge.
Object oriented development (Java) is very different from procedural
coding (PL/SQL).

Now let's explore how you can put Java to work for you
inside PL/SQL programs.

PL/SQL Advanced Techniques - page 158

Java Stored Procedures (JSPs)


Java applet or
app. using
JDBC or
SQLJ

Oracle
Developer
client
(PL/SQL)

OCI or
Pro*C
client

Oracle 8i server

Net8

PL/SQL cover for Java


method
Java virtual machine running
Java method

VB or C++
via OO4O or
ODBC
PL/SQL Advanced Techniques - page 159

JSPs: Some sample uses


PL/SQL

extender

For example, better file I/O


RMI callouts, network communication in/out
PL/SQL

replacement

More standard language


Good performer for numeric processing tasks
Beware database I/O & string manipulation performance
Scaleable

deployment of third party code

Vertical application
Web server
XML parser/generator
PL/SQL Advanced Techniques - page 160

Creating JSP to call from PL/SQL

1. Create Java classes in your favorite IDE

2. Load into server using loadjava command-line


tool

3. Publish PL/SQL cover using AS LANGUAGE


JAVA... rather than BEGIN...END

4. Grant privileges as desired

5. Call from PL/SQL (or SQL) as if calling PL/SQL

PL/SQL Advanced Techniques - page 161

Create Java class(es)


class Corporation extends Person {
long layoffs;
long CEOCompensation;
public Corporation (
String Pname, long Playoffs, long PceoComp) {
name = Pname;
layoffs = Playoffs;
CEOCompensation = PceoComp;
}
public String toString () {
return name +
" is a transnational entity with " + layoffs +
" laid-off employees, paying its" +
" Chief Executive Officer " + CEOCompensation;
}
public static void main (String[] args) {
// A very scary company
Corporation TheGlobalMonster =
new Corporation (
"Northrup-Ford-Mattel-Yahoo-ATT",
5000000, 50000000);

Notes on Java
classes

toString method
automatically used by
System.out.println

main method is used to


test the class.

Entry points must be


public static in most
cases

Classes may call other


classes

Avoid GUI calls

System.out.println (TheGlobalMonster);
}}

person.java
PL/SQL Advanced Techniques - page 162

Upload using loadjava utility


.class file

Java
resource
file

.java file

.jar file

Oracle 8i server

loadjava

Java
class

Java
resource

Java
source

Example:
loadjava

loadjava -user scott/tiger -oci8


-resolve datacraft/bill/Hello.class

options (abbreviated)

-oci8
-resolve

loadjava will connect using OCI driver


Resolves external class references at
compile time
-resolver (shown later) Search path like CLASSPATH

PL/SQL Advanced Techniques - page 163

Publish

Example (top-level
call spec)

Syntax (simplified)

CREATE OR REPLACE FUNCTION hello_emp


(empno_in IN NUMBER)
RETURN VARCHAR2
AS LANGUAGE JAVA
NAME 'datacraft.bill.Hello.Emp(int)
return java.lang.String';
/

CREATE [ OR REPLACE ] { PROCEDURE | FUNCTION } <name>


[ RETURN <sqltype> ]
[ ( <args> ) ]
[ AUTHID { DEFINER | CURRENT_USER } ]
AS LANGUAGE JAVA
NAME '<method fullname> (<Java type fullname>, ...)
[ return <Java type fullname> ]';
PL/SQL Advanced Techniques - page 164

Call the wrapped method

Method 1: Call as if PL/SQL module


BEGIN
DBMS_OUTPUT.PUT_LINE(hello_emp(7499));
END;

Method 2: Use 8i SQL CALL statement; for example, from


SQL*Plus:
VARIABLE thename VARCHAR2(12)
CALL hello_emp(7499) INTO :thename;
PRINT :thename

CALL avoids overhead of SELECT fn FROM DUAL


jsp.sql
PL/SQL Advanced Techniques - page 165

Publishing -- more concepts


Shape

mapping

Java methods declared void become PL/SQL


procedures
Signature mismatches detected only at runtime
Type

mapping (typical)

java.lang.String

VARCHAR2

java.sql.Timestamp

DATE

java.math.BigDecimal

NUMBER

oracle.sql.STRUCT

user-defined object type

<named type>

user-defined object type

oracle.sql.REF

object REFerence

oracle.sql.ARRAY

user-defined collection type

PL/SQL Advanced Techniques - page 166

Publish in PL/SQL Package Spec

Designate Java module in PL/SQL package spec...


CREATE OR REPLACE PACKAGE hello_pkg
AS
FUNCTION hi_emp (empno_in IN NUMBER)
RETURN VARCHAR2
AS LANGUAGE JAVA
NAME 'datacraft.util.Hello.Emp(int)
return java.lang.String';
END;
/

(No package body required in this case)


PL/SQL Advanced Techniques - page 167

Publish as module in package body

...or in package body


CREATE OR REPLACE PACKAGE hello_pkg2
AS
FUNCTION hi_emp (empno_in IN NUMBER)
RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY hello_pkg2
AS
FUNCTION hi_emp (empno_in IN NUMBER)
RETURN VARCHAR2
IS LANGUAGE JAVA
NAME 'datacraft.util.Hello.Emp(int)
return java.lang.String';
END;
/
PL/SQL Advanced Techniques - page 168

Publish as object type method

Either in
spec:

or in the
body:

CREATE OR REPLACE TYPE foo_t AS OBJECT (


bar VARCHAR2(30),
MEMBER FUNCTION hello_emp RETURN VARCHAR2
IS LANGUAGE JAVA
NAME 'datacraft.util.Hello.Emp(int)
return java.lang.String');
/
CREATE OR REPLACE TYPE foo_t AS OBJECT (
bar VARCHAR2(30),
MEMBER FUNCTION hello_emp RETURN VARCHAR2);
/
CREATE OR REPLACE TYPE BODY foo_t AS
MEMBER FUNCTION hello_emp RETURN VARCHAR2
IS LANGUAGE JAVA
NAME 'datacraft.util.Hello.Emp(int)
return java.lang.String';
END;
/

PL/SQL Advanced Techniques - page 169

New DDL Statements and Roles

CREATE JAVA
Alternative to loadjava utility
Creates or replaces an Oracle library unit from Java source, class, or
resource
Can read file designated with BFILE() function

ALTER JAVA: compiles Java source, resolves Java


class references.

DROP JAVA: drops a named Java library unit

Several roles available for Java operations:


JAVAUSERPRIV, JAVASYSPRIV (needed for file IO operations),
JAVA_ADMIN, JAVAIDPRIV, JAVADEBUGPRIV
You can also grant specific privileges.

PL/SQL Advanced Techniques - page 170

Passing Oracle8 objects


Oracle8i server

Java
Javaapplication
application

Object
in O-R
table

Java
object

Harder than youd think

Three different interfacing techniques


oracle.sql.STRUCT
oracle.sql.CustomDatum
oracle.jdbc2.SQLData
PL/SQL Advanced Techniques - page 171

JPublisher utility
Database server

User-supplied
JPublisher
input file

Definition of
type or REF in
data dictionary

What

does it do?

Generates Java that


encapsulates type and
REF
Three

types of mapping
supported for methods
JPublisher

Oracle mapping
JDBC mapping
Object JDBC mapping

Generated Java
file(s) for use in
Java programs

PL/SQL Advanced Techniques - page 172

Passing object using JPub-generated Java

After generating and uploading classes with JPub,


they become available to use in mapping

Example of passing an Account_t object:


CREATE OR REPLACE PROCEDURE account_save
(new_acct IN Account_t)
IS LANGUAGE JAVA
NAME
'datacraft.bill.AccountRuntime.save
(datacraft.bill.Account_t)';
/

jspobj.sql

PL/SQL Advanced Techniques - page 173

Example: Improving File I/O

You can read/write files in PL/SQL with


UTL_FILE, but that package is very limited.

Java offers many file-related classes with much


greater capabilities.

Let's see how we can make that great Java stuff


available from within PL/SQL.

PL/SQL Advanced Techniques - page 174

Encapsulate Java Classes

You won't generally access native Java methods in


your PL/SQL wrapper.
Instead build a static method that instantiates a Java
object from the class and then invokes the relevant
method against that object.

Let's start with something simple...


The File class offers a length method that returns the
number of bytes in a file.
This is not available through UTL_FILE (though you can
get it through DBMS_LOB).

PL/SQL Advanced Techniques - page 175

My Own Java Class for File Manipulation

Accept the name of a file and return its length.


import java.io.File;
public class JFile2 {
public static long length (String fileName) {
File myFile = new File (fileName);
return myFile.length(); }
}
JFile2.java

Take each of these steps:


Import the File class to resolve reference.
Instantiate a File object for the specified name.
Call the method of choice against that object and return
the value.

PL/SQL Advanced Techniques - page 176

Build a Package over Java Method

Let's put it in a package; we will certainly want to


add more functionality over time.
I translate the Java long to a PL/SQL NUMBER.
CREATE OR REPLACE PACKAGE xfile
IS
FUNCTION length (file IN VARCHAR2) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY xfile
IS
FUNCTION length (file IN VARCHAR2) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFile.length (java.lang.String) return long';
END;
/
xfile2.pkg

PL/SQL Advanced Techniques - page 177

Mapping the Boolean Datatype

Both Java and PL/SQL support a native Boolean


datatype, so you'd expect smooth sailing. Not so!

To pass a Boolean back from Java to PL/SQL, you


will need to take these steps:
1. Convert the Java boolean to a String or number and
return that value.
2. Write a "hidden" PL/SQL wrapper function that returns
the string or number.
3. Write a "public" PL/SQL wrapper function to convert
that number to a true PL/SQL Boolean.

PL/SQL Advanced Techniques - page 178

Translate Boolean to Number

Accept the name of a file and return its length.


import java.io.File;
public class JFile3 {
public static int canRead (String fileName) {
File myFile = new File (fileName);
boolean retval = myFile.canRead();
if (retval) return 1; else return 0; }
}

JFile3.java

Translate TRUE to 1 and FALSE to 0.


And don't forget: this is a boolean primitive, not a Boolean class.

PL/SQL Advanced Techniques - page 179

Wrapper for Pseudo-Boolean function

Simple translation back to PL/SQL Boolean.


Avoid the hard-codings with named constants...
CREATE OR REPLACE PACKAGE xfile IS
FUNCTION canRead (file IN VARCHAR2) RETURN BOOLEAN;
END;
/
CREATE OR REPLACE PACKAGE BODY xfile
IS
FUNCTION IcanRead (file IN VARCHAR2) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFile3.canRead (java.lang.String) return int';
FUNCTION canRead (file IN VARCHAR2) RETURN BOOLEAN AS
BEGIN
xfile3.pkg
RETURN IcanRead (file) = 1;
JFile4.java
END;
xfile4.pkg
END;
JFile.java
/
xfile.pkg
PL/SQL Advanced Techniques - page 180

Passing Objects to Java with STRUCT

You can pass Oracle object information to Java without relying on JPub by using the STRUCT
class.

public class UnionBuster {


Obtain attributes of
the Oracle object.

public static void wageStrategy (STRUCT e)


throws java.sql.SQLException {
// Get the attributes of the labor_source object.
Object[] attribs = e.getAttributes();

// Access individual attributes by array index,


// starting with 0
String laborType = (String)(attribs[0]);
BigDecimal hourly_rate = (BigDecimal)(attribs[1]);
System.out.println (
"Pay " + laborType + " $" +
hourly_rate + " per hour");
}
}

Extract individual
attribute values from
the array.

UnionBuster.java
passobj.tst
PL/SQL Advanced Techniques - page 181

Wrapping a STRUCT-based Method

You can pass Oracle object information to Java without relying on JPub by using the STRUCT
class.

CREATE TYPE labor_source_t AS OBJECT (


labor_type VARCHAR2(30),
hourly_rate NUMBER);
/

The Oracle object


type definition

CREATE OR REPLACE PROCEDURE bust_em_with (


labor_source_in IN labor_source_t)
AS LANGUAGE JAVA
NAME 'UnionBuster.wageStrategy (oracle.sql.STRUCT)';
/
BEGIN
bust_em_with (
labor_source ('Workfare', 0));
bust_em_with (
labor_source ('Prisoners', '5'));
END;

Fully specify the


STRUCT class in
parameter list.

PL/SQL Advanced Techniques - page 182

Viewing Output from Java Methods

Java provides a "print line" method similar to


DBMS_OUTPUT.PUT_LINE: System.out.println.
Call it within methods and output will display in your Java
environment.
public class HelloAll {
public static void lotsaText (
int count) {
for (int i = 0; i < count; i++) {
System.out.println (
"Hello Hello Hello Hello Hello All!");

}}}

When called within a PL/SQL wrapper, you can redirect the


output to the DBMS_OUTPUT buffer.
HelloAll.java
Here is a good nucleus for a login.sql file:

HelloAll.tst

SET SERVEROUTPUT ON SIZE 1000000


CALL DBMS_JAVA.SET_OUTPUT (1000000);
PL/SQL Advanced Techniques - page 183

Error Handling with Java-PL/SQL

Java offers a very similar, but more robust error handling


mechanism than PL/SQL.
Exceptions are objects instantiated from the Exception class or a
subclass of it, such as java.sql.SQLException.
Instead of raising and handling, you "throw" and "catch".
Use two methods, getErrorCode() and getMessage() to obtain
information about the error thrown.

Any error not caught by the JVM (Java virtual machine) will
be thrown back to the PL/SQL block or SQL statement.
And also spew out the entire Java error stack! (at least through
8.1.5).

PL/SQL Advanced Techniques - page 184

Trapping and Identifying Errors

Currently, the entire Java stack is displayed on your screen, and you have to do
some digging to extract the Oracle error information.

SQL>
2
3
4
5
6
7
8
9

BEGIN
dropany ('TABLE', 'blip');
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (SQLCODE);
DBMS_OUTPUT.PUT_LINE (SQLERRM);
END;
/

java.sql.SQLException: ORA-00942: table or view does not exist


at oracle.jdbc.kprb.KprbDBAccess.check_error(KprbDBAccess.java)
at oracle.jdbc.kprb.KprbDBAccess.parseExecuteFetch(KprbDBAccess.java)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java)
at oracle.jdbc.driver.OracleStatement.doExecuteWithBatch(OracleStatement.java)
at oracle.jdbc.driver.OracleStatement.doExecute(OracleStatement.java)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java)
at oracle.jdbc.driver.OracleStatement.executeUpdate(OracleStatement.java)
at DropAny.object(DropAny.java:14)

DropAny.java

-29532
dropany.tst
ORA-29532: Java call terminated by uncaught Java exception: java.sql.SQLException:
getErrInfo.sp
dropany2.tst
ORA-00942: table or view does not exist

PL/SQL Advanced Techniques - page 185

Achieving PL/SQL Excellence

External Procedures

An external procedure is a 3GL routine that can serve as


the body of a PL/SQL function, procedure, or method.
Must be a shared object library on Unix or a dynamically linked
library (DLL) on Windows.

An Oracle8 feature that allowed relatively "native"


callouts to C from PL/SQL for the first time.

PL/SQL Advanced Techniques - page 186

Process & data flow


Application

Net8
Makes
request

Client
Clientoror
server-side
server-side
application
application

Calls

PL/SQL Runtime
Engine

External
External
Procedure
Procedure
Listener
Listener
spawns

PL/SQL
PL/SQL
body
body

extproc
extproc

returns
results
returns
results

External
Shared
Library
calls
routine
from

Routine
Routineinin
.DLL
.DLLoror.so
.so
file
file

returns
results

PL/SQL Advanced Techniques - page 187

External procedures: Sample uses

Send email

Invoke operating system command

Invoke custom or legacy application

Call C runtime library function

Perform admin tasks

email.sql

diskspace.sql

PL/SQL Advanced Techniques - page 188

Determine free disk space on NT (1/4)

Use pre-existing routine:


GetDiskFreeSpaceA in kernel32.dll
For given drive letter, this function returns:

sectors per cluster


bytes per sector
number of free clusters
total number of clusters
return code indicating success or failure

First, create an Oracle8 library:


CREATE OR REPLACE LIBRARY nt_kernel
AS
'c:\winnt\system32\kernel32.dll';
/

PL/SQL Advanced Techniques - page 189

Package spec (2/4)

Nothing unexpected here:


CREATE OR REPLACE PACKAGE disk_util
AS
FUNCTION get_disk_free_space
(root_path IN VARCHAR2,
sectors_per_cluster OUT PLS_INTEGER,
bytes_per_sector OUT PLS_INTEGER,
number_of_free_clusters OUT PLS_INTEGER,
total_number_of_clusters OUT PLS_INTEGER)
RETURN PLS_INTEGER;
END disk_util;
/

PL/SQL Advanced Techniques - page 190

Package body in Oracle 8.0 (3/4)

All the
magic is in
the
EXTERNAL

section

CREATE OR REPLACE PACKAGE BODY disk_util AS


FUNCTION get_disk_free_space
(root_path IN VARCHAR2,
sectors_per_cluster OUT PLS_INTEGER,
bytes_per_sector OUT PLS_INTEGER,
number_of_free_clusters OUT pls_integer,
total_number_of_clusters OUT PLS_INTEGER)
RETURN PLS_INTEGER
IS EXTERNAL
LIBRARY nt_kernel
NAME "GetDiskFreeSpaceA"
LANGUAGE C
CALLING STANDARD PASCAL
PARAMETERS
(root_path STRING,
sectors_per_cluster BY REFERENCE LONG,
bytes_per_sector BY REFERENCE LONG,
number_of_free_clusters BY REFERENCE LONG,
total_number_of_clusters BY REFERENCE LONG,
RETURN LONG);
END disk_util;

PL/SQL Advanced Techniques - page 191

Usage (4/4)
DECLARE
lroot_path VARCHAR2(3) := 'C:\';
lsectors_per_cluster PLS_INTEGER;
lbytes_per_sector PLS_INTEGER;
lnumber_of_free_clusters PLS_INTEGER;
ltotal_number_of_clusters PLS_INTEGER;
return_code PLS_INTEGER;
free_meg REAL;
BEGIN
return_code := disk_util.get_disk_free_space (lroot_path,
lsectors_per_cluster, lbytes_per_sector,
lnumber_of_free_clusters, ltotal_number_of_clusters);
free_meg := lsectors_per_cluster * lbytes_per_sector *
lnumber_of_free_clusters / 1024 / 1024;
DBMS_OUTPUT.PUT_LINE('free disk space, Mb = ' || free_meg);
diskspace.sql
END;
PL/SQL Advanced Techniques - page 192

Creating your own external procedure

1. Identify or create shared library

2. Identify or CREATE LIBRARY within Oracle

3. Map the parameters using:


EXTERNAL clause (Oracle8)
or
LANGUAGE clause (Oracle8i)

PL/SQL Advanced Techniques - page 193

1. Identify/create external routine

Prerequisite: O/S must support shared libraries

Some useful routines are pre-built in C runtime library


On Unix: /lib/libc.so
On NT: <systemroot>\system32\crtdll.dll

Building a shared library of your own requires knowledge of:


Appropriate language (typically C)
Compiler & linker

PL/SQL Advanced Techniques - page 194

2. Create the Oracle library

Syntax:

CREATE OR REPLACE LIBRARY <library name>


AS
'<full path to file>';

Assigns a programmer-defined alias to a specific shared


library file

Notes
Requires CREATE LIBRARY privilege
Does not validate directory
Cant use symbolic link

PL/SQL Advanced Techniques - page 195

3. Map the parameters


This can get complicated, because
Must pass extra
parameters to designate:
NULL/NOT NULL state
String length
Maximum allocated length

Six choices of parameter mode

IN
RETURN
IN BY REFERENCE
RETURN BY REFERENCE
OUT
IN OUT

These details are better suited for close reading


PL/SQL Advanced Techniques - page 196

Achieving PL/SQL Excellence

Oracle8i New Features


Autonomous Transactions
Invoker Rights Model
Row Level Security

PL/SQL Advanced Techniques - page 197

Oracle8i New Features

Oracle8i offers a number of PL/SQL-specific features that


give you tremendous additional flexibility and capability.
And the learning curve to take advantage of these features is
generally not too steep.

Autonomous Transactions

The Invoker Rights Model

Row level security

PL/SQL Advanced Techniques - page 198

Autonomous Transactions

Prior to Oracle8i, a COMMIT or ROLLBACK in any program


in your session committed or rolled back all changes in your
session.
There was only one transaction allowed per connection.

With Oracle8i, you can now define a PL/SQL block to


execute as an "autonomous transaction".
Any changes made within that block can be saved or reversed
without affecting the outer or main transaction.
CREATE OR REPLACE PROCEDURE loginfo (
code IN PLS_INTEGER,
msg IN VARCHAR2)
AS
PRAGMA AUTONOMOUS_TRANSACTION;

PL/SQL Advanced Techniques - page 199

When to Use Autonomous Transactions

Reusable Application Components


ATs are more or less required in the new distributed application
architecture of the Internet.
One component should not have any impact (esp. something like a
COMMIT) on other components.

Logging Mechanism
Commonly developers lot to database tables, which can cause all sorts
of complications: your log entry becomes a part of your transaction.
Now you can avoid the complexities (need for ROLLBACK TO savepoints
and so on).

Tracing Code Usage


Build retry mechanisms, software usage meter, etc.

Call functions within SQL that change the database.


PL/SQL Advanced Techniques - page 200

Logging with ATs

Don't forget that ROLLBACK in the exception section!

CREATE OR REPLACE PACKAGE BODY log


IS
PROCEDURE putline (
code_in IN INTEGER, text_in IN VARCHAR2
)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO logtab
VALUES (code_in, text_in,
SYSDATE, USER, SYSDATE, USER,
rec.machine, rec.program
);
COMMIT;
EXCEPTION WHEN OTHERS THEN ROLLBACK;
END;
END;

Avoid interdependencies with


the main transaction.

While we're at it, let's


add some session
information.
logger.sp
log81.pkg
log81*.tst

retry.pkg
retry.tst

PL/SQL Advanced Techniques - page 201

Tips and Gotchas with ATs

An AT program must COMMIT or ROLLBACK before terminating, or an


error is raised.

The AT PRAGMA can be used only with individual programs and toplevel anonymous blocks.
You cannot define an entire package as an AT.
You cannot define a nested anonymous block to be an AT.

The AT PRAGMA goes in the body of packages.


You cannot tell by looking at the package spec if you are calling ATs or not -and this info is not available in the data dictionary.

Any changes committed in an AT are visible in the outer transaction.


You can use the SET TRANSACTION ISOLATION LEVEL SERIALIZABLE to
indicate that you do not want the changes visible until the outer transaction
commits.
Place the SET TRANSACTION statement in the outer transaction.
autonserial.sql
auton_in_sql.sql
autontrigger*.sql

PL/SQL Advanced Techniques - page 202

The Invoker Rights Model

Prior to Oracle8i, whenever you executed a stored program,


it ran under the privileges of the account in which the
program was defined.
This is called the

Definer Rights Model

With Oracle8i, you can now decide at compilation time


whether your program or package will execute in the
definer's schema (the default) or the schema of the invoker
of the code.
This is called the

Invoker Rights Model


PL/SQL Advanced Techniques - page 203

About Definer Rights

Allows you to centralize


access to and control of
underlying data
structures.
Ignores roles and relies
on directly-granted
privileges.
But it can be a source of
confusion and
architectural problems.

OE Code

Sam_Sales

Order_Mgt
Place

Close Old
Orders

Cancel

OE Data

Orders

Cannot alter
table directly.

Note: Oracle built-in packages have long had the capability of


running under the invoker's authority.
PL/SQL Advanced Techniques - page 204

Problems with Definer Rights

Deployment & maintenance


Must install module in all remote databases where needed
In some databases, each user has own copy of table(s), requiring
copy of stored module

Security
No declarative way to restrict privileges on certain modules in a
package -- it's all or nothing, unless you write code in the package to
essentially recreate roles programmatically.
Can bypass 8is fine-grained access features (see the DBMS_RLS
package)
Difficult to audit privileges

Sure would be nice to have a choice...and now you do!

PL/SQL Advanced Techniques - page 205

Oracle8i Invoker Rights Syntax

For top level modules:


CREATE [ OR REPLACE ] <module type>
[ AUTHID { DEFINER | CURRENT_USER } ]
AS ...

For modules with separate spec and body, AUTHID goes


only in spec, and must be at the package level.

Synonyms may be necessary if modules use object names


not qualified by schema.
In other words, do what it takes to get the code to compile. You could
also create local, "dummy" objects.
At run-time, the objects referenced may well be different from those
against which it compiled.

PL/SQL Advanced Techniques - page 206

"Reflection" Capability of Invoker Rights

With invoker rights, you can execute code owned by another


schema, yet have all references to data structures "reflect
back" into your own schema.

Central
Central Code
Code schema
schema
PACKAGE acct_mgr
make
AUTHID
CURRENT_USER

...FROM accounts
WHERE...

modify
destroy

User/Data
User/Data schema
schema
PROCEDURE mng_account IS
BEGIN
...
code.acct_mgr.destroy(...);
END;

accounts table

PL/SQL Advanced Techniques - page 207

Tips and Gotchas for Invoker Rights

Does not apply to code objects, only data


When your stored code references another stored code element, the
definer rights model is always applied to resolve the reference.
Both static and dynamic SQL is supported.

Once a definer rights program is called, all other calls in


stack are resolved according to definer rights.
AUTHID CURRENT_USER is ignored.

Information about the rights model is not available in the


data dictionary

What if you want to maintain a single version of your code


for both pre-Oracle8i and Oracle8i installations, taking
advantage of the invoker rights model whenever possible?
A creative use of SQL*Plus substitution variables comes in very
invdefinv.sql
handy. Note: cannot use with wrapped code.
oneversion.sql
PL/SQL Advanced Techniques - page 208

When to Invoke Invoker Rights

You want to reuse the same code among many users, but
you want their own directly-granted privileges to determine
access.
National HQ

Chicago
Schema
stolenlives

Check City
Statistics

New York
Schema
stolenlives

Especially handy with dynamic SQL.


Share a program that uses dynamic SQL (whether DBMS_SQL or the
new native implementation) and you end up taking the most
unexpected DDL and DML detours.
authid.sql
whichsch*.sql

PL/SQL Advanced Techniques - page 209

Combining Invoker & Definer Rights

Rely on invoker rights to allow centralized code to work with


schema-specific data.

Rely on definer rights to access centralized data from any schema.

HQ
Chicago

stolenlives

Check City
Statistics

Analyze
Pattern

New York

stolenlives

perpetrators

perp.sql
PL/SQL Advanced Techniques - page 210

Row Level Security

DBMS_RLS
Row-Level
Security

PL/SQL Advanced Techniques - page 211

Row-Level Security with DBMS_RLS

Oracle8i offers a new package, DBMS_RLS, with which to


implement automated row-level security (also referred to as
"fine grained access control").
Row Level Security

The establishment of
security policies on
(restricted access to)
individual rows of a table.

Prior to Oracle8i, you could


achieve this only partially
through the use of views.

The DBMS_RLS package


(along with "system
contexts") now allow you to
do so in a foolproof manner.
PL/SQL Advanced Techniques - page 212

Components Needed for RLS

System Contexts
A new feature in Oracle8i, the system context is a named set of attributevalue pairs global to your session.

Security Policy Packages


You will need to write a package containing functions that establish the
security policies to be applied to a particular table.

Database Logon Triggers


I want to make sure that when a person logs in, their context information is
set properly.

DBMS_RLS
Use programs in DBMS_RLS to associate your security policies with tables.

Let's step through a simple example to see how all these pieces tie
together.

PL/SQL Advanced Techniques - page 213

A National Health Care System

The year is 2010.


A massive, popular uprising in the United States has forced the
establishment of a national healthcare system. No more for-profit
hospitals pulling billions of dollars out of the system; no more private
insurance companies soaking up 30 cents on the dollar; all children
are vaccinated; all pregnant women receive excellent pre-natal care.

We need a top-notch, highly secure database for NHCS. The


main tables are patient, doctor, clinic and regulator.

Here are some rules:


Doctors can only see patients who are assigned to their clinic.
Regulators can only see patients who reside in the same state.
Patients can only see information about themselves.
fgac.sql

PL/SQL Advanced Techniques - page 214

Set the Context

Define a context in the database, and create a procedure


that will set the context attributes (type and ID) upon login.

CREATE CONTEXT patient_restriction USING nhc_pkg;


PROCEDURE set_context
IS
This is a simplification. See
CURSOR doc_cur IS
fgac.sql for logic that identifies
SELECT doctor_id FROM doctor
different types of people and
WHERE schema_name = USER;
sets the context accordingly.
doc_rec doc_cur%ROWTYPE;
BEGIN
OPEN doc_cur; FETCH doc_cur INTO doc_rec;
DBMS_SESSION.SET_CONTEXT (
'patient_restriction', c_person_type_attr, 'DOCTOR');
DBMS_SESSION.SET_CONTEXT (
'patient_restriction', c_person_id_attr, doc_rec.doctor_id);
END;
PL/SQL Advanced Techniques - page 215

Define the Predicate

A predicate is a string that will be appended to the WHERE


clause of the table to which this security policy is associated
(see next page).
FUNCTION person_predicate (
schema_in VARCHAR2, name_in VARCHAR2) RETURN VARCHAR2
IS
l_context VARCHAR2(100) :=
Extract the context
SYS_CONTEXT (c_context, c_person_type_attr);
information for this
retval VARCHAR2(2000);
connection.
BEGIN
IF l_context = 'DOCTOR'
THEN
retval := 'home_clinic_id IN
(SELECT home_clinic_id FROM doctor
We need a different string
WHERE doctor_id = SYS_CONTEXT (''' ||
to modify the WHERE
c_context || ''', ''' ||
clause for each type of
c_person_id_attr || '''))';
person.
PL/SQL Advanced Techniques - page 216

Create the Security Policy

Use the DBMS_RLS.ADD_POLICY procedure.


The following procedure call specifies that the WHERE clause of any
query, update or delete against the SCOTT.PATIENT table will be
modified by the string returned by the person_predicate function of
the SCOTT.nhc_pkg package.
BEGIN
DBMS_RLS.ADD_POLICY
OBJECT_SCHEMA
OBJECT_NAME
POLICY_NAME
FUNCTION_SCHEMA
POLICY_FUNCTION
STATEMENT_TYPES
END;

(
=>
=>
=>
=>
=>
=>

'SCOTT',
'patient',
'patient_privacy',
'SCOTT',
'nhc_pkg.person_predicate',
'SELECT,UPDATE,DELETE');

PL/SQL Advanced Techniques - page 217

Create Logon Trigger, Setting Context

By setting the context in the logon trigger, we guarantee that the context is
set (and the predicate applied) no matter which product is the entry point to
Oracle.

CREATE OR REPLACE TRIGGER set_id_on_logon


AFTER LOGON ON DATABASE
BEGIN
nhc_pkg.set_context;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE (
'Error ' || SQLCODE ||
' setting context for ' || USER');
END;

Exception handling in
trigger is critical! If you
allow an exception to go
unhandled, logon is
disabled.

fgac.sql

PL/SQL Advanced Techniques - page 218

Queries Transparently Filtered

What you see is determined automatically by who you are.


Context Information for "SWALLACE":
Type: DOCTOR
Doctor sees only her
ID: 1060
patients.
Predicate:
home_clinic_id IN
(SELECT home_clinic_id FROM doctor
WHERE doctor_id = SYS_CONTEXT ('patient_restriction', 'person_id'))
Patients Visible to "SWALLACE":
CSILVA - Chris Silva - IL
VSILVA - Veva Silva - IL

Context Information for "CSILVA":


Type: PATIENT
ID:
Predicate: schema_name = 'CSILVA'

Patient sees only


himself.

Patients Visible to "CSILVA":


CSILVA - Chris Silva - IL
PL/SQL Advanced Techniques - page 219

Take Full Advantage of PL/SQL!


January...

February...

It's so easy to fall into a


rut with a programming
language.

March...

April...

May...

You learn what you


need to get the job
done, and you use only
what you know.

Jump out of
your rut - and
play a new tune
with PL/SQL!

PL/SQL Advanced Techniques - page 220

Achieving PL/SQL Excellence

Appendices
(a.k.a., If time permits)

UTL_FILE file IO

DBMS_JOB Job scheduling

DBMS_PIPE Pipe-based communication

DBMS_UTILITY the kitchen sink package

PL/SQL Advanced Techniques - page 221

Built-in Packages: UTL_FILE

UTL_FILE
Server-side
File I/O
Application
FOPEN
GET_LINE
PUT_LINE
...

Physical Files

PL/SQL Advanced Techniques - page 222

UTL_FILE: Server-side I/O

Allows you to read from and write to operating system


files on the database server.

A "version 1" (fairly primitive) utility...


Maximum of 1023 bytes per line (read or write) until Oracle 8.0.5,
when it jumps to 32K.
No higher-level file operations supported (change privileges,
delete, copy, random access to contents).
Limitations on files you can access (no mapped files, no use of
environmental variables).

But you can read lines from a file and write lines to a file.

PL/SQL Advanced Techniques - page 223

UTL_FILE Module Outline


FCLOSE

Close the specified file

FCLOSE_ALL

Close all open files in your session

FFLUSH

Flush all data from the UTL_FILE buffer to your file

FOPEN

Open a file

GET_LINE

Get the next line from a file

IS_OPEN

Returns TRUE if the file is open (sort of)

NEW_LINE

Insert a new line character in file at the end of current line

PUT

Puts text into the UTL_FILE buffer

PUT_LINE

Puts text and new line character into UTL_FILE buffer.

PUTF

Puts formatted text into the buffer


PL/SQL Advanced Techniques - page 224

Authorizing Directory Access

Oracle requires you to list explicitly those directories you wish


to be able to read/write with UTL_FILE.
You do this by adding lines to the instance parameter file.

Follow these rules to avoid many headaches.


Use a separate entry for each directory (and subdirectory; there is no
subdirectory recursion).
No single or double quotes around directory, no trailing delimiter.
utl_file_dir = /tmp
utl_file_dir = /accounts/newdev

And don't do either of the following:


utl_file_dir = .
Allows read &
write for current
directory.

utl_file_dir = *
Allows read &
write for any
directory.
PL/SQL Advanced Techniques - page 225

Test UTL_FILE Access

About the hardest part to working with UTL_FILE is simply


getting started.

So before you write anything fancy, modify your


initialization file, restart your database, and then run the
following test script (it can't get much simpler than this):
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
/* Change the directory name to one to which you at least
|| THINK you have read/write access.
*/
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W');
UTL_FILE.PUT_LINE (fid, 'hello');
UTL_FILE.FCLOSE (fid);
END;
/
utlfile.tst

PL/SQL Advanced Techniques - page 226

Opening a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W');
END;

Specify file location, name and operation type.


Types are 'R' for Read, 'W' for Write and 'A' for Append.

The FOPEN function returns a record ("file handle") based on the


UTL_FILE.FILE_TYPE.
Currently contains a single ID field.

Maximum of 10 files may be opened in each user session.


Test to see if file is open with the IS_OPEN function.
In actuality, this function simply returns TRUE if the file handle's id field is NOT
NULL. Not much of a test...

PL/SQL Advanced Techniques - page 227

Reading from a File


DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'R');
UTL_FILE.GET_LINE (fid, myline);
UTL_FILE.FCLOSE (fid);
END;

Can only read from a file opened with the "R" mode.

Lines in the file cannot be longer than 1023 bytes in length.


In Oracle8 Release 8.0.5 and above, the ceiling is raised to 32K.

The NO_DATA_FOUND exception is raised if you read past


the end of the file.
You might want to build your own GET_LINE which handles the
exception and returns an EOF Boolean status flag.
getnext.sp

PL/SQL Advanced Techniques - page 228

Writing to a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W');
UTL_FILE.PUT_LINE (fid, 'UTL_FILE');
UTL_FILE.PUT (fid, 'is so much fun');
UTL_FILE.PUTF (fid, ' that I never\nwant to %s', '&1');
UTL_FILE.FCLOSE (fid);
END;
UTL_FILE
is so much fun that I never
want to stop

Resulting Text

You can use PUT, PUT_LINE or PUTF.


PUTF is like the C printf program, allowing for some formatting.

Call FFLUSH to make sure that everything you have written


to the buffer is flushed out to the file.
The file buffers are automatically flushed when you close a file or exit
your session.
PL/SQL Advanced Techniques - page 229

Closing a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'R');
UTL_FILE.GET_LINE (fid, myline);
UTL_FILE.FCLOSE (fid);
EXCEPTION
WHEN UTL_FILE.READ_ERROR
THEN
UTL_FILE.FCLOSE (fid);
END;

If you do not close the file, you will not see the data you have
(supposedly) written to that file.

You can close a single file with FCLOSE or all open files with
FCLOSE_ALL.

You should close files in exception handlers to make sure that files are not
left "hanging" open.
PL/SQL Advanced Techniques - page 230

Error Handling in UTL_FILE


PACKAGE UTL_FILE
IS
invalid_path
invalid_mode
invalid_filehandle
invalid_operation
read_error
write_error
internal_error
END;

EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;

UTL_FILE relies on a combination of user-defined exceptions and


STANDARD exceptions to communicate errors.
NO_DATA_FOUND when you try to read past the end of the file.
UTL_FILE-named exceptions in other cases.

You have to take special care to trap and handle the named exceptions.
They all share a common SQLCODE of 1.

PL/SQL Advanced Techniques - page 231

Recommended Exception Section


EXCEPTION
WHEN UTL_FILE.INVALID_PATH
THEN recNgo (PLVfile.c_invalid_path); RAISE;
WHEN UTL_FILE.INVALID_MODE
THEN recNgo (PLVfile.c_invalid_mode); RAISE;
WHEN UTL_FILE.INVALID_FILEHANDLE
THEN recNgo (PLVfile.c_invalid_filehandle); RAISE;
WHEN UTL_FILE.INVALID_OPERATION
THEN recNgo (PLVfile.c_invalid_operation); RAISE;
WHEN UTL_FILE.READ_ERROR
THEN recNgo (PLVfile.c_read_error); RAISE;
WHEN UTL_FILE.WRITE_ERROR
THEN recNgo (PLVfile.c_write_error); RAISE;
WHEN UTL_FILE.INTERNAL_ERROR
THEN recNgo (PLVfile.c_internal_error); RAISE;
END;

utlflexc.sql

Trap locally by name; record the error, translating the generic userdefined exception into an understandable message.

Re-raise exception if you want it to propagate from that block.


PL/SQL Advanced Techniques - page 232

Built-in Packages: DBMS_JOB

DBMS_JOB
Scheduled Execution
of Stored Procedures
Application

Oracle Job Queue


Subsystem
SUBMIT
RUN

DBA_JOBS

REMOVE
...
Background
Process

PL/SQL Advanced Techniques - page 233

Overview of DBMS_JOB

DBMS_JOB provides an API to the Oracle job queues, which in


turn offers job scheduling capabilities within the Oracle Server.

Built by Oracle to support snapshots and replication.

Made its debut in PL/SQL Release 2.1, but only publicly


available and supported in PL/SQL Release 2.2.

You can use DBMS_JOB to:

Replicate data between different database instances.


Schedule regular maintenance on instances.
Schedule large batch jobs to run on "off hours".
Create a listener program to poll the contents of a pipe and take action.
Spawn background processes to avoid blocking client process.

PL/SQL Advanced Techniques - page 234

DBMS_JOB Module Outline


BROKEN

Marks the job as either FIXED or BROKEN.


Broken jobs will not run as scheduled.

CHANGE

Changes one or all attributes of a job.

INTERVAL

Changes the interval between executions of a job.

ISUBMIT

Submits a job to the queue using a predefined job number.

NEXT_DATE

Changes when a queued job will run.

REMOVE

Removes the job from the queue.

RUN

Forces immediate execution of the specified job number.

SUBMIT

Submits a job to the queue returning a unique job number.

USER_EXPORT

Returns the job string for a job number.

WHAT

Changes the job string of a job.


PL/SQL Advanced Techniques - page 235

Submitting a Job
DECLARE
job# BINARY_INTEGER;
BEGIN
DBMS_JOB.SUBMIT
(job#, 'calculate_totals;', SYSDATE, 'SYSDATE + 1');
END;

A job is a call to a stored procedure or an anonymous block


It must end with a semi-colon and can contain hard-coded arguments.

When you submit a job, you specify the date on which it should next execute, and then the jobs execution
interval (frequency of execution).
In the above example, I run calculate_totals immediately and then on a daily basis thereafter. Notice that the start time is
a DATE expression, while the interval is a string (this is dynamic PL/SQL!)

You can also call DBMS_JOB.ISUBMIT and supply the job number, instead of having DBMS_JOB generate one
for you.

PL/SQL Advanced Techniques - page 236

Submit Job Example


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)'
);
p.l (v_jobno);
END;
/

This block submits a job that uses a built-in procedure,


DBMS_DDL.ANALYZE_OBJECT, to analyze a specific table every
evening at midnight.
PL/SQL Advanced Techniques - page 237

DBMS_JOB.ISubmit Job Example


BEGIN
DBMS_JOB.ISUBMIT
(job => 1
,what => 'my_job1(''string_parm_value'',120);'
,next_date => SYSDATE + 1/24
,interval => 'SYSDATE +1');
DBMS_JOB.ISUBMIT
(2, 'my_job2(date_IN=>SYSDATE);'
,SYSDATE+1,'SYSDATE+10/1440');
DBMS_JOB.ISUBMIT(3,'BEGIN null; END;',SYSDATE,null);
END;
/

This block submits three jobs to the job queue, numbered 1,2, and 3.
Job 1 passes a string and number into procedure MY_JOB1, runs it in one hour and executes every day
thereafter.
Job 2 passes a date into procedure MY_JOB2, executes for the first time tomorrow and every 10 minutes
thereafter.
Job 3 is a PL/SQL block which does nothing, executes immediately, and will be removed from the queue
automatically.

PL/SQL Advanced Techniques - page 238

Specifying Job Times & Frequencies

Probably the most complicated part of using DBMS_JOB is to


get the string expression of the job interval right.
Since it's a string, you must use 2 single quotes to embed strings.
Use date arithmetic to request intervals smaller than a day.
Every hour

'SYSDATE + 1/24'

Every Sunday at 2
AM

'NEXT_DAY (TRUNC (SYSDATE), ''SATURDAY'')


2/24'

First Monday of
each quarter, at 9
AM

'NEXT_DAY (
ADD_MONTHS (TRUNC (SYSDATE, ''Q''), 3),
''MONDAY'') + 9/24'

Every Monday,
Wednesday and
Friday at 6 PM

'TRUNC (LEAST (
NEXT_DAY (SYSDATE, ''MONDAY''),
NEXT_DAY (SYSDATE, ''WEDNESDAY''),
NEXT_DAY (SYSDATE, ''FRIDAY''))) + 18/24'

PL/SQL Advanced Techniques - page 239

Other Job Queue Operations

Remove a job from the queue.


Only if the job was submitted from same schema

BEGIN
FOR rec IN (SELECT * FROM USER_JOBS) LOOP
DBMS_JOB.REMOVE (rec.job);
END LOOP;

Remove all jobs


for current
schema.

Run a job immediately.


Performs an implicit COMMIT in current session.

DBMS_JOB.RUN (my_job#);

Export jobs from the queue.


Produces a string that can be used to recreate an existing job in the job queue.
Uses DBMS_JOB.ISUBMIT, retaining current job number.

expjob.sql

PL/SQL Advanced Techniques - page 240

Job Queue Views

You can also view the job queue by looking at the


DBA_JOBS_RUNNING, DBA_JOBS and USER_JOBS views.
The following query blends job information with session information
to display currently-executing jobs, who owns them and when they
began.
SELECT

FROM

jr.job
,username
,jr.this_date
,what

job_id
username
start_date
job_definition

DBA_JOBS_RUNNING
,DBA_JOBS
,V$SESSION

WHERE s.sid = jr.sid


AND jr.job = j.job
ORDER BY jr.this_date;

jr
j
s
showjobs.sql

PL/SQL Advanced Techniques - page 241

Setting up the Job Facility

Make sure that the correct access is set up for the


DBMS_JOB package.
The default is PUBLIC access. You will have to take special DBA
action if you want to restrict who can run jobs.

You will need to set three parameters in the init.ora


(initialization) file for your database instance:
job_queue_processes=N where n is the number of concurrent
background processes permitted. The valid range is 0 through 36.
job_queue_interval=N where N is the interval in seconds to check
the job queue. The valid range is 1 to 3600 (a maximum, therefore, of
one hour).

PL/SQL Advanced Techniques - page 242

Error Handling with DBMS_JOB

What if your stored procedure fails?


After 16 attempts, the job facility will mark your job as broken.
Do you want it to try 16 times?
In addition, if your failure raises an unhandled exception, it may
cause the background processes to fail and no longer run any
jobs at all.

To avoid unexpected and unhandled failures of scheduled


jobs:
Always use the RUN built-in to execute your job in a test mode.
Then you can go ahead and submit it.
Always include a WHEN OTHERS exception handler in your job
program which traps any and all problems and automatically sets
the job status to broken.

PL/SQL Advanced Techniques - page 243

Stopping the Job When It Fails


PROCEDURE calc_totals IS
BEGIN
...
EXCEPTION
WHEN OTHERS
THEN
job# := job_pkg.job (calc_totals)
DBMS_JOB.BROKEN (job#, TRUE);
job_pkg.log (calc_totals, FAIL);
END;

spacelog.sql
showspc.sql

The WHEN OTHERS exception handler of the calc_totals


procedure traps any kind of failure.
Obtains the job number from a packaged function by passing the name of
the procedure.
Uses a call to BROKEN to set the status of the job to broken.
Calls log program of package to record that failure took place.
Now the job facility will not try to run this program again. You can go in
and fix the problem.
PL/SQL Advanced Techniques - page 244

"Good to Knows" for DBMS_JOB

You may find it useful to always wrap your stored procedure call (the
what) inside a BEGIN-END block.
We've noticed some aberrant, difficult to reproduce behavior at times with
"straight" procedure calls.

You will need to fully-qualify all database links (with user name and
password) to get them to work properly.

If you find that you submit a job to run immediately and it does not start,
perform a COMMIT after your submit.

When a job runs, it picks up the current execution environment for the
user.

You can use the DBMS_IJOB to manage the jobs of other users.
DBMS_JOB only allows you to modify characteristics and behavior of jobs
submitted by the current schema.

PL/SQL Advanced Techniques - page 245

Built-in Packages: DBMS_PIPE

DBMS_PIPE
Inter-session
Communication

PL/SQL Advanced Techniques - page 246

DBMS_PIPE Overview

Allows communication between different Oracle sessions


through a pipe in the RDBMS Shared Global Area.
Operates outside of database transaction limitations.

Uses for DBMS_PIPE include:


Parallelization of program execution. Oracle uses database pipes
to parallelize database operations. You can parallelize your own
code.
More sophisticated debugging inside PL/SQL programs (work
around fundamental limitations of DBMS_OUTPUT).
Interface PL/SQL-database activities with operating system
functions and programs written in other languages.
Perform and commit DML in a separate transaction space from
your main transaction.

PL/SQL Advanced Techniques - page 247

Architecture of DBMS_PIPE
Shared Global Area
Sue
Bob

Message Buffer

Message Buffer

Session A

Session B

A pipe is a named object that uses the System Global Area


to provide a non-transaction based conduit of information.
The pipe sends/receives a message, which can be composed of
one or more separate packets, using a maximum of 4096 bytes.
Names can be up to 128 chars long (do not use names beginning
with ORA$. They are reserved for Oracle use).
PL/SQL Advanced Techniques - page 248

Processing Flow of DBMS_PIPE

Construct a message from packets of information.


Each packet can be a string, date, number, ROWID or RAW.
There is just one message buffer per session.

Send the message to a named pipe.


If there isnt currently room in the pipe, you can wait for up to
1000 days (or 86400000 seconds) for the pipe to be cleared.
This is the default, so you should always specify a timeout period.

Receive a message from that pipe.


You can wait for up to 1000 days for a message.

Unpack the message packets and take action.


Separate out the individual packets in the message (again: string,
date or number).
pipex1.sql
pipex2.sql

PL/SQL Advanced Techniques - page 249

DBMS_PIPE Module Outline


Module Name

Description

CREATE_PIPE

Creates a PUBLIC or PRIVATE pipe.

NEXT_ITEM_TYPE Returns the datatype of the next item in the piped message.
PACK_MESSAGE Packs an item into the message buffer for your session.
PURGE

Empties the contents of a pipe into your local buffer freeing it for
removal, making it a candidate for removal with a LRU algorithm.

RECEIVE_MESSAGE Receives a message from pipe and copies to local buffer.


REMOVE_PIPE

Removes a pipe explicitly created via CREATE_PIPE.

RESET_BUFFER

Clears your buffer so that PACK_MESSAGE and


UNPACK_MESSAGE can work from the first item.

SEND_MESSAGE Sends contents of message buffer to the specified pipe.


UNIQUE_SESSION_NAME Returns name that is unique among all sessions in the database.
UNPACK_MESSAGE
Unpacks the next item from the local message buffer and deposits it into
the specified local variable.
PL/SQL Advanced Techniques - page 250

Creating Public and Private Pipes

There are two ways to create pipes: implicitly and explicitly.


Send messages to non-existent pipe implicitly create it.
Use CREATE_PIPE to create a pipe explicitly.
PROCEDURE newpipe IS
stat INTEGER;
BEGIN
stat := DBMS_PIPE.CREATE_PIPE (
pipename => 'bbs', maxpipesize => 20000, private => TRUE);

An explicit pipe can be private (accessible only to


sessions with matching userID or SYSDBA privileges).
Specify TRUE for private argument of CREATE_PIPE.

A public pipe is accessible as long as you know its name.


Implicitly-created pipes are always public.
PL/SQL Advanced Techniques - page 251

Sending a Message
Provide pipe name,
seconds you will wait,
and new max pipe size
(you can make it bigger,
but not smaller).

FUNCTION send_message
(pipename IN VARCHAR2,
timeout IN INTEGER DEFAULT maxwait,
maxpipesize IN INTEGER DEFAULT 8192)
RETURN INTEGER;

FOR month_num IN 1 .. 12
LOOP
DBMS_PIPE.PACK_MESSAGE (
total_sales (month_num));
END LOOP;
pipe_stat :=
DBMS_PIPE.SEND_MESSAGE (
monthly, 60, 10 * 4096);
IF pipe_stat != 0
THEN
RAISE could_not_send;

Fill your message buffer


with packets of data.

Send to "monthly" pipe,


waiting up to 1 minute,
expanding pipe size to 40
Kbytes.

Status of 0 means message was sent.


PL/SQL Advanced Techniques - page 252

Receiving and Unpack a Message


FUNCTION receive_message
(pipename IN VARCHAR2, timeout IN INTEGER DEFAULT maxwait)
RETURN INTEGER;

First you pull the message from the pipe and place it in buffer
with RECEIVE_MESSAGE.
Specify pipe and number of seconds you will wait before you time out.
Pipe status of 0 means the message was read successfully.
PROCEDURE unpack_message (item OUT VARCHAR2);
PROCEDURE unpack_message (item OUT NUMBER);
PROCEDURE unpack_message (item OUT DATE);

Then you call UNPACK_MESSAGE to extract individual packets from


the message.
You need to know the datatype of packet or check it using NEXT_ITEM_TYPE.

PL/SQL Advanced Techniques - page 253

A Pipe-based Listener Program

Program wakes up every N seconds to read the data from the pipe and analyze results from
assembly line (an intentional "infinite loop"!).

PROCEDURE analyze_assembly_data (every_n_secs IN INTEGER)


IS
pipe_status INTEGER;
Wait up to N
prod_total NUMBER;
seconds for the
BEGIN
next report.
LOOP
pipe_status :=
DBMS_PIPE.RECEIVE_MESSAGE (
'production', every_n_secs);
If I got something,
pass it on to the
IF pipe_status = 0
computation
THEN
program.
DBMS_PIPE.UNPACK_MESSAGE (prod_total);
analyze_production (SYSDATE, prod_total);
ELSE
RAISE_APPLICATION_ERROR (
Stop process if data
-20000, 'Production data unavailable.');
not received.
END IF;
END LOOP;
END;
PL/SQL Advanced Techniques - page 254

Unpacking the Message


FUNCTION next_item_type RETURN INTEGER;

Returns one of the following values:


Value
0
6
9
11
12
23

Description or Data type


No more items in buffer
NUMBER
VARCHAR2
ROWID
DATE
RAW

dbpipe.sql
dbpipe.tst

Since a message can be composed of packets of different


datatypes, you have to make sure that you unpack a packet
into the right kind of variable. Either:
You know the datatype and therefore can hard-code the correct
variable into the call to UNPACK_MESSAGE.
Or you use the built-in NEXT_ITEM_TYPE to tell you in advance the
datatype of the next packet in the message and take appropriate
action.
PL/SQL Advanced Techniques - page 255

Parallelizing Your Code with Pipes

Oracle uses DBMS_PIPE to improve RDBMS performance;


you can do the same for your application if:
You have multiple CPUs available.
You have processes which can run in parallel.

Suppose I want to calculate my net profit. In order to do so I


must first compute total sales, total office expenses and
total compensation.
These programs each take 15 minutes, but are not dependent on
each other.

Without pipes, I must execute them sequentially and incur


an elapsed time of 45 minutes before I calculate the profits.
The CEO is decidedly unhappy about the delay.

PL/SQL Advanced Techniques - page 256

Sequential vs. Parallel Execution


Sequential Execution: Maximum Elapsed Time
S
t
a
r
t

Process A

Process B

Process C

E
n
d

Parallel Execution: Minimum Elapsed Time


Start

Process A

Process B

Process C

End
PL/SQL Advanced Techniques - page 257

Parallelizing PL/SQL Execution


BEGIN
kick_off_sales_calc;
kick_off_exp_calc;
kick_off_totcomp_calc;
wait_for_confirmation;
calculate_net_profits;
END;

The kick off programs pass their messages to separate pipes.


Other programs running in the background grab the messages and start their
corresponding calculation program.
When each program is complete, it puts a message (perhaps even a value) into the
pipe.

The wait program waits till it receives a message from each program.
Then net profits can be computed -- in a much-decreased elapsed time.

PL/SQL Advanced Techniques - page 258

Code to Kick Off and Calculate


PROCEDURE kick_off_sales_calc IS
stat INTEGER;
BEGIN
DBMS_PIPE.PACK_MESSAGE (1995);
stat := DBMS_PIPE.SEND_MESSAGE (sales);
END;

Send message to start


calculations.

PROCEDURE calculate_sales IS
stat INTEGER;
BEGIN
stat := DBMS_PIPE.RECEIVE_MESSAGE (sales);
IF stat = 0
THEN
lots_of_number_crunching;
DBMS_PIPE.PACK_MESSAGE (sales$);
stat := DBMS_PIPE.SEND_MESSAGE (sales);
ELSE
DBMS_PIPE.PACK_MESSAGE (NULL);
stat := DBMS_PIPE.SEND_MESSAGE (sales);
END IF;
END;

Receive the year,


calculate sales, and send
back the results.

PL/SQL Advanced Techniques - page 259

Waiting for Confirmation


PROCEDURE wait_for_confirmation
IS
stat INTEGER;
BEGIN
stat := DBMS_PIPE.RECEIVE_MESSAGE (sales);
DBMS_PIPE.UNPACK_MESSAGE (sales$);
stat := DBMS_PIPE.RECEIVE_MESSAGE (offexp);
DBMS_PIPE.UNPACK_MESSAGE (offexp$);

Wait for all calculations to


finish.
The order in which you
wait is insignificant.

stat := DBMS_PIPE.RECEIVE_MESSAGE (comp);


DBMS_PIPE.UNPACK_MESSAGE (comp$);
END;
PROCEDURE calculate_net_profits IS
BEGIN
net_profits := sales$ - offexp$ - comp$;
END;

Perform final
calculation.

parallel.sql

PL/SQL Advanced Techniques - page 260

Other DBMS_PIPE Examples

Simple trace package that sends it output either to screen or to pipe.


Demonstrates the use of toggles and switches in packages.

watch.pkg
p_and_l.pkg

Implementation of a system-wide, in-memory cache.


A request for data is passed to a "central" pipe.
A listener program grabs the request (including the return pipe
name), obtains the data, and sends it to the pipe.
The requester reads the information from the pipe.
syscache.pkg
syscache.tst

PL/SQL Advanced Techniques - page 261

Built-in Packages: DBMS_UTILITY


DBMS_UTILITY
Application

GET_TIME
GET_HASH_VALUE
FORMAT_CALL_STACK
...

A grab-bag
of miscellaneous
operations

SCHEMA

PL/SQL Advanced Techniques - page 262

DBMS_UTILITY Module Outline


ANALYZE_DATABASE

Analyze objects in database

ANALYZE_PART_OBJECT

Runs ANALYZE TABLE or ANALYZE INDEX


for each partition of the object (Oracle8).

COMMA_TO_TABLE

Parses comma-delimited list to index-by table.

COMPILE_SCHEMA

Recompile INVALID objects in specified schema.

DATA_BLOCK_ADDRESS_BLOCK

Gets block number part of data block address.

DATA_BLOCK_ADDRESS_FILE

Gets file number part of data block address.

DB_VERSION

Returns database version (Oracle8)

EXEC_DDL_STATEMENT

Executes DDL statement (Oracle8).

FORMAT_CALL_STACK

Returns execution call stack.

FORMAT_ERROR_STACK

Returns error stack.

GET_HASH_VALUE

Returns hash value for string.

dbver.pkg
dbparm.pkg

PL/SQL Advanced Techniques - page 263

DBMS_UTILITY Module Outline, cont.


GET_PARAMETER_VALUE

Retrieves value of database parameter (Oracle8).

GET_TIME

Returns "current time" down to hundredth of second.

IS_PARALLEL_SERVER

Returns TRUE if in parallel server mode.

MAKE_DATA_BLOCK_ADDRESS

Creates data block address from block & file numbers.

NAME_RESOLVE

Resolves name of object into component parts.

NAME_TOKENIZE

Parses string object designator into components.

PORT_STRING

Returns platform and version of database.

TABLE_TO_COMMA

Converts list (in index-by-table) of elements into a


comma-delimited list (string).

PL/SQL Advanced Techniques - page 264

Sub-Second Timings with GET_TIME

GET_TIME returns the number of 100ths of seconds that


have elapsed since an arbitrary point in time.
SYSDATE only reflects times down to the nearest second, so
GET_TIME offers significant additional granularity.
Useful when analyzing individual PL/SQL programs, especially
those that run in sub-second time.

Compare results from consecutive calls to GET_TIME to


determine the elapsed time of PL/SQL code execution.
Basic steps necessary
to convert GET_TIME
into a performance
analysis tool.

DECLARE
v_start BINARY_INTEGER;
BEGIN
v_start := DBMS_UTILITY.GET_TIME;
calc_totals;
DBMS_OUTPUT.PUT_LINE
(DBMS_UTILITY.GET_TIME - v_start);
END;
PL/SQL Advanced Techniques - page 265

Building a Layer Over GET_TIME


Partial package
specification

PACKAGE PLVtmr
IS
PROCEDURE turn_on;
PROCEDURE turn_off;
PROCEDURE set_factor (factor_in IN NUMBER);
PROCEDURE capture (
context_in IN VARCHAR2 := NULL)
PROCEDURE show_elapsed
(prefix_in IN VARCHAR2 := NULL,
adjust_in IN NUMBER := 0,
reset_in IN BOOLEAN := TRUE);
END PLVtmr;

Clean and lean timing code


ovrhead.sql
plvtmr.sps
plvtmr.spb

BEGIN
PLVtmr.capture;
calc_totals;
PLVtmr.show_elapsed;
END;

PL/SQL Advanced Techniques - page 266

Accessing the Execution Call Stack


Use in the
exception
section to show
context of error.

EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (
DBMS_UTILITY.FORMAT_CALL_STACK);
END;
----- PL/SQL Call Stack ----object
line
object
handle
number name
88ce3f74
88e49fc4
88e49390
88e2bd20

8
2
1
1

package STEVEN.VALIDATE_REQUEST
function STEVEN.COMPANY_TYPE
procedure STEVEN.CALC_NET_WORTH
anonymous block

"Little known facts" about FORMAT_CALL_STACK:


Contains embedded new-line characters, equivalent to CHR(10).

Most recent program at beginning of "report".


Does not show package elements, only the package name.
PL/SQL Advanced Techniques - page 267

Accessing Call Stack Contents

The call stack length can easily exceed 255 bytes, which means you cannot
pass it to DBMS_OUTPUT directly. Instead, use a loop to read through the stack.

CREATE OR REPLACE PROCEDURE dispcs IS


stk VARCHAR2(10000);
next_newline INTEGER;
next_line VARCHAR2(255);
startpos INTEGER := 1;
BEGIN
stk := DBMS_UTILITY.FORMAT_CALL_STACK || CHR(10);
LOOP
next_newline := INSTR (stk, CHR(10), startpos, 1);
EXIT WHEN next_newline = 0;
next_line := SUBSTR (
stk, startpos, next_newline - startpos + 1);
dispcs.sp
dispcs.tst
callstack.pkg
plvcs.sps

DBMS_OUTPUT.PUT_LINE (next_line);
startpos := next_newline + 1;
END LOOP;
END;
PL/SQL Advanced Techniques - page 268

Resolving Names of Stored Code

Code names have many components; the way they are


attached also follows a complicated syntax.
Use NAME_RESOLVE to break down an identifier string into its
components easily.

PROCEDURE DBMS_UTILITY.NAME_RESOLVE
(name IN VARCHAR2,
context IN NUMBER,
schema OUT VARCHAR2,
part1 OUT VARCHAR2,
part2 OUT VARCHAR2,
dblink OUT VARCHAR2,
part1_type OUT NUMBER,
object_number OUT NUMBER);

Possible "Part 1" Types


5

Synonym

Procedure

Function

Package

What a chore! All those arguments...but don't see it as a problem,


see it as an opportunity...for encapsulation!
showcomp.sp
snc.pkg

PL/SQL Advanced Techniques - page 269