Advanced Techniques of PL/SQL
Advanced Techniques of PL/SQL
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
Expand your knowledge and awareness of important and new
features of the PL/SQL language.
Outline
plsql_ides.txt
Building with
PL/SQL Packages
Overview
Initialization section
Overloading
What is a Package?
custrules.pkg
insga.pkg
Package Initialization
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.
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.
BEGIN after/outside
of any program
defined in the pkg.
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;
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
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.
Program Overloading
myproc
myproc
myproc
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.
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?
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
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.
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
DBMS_OUTPUT.PUT_LINE
('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));
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
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!
Why not overload a "create file" program so that you can pick the one that most closely fits your situation?
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
"By-Type" Overloading
BEGIN
DBMS_SQL.DEFINE_INTEGER_COLUMN (cur, 1);
DBMS_SQL.DEFINE_VARCHAR2_COLUMN (cur, 2, 30);
BEGIN
DBMS_SQL.DEFINE_COLUMN (cur, 1, 'NUMBER');
DBMS_SQL.DEFINE_COLUMN (cur, 2, 'STRING', 30);
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);
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
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)
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
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
Collections
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;
Sparse
Data does not have to be stored in consecutive rows of information.
Homogeneous
Data in each row has the same structure.
Index_by Tables
TYPE
declaration
Variable
declaration
children
6306
Barbara Anne
6412
Gary Richard
6904
Lisa Marie
children
child_list_type;
Component
Selection
kid := children (4);
Error:
NO_DATA_FOUND
children
6306
Barbara Anne
6412
Gary Richard
6810
Adam Russell
6904
Lisa Nadezhka
datemgr.pkg
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;
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
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;
Barbara Anne
Gary Richard
Lisa Marie
BOND
Eric Thomas
Max Richard
ntdemo.sql
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;
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.
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
Defining Collections
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
colldd.sql
Initializing Collections
TYPE defined in
PL/SQL
TYPE defined in
the database
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;
Sparse is Nice
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
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!
preextend.tst
Collection Methods
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
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.
the.sql
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
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
Instead, call functions that retrieve the table's data, but hide
the index-by table structure.
Make accessible
in SQL for
Oracle8 and
below.
ibtab_in_sql.sql
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!
bidir.pkg
bidir.tst
Row Level
UPDATE row N
mutating.sql
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.
Writes to list
Work List
(PL/SQL Table)
Writes to list
Process data
in the list.
Statement Trigger
PL/SQL Advanced Techniques - page 56
Salesperson ID
Sales Amount
Rank
1055
64333
74055.88
1055
65709
144533.91
1055
65706
109000.25
1047
70904
65011.25
ranking.pkg
Avoid recursive
execution of logic
Row number is
department number.
Clean up for
next time.
PL/SQL Advanced Techniques - page 60
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
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.
Cursor Variables
Hard-Coded
Hard-Coded
Cursor
Cursor
PGA
Result
Result
Set
Set
Shared
Global
Area
Cursor
Cursor
Object
Object
Cursor
Cursor
Variable
Variable
Result
Result
Set
Set
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.
Close the
cursor variable.
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;
curcmp_new comp_curtype;
cur_any weak_curtype;
BEGIN
Declare cursor variables
from the TYPEs.
Match Needed
BEGIN
OPEN emp_curvar FOR SELECT * from emp;
...
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
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.
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
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.
Dynamic SQL
Method 3: queries with a fixed number of items in the SELECT list and a
fixed number of host variables.
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]...];
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.
tabcount81.sf
compare with:
tabcount.sf
Perform an update...
Pass in bind
variables with
USING clause.
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
You should only use DBMS_SQL when you cannot use NDS.
DDL
Create an index from within PL/SQL
DML
Update rows in a table
Queries
Method 3 and a dynamic WHERE clause
PL/SQL
Create a generic calculation program
creind.sp
updnval1.sp
updnval2.sp
updnval3.sp
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)
showemps.sp
showemp2.sp
showemps.tst
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;
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);
0,
NULL,
NULL,
NULL,
NULL,
NULL,
dyncalc.sf
dyncalc.pkg
Use VARIABLE_VALUE to extract a value from any variable you have bound.
dynvar.pkg
dynvar.tst
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.
dumplong.pkg
dumplong.tst
PL/SQL8
Extensions to
DBMS_SQL
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/SQL8 now allows you to specify the use of "arrays", i.e., index
tables, when you perform updates, inserts, deletes and fetches.
effdsql.sql
openprse.pkg
whichsch.sql
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!
Oracle
Advanced Queuing
Retailer
Shipping
Service
Consumer
PL/SQL Advanced Techniques - page 111
Auction portals
AQ architectural overview
Queue Monitor process
Queue table
Queue
Producers
Consumers
Enqueued
messages
Message 4
Message 3
Message 2
Dequeued
messages
Message1
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:
AQ Components
DBMS_AQADM Highlights
CREATE_QUEUE_TABLE
DROP_QUEUE_TABLE
CREATE_QUEUE
DROP_QUEUE
START_QUEUE
STOP_QUEUE
ADD_SUBSCRIBER
BEGIN
DBMS_AQADM.CREATE_QUEUE_TABLE
(queue_table => 'msg',
queue_payload_type => 'message_type');
DBMS_AQADM.CREATE_QUEUE
(queue_name => 'msgqueue',
queue_table => 'msg');
aq.sql
aqenq*.*
Declare records to
hold various enqueue
and msg properties.
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;
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
aqstk.pkg
aqstk2.pkg
priority.*
aqmult*.*
Oracle AQ - Summary
Managing
Large Objects
with DBMS_LOB
PL/SQL Advanced Techniques - page 126
LOB Terms
Internal LOBs
BFILE: pointer to an
operating system file
External LOBs
Temporary LOBs
Internal LOBs that do not
participate in transactions,
improving performance.
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
Question:
Whats in the_fax?
fax_id
received
fax
281937
12-JAN-98
<lob locator>
Updating
This
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;
...
BEGIN
Syntax:
Example:
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
);
/
Example:
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
loadblob.sql
ISOPEN
PL/SQL Advanced Techniques - page 139
HOT
Some weaknesses...
LOB locator behavior slightly abstruse
Inability to modify BFILEs directly from within
PL/SQL.
COLD
Leveraging Java
Inside PL/SQL
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?
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.
Running a Class
Run a class? What does that mean? It means that if your
class contains a method with this header:
then you can "run" the main method with the java
command:
d:\java> java Hello
Using a Class
p.java
Tmr.java
InFile.java
Types of Classes
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.
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
Language Basics
Comments
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();
Examples:
for (indx indx=0;
indx < args.length;
indx++)
System.out.println (args[indx]);
Passing Parameters
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
Now let's explore how you can put Java to work for you
inside PL/SQL programs.
Oracle
Developer
client
(PL/SQL)
OCI or
Pro*C
client
Oracle 8i server
Net8
VB or C++
via OO4O or
ODBC
PL/SQL Advanced Techniques - page 159
extender
replacement
Vertical application
Web server
XML parser/generator
PL/SQL Advanced Techniques - page 160
Notes on Java
classes
toString method
automatically used by
System.out.println
System.out.println (TheGlobalMonster);
}}
person.java
PL/SQL Advanced Techniques - page 162
Java
resource
file
.java file
.jar file
Oracle 8i server
loadjava
Java
class
Java
resource
Java
source
Example:
loadjava
options (abbreviated)
-oci8
-resolve
Publish
Example (top-level
call spec)
Syntax (simplified)
mapping
mapping (typical)
java.lang.String
VARCHAR2
java.sql.Timestamp
DATE
java.math.BigDecimal
NUMBER
oracle.sql.STRUCT
<named type>
oracle.sql.REF
object REFerence
oracle.sql.ARRAY
Either in
spec:
or in the
body:
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
Java
Javaapplication
application
Object
in O-R
table
Java
object
JPublisher utility
Database server
User-supplied
JPublisher
input file
Definition of
type or REF in
data dictionary
What
does it do?
types of mapping
supported for methods
JPublisher
Oracle mapping
JDBC mapping
Object JDBC mapping
Generated Java
file(s) for use in
Java programs
jspobj.sql
JFile3.java
You can pass Oracle object information to Java without relying on JPub by using the STRUCT
class.
Extract individual
attribute values from
the array.
UnionBuster.java
passobj.tst
PL/SQL Advanced Techniques - page 181
You can pass Oracle object information to Java without relying on JPub by using the STRUCT
class.
}}}
HelloAll.tst
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).
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;
/
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
External Procedures
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
Send email
email.sql
diskspace.sql
All the
magic is in
the
EXTERNAL
section
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
Syntax:
Notes
Requires CREATE LIBRARY privilege
Does not validate directory
Cant use symbolic link
IN
RETURN
IN BY REFERENCE
RETURN BY REFERENCE
OUT
IN OUT
Autonomous Transactions
Autonomous Transactions
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).
retry.pkg
retry.tst
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.
OE Code
Sam_Sales
Order_Mgt
Place
Close Old
Orders
Cancel
OE Data
Orders
Cannot alter
table directly.
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
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
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
HQ
Chicago
stolenlives
Check City
Statistics
Analyze
Pattern
New York
stolenlives
perpetrators
perp.sql
PL/SQL Advanced Techniques - page 210
DBMS_RLS
Row-Level
Security
The establishment of
security policies on
(restricted access to)
individual rows of a table.
System Contexts
A new feature in Oracle8i, the system context is a named set of attributevalue pairs global to your session.
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.
(
=>
=>
=>
=>
=>
=>
'SCOTT',
'patient',
'patient_privacy',
'SCOTT',
'nhc_pkg.person_predicate',
'SELECT,UPDATE,DELETE');
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.
Exception handling in
trigger is critical! If you
allow an exception to go
unhandled, logon is
disabled.
fgac.sql
February...
March...
April...
May...
Jump out of
your rut - and
play a new tune
with PL/SQL!
Appendices
(a.k.a., If time permits)
UTL_FILE file IO
UTL_FILE
Server-side
File I/O
Application
FOPEN
GET_LINE
PUT_LINE
...
Physical Files
But you can read lines from a file and write lines to a file.
FCLOSE_ALL
FFLUSH
FOPEN
Open a file
GET_LINE
IS_OPEN
NEW_LINE
PUT
PUT_LINE
PUTF
utl_file_dir = *
Allows read &
write for any
directory.
PL/SQL Advanced Techniques - page 225
Opening a File
DECLARE
fid UTL_FILE.FILE_TYPE;
BEGIN
fid := UTL_FILE.FOPEN ('c:\temp', 'test.txt', 'W');
END;
Can only read from a file opened with the "R" mode.
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
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
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
EXCEPTION;
You have to take special care to trap and handle the named exceptions.
They all share a common SQLCODE of 1.
utlflexc.sql
Trap locally by name; record the error, translating the generic userdefined exception into an understandable message.
DBMS_JOB
Scheduled Execution
of Stored Procedures
Application
DBA_JOBS
REMOVE
...
Background
Process
Overview of DBMS_JOB
CHANGE
INTERVAL
ISUBMIT
NEXT_DATE
REMOVE
RUN
SUBMIT
USER_EXPORT
WHAT
Submitting a Job
DECLARE
job# BINARY_INTEGER;
BEGIN
DBMS_JOB.SUBMIT
(job#, 'calculate_totals;', SYSDATE, 'SYSDATE + 1');
END;
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.
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.
'SYSDATE + 1/24'
Every Sunday at 2
AM
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'
BEGIN
FOR rec IN (SELECT * FROM USER_JOBS) LOOP
DBMS_JOB.REMOVE (rec.job);
END LOOP;
DBMS_JOB.RUN (my_job#);
expjob.sql
FROM
jr.job
,username
,jr.this_date
,what
job_id
username
start_date
job_definition
DBA_JOBS_RUNNING
,DBA_JOBS
,V$SESSION
jr
j
s
showjobs.sql
spacelog.sql
showspc.sql
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.
DBMS_PIPE
Inter-session
Communication
DBMS_PIPE Overview
Architecture of DBMS_PIPE
Shared Global Area
Sue
Bob
Message Buffer
Message Buffer
Session A
Session B
Description
CREATE_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.
RESET_BUFFER
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;
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);
Program wakes up every N seconds to read the data from the pipe and analyze results from
assembly line (an intentional "infinite loop"!).
dbpipe.sql
dbpipe.tst
Process A
Process B
Process C
E
n
d
Process A
Process B
Process C
End
PL/SQL Advanced Techniques - page 257
The wait program waits till it receives a message from each program.
Then net profits can be computed -- in a much-decreased elapsed time.
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;
Perform final
calculation.
parallel.sql
watch.pkg
p_and_l.pkg
GET_TIME
GET_HASH_VALUE
FORMAT_CALL_STACK
...
A grab-bag
of miscellaneous
operations
SCHEMA
ANALYZE_PART_OBJECT
COMMA_TO_TABLE
COMPILE_SCHEMA
DATA_BLOCK_ADDRESS_BLOCK
DATA_BLOCK_ADDRESS_FILE
DB_VERSION
EXEC_DDL_STATEMENT
FORMAT_CALL_STACK
FORMAT_ERROR_STACK
GET_HASH_VALUE
dbver.pkg
dbparm.pkg
GET_TIME
IS_PARALLEL_SERVER
MAKE_DATA_BLOCK_ADDRESS
NAME_RESOLVE
NAME_TOKENIZE
PORT_STRING
TABLE_TO_COMMA
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
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;
BEGIN
PLVtmr.capture;
calc_totals;
PLVtmr.show_elapsed;
END;
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
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.
DBMS_OUTPUT.PUT_LINE (next_line);
startpos := next_newline + 1;
END LOOP;
END;
PL/SQL Advanced Techniques - page 268
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);
Synonym
Procedure
Function
Package