SlideShare a Scribd company logo
Copyright 2000-2006 Steven Feuerstein - Page 1
Some of the most
wonderful features
of this most wonderful
programming language
Steven Feuerstein
PL/SQL Evangelist, Quest Software
steven.feuerstein@quest.com
www.quest.com www.oracleplsqlprogramming.com
"Best of" Oracle PL/SQL
Copyright 2000-2006 Steven Feuerstein - Page 2
Two days of fun with PL/SQL! (1)
 Understanding PL/SQL architecture
– Memory mgt, autonomous transactions, invoker rights
 The brilliant Oracle10g PL/SQL compiler
– Optimizing compiler, compile-time warnings, conditional
compilation
 Collections
– Array-like structures that are fantastically useful.
 Optimizing SQL in PL/SQL
– BULK COLLECT and FORALL: turbocharged SQL
– Table functions
– Dynamic SQL in PL/SQL
– Best practices for SQL in PL/SQL: Stop writing so much SQL!
Copyright 2000-2006 Steven Feuerstein - Page 3
Two days of fun with PL/SQL! (2)
 Managing errors in PL/SQL
– A review of available functionality for raising,
handling and logging errors in PL/SQL, plus best
practices
 Unit testing PL/SQL programs
– I will recommend a methodology and show you a
new tool for testing PL/SQL programs
 Write readable, maintainable code
– Packages, local modules, and more
Copyright 2000-2006 Steven Feuerstein - Page 4
Eleven Years Writing Ten Books
on the Oracle PL/SQL Language
Copyright 2000-2006 Steven Feuerstein - Page 5
How to benefit most from this class
 Watch, listen, ask questions.
 Download the training materials and supporting scripts:
– http://oracleplsqlprogramming.com/resources.html
– "Demo zip": all the scripts I run in my class available at
http://oracleplsqlprogramming.com/downloads/demo.zip
 Use these materials as an accelerator as you venture into
new territory and need to apply new techniques.
 Play games! Keep your brain fresh and active by mixing
hard work with challenging games
– MasterMind and Set (www.setgame.com)
filename_from_demo_zip.sql
Copyright 2000-2006 Steven Feuerstein - Page 6
High Level, High Impact
 Drink lots of water.
 Write tiny chunks of code.
 Stop writing so much SQL.
 Stop guessing and start testing.
Top four tips....
 Assume everything will change.
 Aim for a single point of definition (a SPOD!).
Important principles...
Copyright 2000-2006 Steven Feuerstein - Page 7
Drink lots of water!
 And lots less coffee.
 OK, don't go cold turkey on caffeine.
 But drink lots (and lots more) water.
– Coffee dehydrates you and a dehydrated brain
just doesn’t work as effectively.
 Generally, we need to take care of our host
body, so that our brain can keep on earning
that big fat paycheck!
– Get away from your computer, take breaks.
– Exercise and stretch.
Copyright 2000-2006 Steven Feuerstein - Page 8
Some PL/SQL Fundamentals
 The STANDARD package and how to be a
PL/SQL sleuth
 The PL/SQL run-time memory architecture
 Invoker rights
– Running code under the current user's authority
 Autonomous transactions
– Control the scope of commits and rollbacks
Copyright 2000-2006 Steven Feuerstein - Page 9
The STANDARD Package and the
Rdbms/Admin Directory
 Much of what we consider to be the base
PL/SQL language is defined in the STANDARD
package.
– One of two "default" packages; the other is
DBMS_STANDARD.
 You can view the contents of the STANDARD
package (and other "supplied" packages by
visiting the appropriate variation of:
$ORACLE_HOME/Rdbms/Admin
Copyright 2000-2006 Steven Feuerstein - Page 10
Code and Data in Shared Memory
 PL/SQL is an interpretative language. The source
code is “partially compiled” into an intermediate form
(“p-code”).
– The p-code is loaded into the shared pool when any
element of that code (package or stand-alone program) is
referenced.
 The partially-compiled code is shared among all
users who have EXECUTE authority on the
program/package.
 Each user (Oracle session) has its own copy of any
data structures defined within the program/package.
– Distinct sets of in-memory data (not shared among
different users) are stored in the PGA.
Copyright 2000-2006 Steven Feuerstein - Page 11
System Global Area (SGA) of RDBMS Instance
PL/SQL in Shared Memory
Shared Pool
Large Pool
Reserved Pool
show_emps
calc_totals upd_salaries
Select *
from emp
Shared SQL
Pre-parsed
Update emp
Set sal=...
Library cache
Session 1 memory
(PGA/UGA)
emp_rec emp%rowtype;
tot_tab tottabtype;
Session 2 memory
(PGA/UGA)
emp_rec emp%rowtype;
tot_tab tottabtype;
Session 1
Session 2
mysess.pkg
Sess2.sql
Copyright 2000-2006 Steven Feuerstein - Page 12
Execution Mode Options
for PL/SQL:
Invoker and Definer Rights
Execution Mode Options
Copyright 2000-2006 Steven Feuerstein - Page 13
Two options for resolving data references
 Definer Rights
–Whenever you executed a stored program, it
runs under the privileges of the schema in
which the program was compiled or defined.
 Invoker Rights
–Oracle resolves all data references (table,
view, etc.) at run-time, based on the
currently-connect user and its privileges
(directly granted or available through roles).
Copyright 2000-2006 Steven Feuerstein - Page 14
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.
Orders
OE Data
OE Code
Order_Mgt
Cancel
Sam_Sales
Place
Close Old
Orders
X
Cannot alter
table directly.
Note: Oracle built-in packages have
long had the capability of running
under the invoker's authority.
Copyright 2000-2006 Steven Feuerstein - Page 15
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.
– Difficult to audit privileges
 Sure would be nice to have a choice...and now you do!
Copyright 2000-2006 Steven Feuerstein - Page 16
 For top level modules:
 For modules with separate spec and body,
AUTHID goes only in spec, and must be at
the package level.
– Holds true for packages and object types.
Invoker Rights Syntax
CREATE [ OR REPLACE ] <module type>
[ AUTHID { DEFINER | CURRENT_USER } ]
AS ...
Copyright 2000-2006 Steven Feuerstein - Page 17
"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.
User/Data schema
accounts table
PROCEDURE mng_account IS
BEGIN
...
code.acct_mgr.destroy(...);
END;
Central Code schema
PACKAGE acct_mgr
...FROM accounts
WHERE...
destroy
modify
make
AUTHID
CURRENT_USER
Copyright 2000-2006 Steven Feuerstein - Page 18
When Invoker Rights Applies
 Resolution against invoker's privileges is made
for these statements:
– SELECT, INSERT, UPDATE, and DELETE data
manipulation statements
– The LOCK TABLE transaction control statement
– OPEN and OPEN-FOR cursor control statements
– EXECUTE IMMEDIATE and OPEN-FOR-USING dynamic
SQL statements
– SQL statements parsed using DBMS_SQL.PARSE()
 For all other statements, resolution is by the
owner's privileges.
– This includes ALL code references.
Copyright 2000-2006 Steven Feuerstein - Page 19
Roles and Privileges
 With definer rights, roles are disabled and
ignored.
– All references are resolved against directly granted
privileges.
 With invoker rights, roles are enabled and used
for privilege checking.
– You can even use dynamic SQL to set roles for the
session, altering how the reference is resolved at
run-time.
– Exception: if the CURRENT_USER programs was
called directly or indirectly by a definer-rights
subprogram.
invrole.sql
Copyright 2000-2006 Steven Feuerstein - Page 20
 When writing code with the intention of relying
on invoker rights, the data object referenced
may not be present in the code's schema.
– You need some kind of "template" against which to
successfully compile the code.
 Two options; either create a...
– Synonym to any of the possible resolved objects.
– Local, "dummy" object to allow the code to compile,
knowing that it will never be used at run-time.
Compiling with "Template" Objects
Copyright 2000-2006 Steven Feuerstein - Page 21
Invoker-Definer Precedence
 If the first program in the execution stack is defined
with invoker rights, then it executes under the session
user's authority.
 When and if a definer rights program is called in the
stack, the "current user" is set to the owner of the
definer rights program.
 All subsequent calls in the stack are resolved
according to the privileges of that schema.
invdefinv.sql
invdefinv.tst
irdynsql.sql
invoker_rights_mode.sf
Copyright 2000-2006 Steven Feuerstein - Page 22
Invoker Rights and Distributed
Databases
 Invoker rights affect only one kind of database link--
current-user links, which are created as follows:
 A current-user link lets you connect to a remote
database as another user, with that user's privileges.
 To connect, Oracle uses the username of the current
user (who must be a global user).
– Suppose an invoker-rights subprogram owned by user blake
references the database link below. If global user scott calls
the subprogram, it connects to the Dallas database as user
scott, who is the current user.
CREATE DATABASE LINK link_name CONNECT TO
CURRENT_USER USING connect_string;
Copyright 2000-2006 Steven Feuerstein - Page 23
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.
 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;
Copyright 2000-2006 Steven Feuerstein - Page 24
When to Use Autonomous Transactions
 Reusable Application Components
– ATs are more or less required in the new distributed
application architecture of the Internet.
 Logging Mechanism
– Solves problems of error logs in database tables,
with log entries a part of your transaction.
 Call functions within SQL that change the
database.
 Issue commits and rollbacks inside DB triggers.
Copyright 2000-2006 Steven Feuerstein - Page 25
Autonomous vs. Nested Transactions
 An AT is started by another transaction, but is not a
nested transaction for the following reasons:
– It does not share transactional resources (such as locks) with
the main transaction.
– It does not depend on the main transaction. For example, if
the main transaction rolls back, nested transactions roll back,
but autonomous transactions do not.
– Its committed changes are visible to other transactions
immediately. (A nested transaction's committed changes are
not visible to other transactions until the main transaction
commits.)
Copyright 2000-2006 Steven Feuerstein - Page 26
Logging with ATs
logger.sp
log81.pkg
log81*.tst
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;
retry.pkg
retry.tst
Save on
successful exit
Avoid inter-
dependencies with
the main
transaction.
Don't forget to
rollback on error!
Copyright 2000-2006 Steven Feuerstein - Page 27
Tips and Gotchas with ATs
 An AT program that executes DML must COMMIT or
ROLLBACK before terminating, or an error is raised.
– If you only query, COMMIT/ROLLBACK is not required.
 The AT PRAGMA can be used only with individual
programs and top-level anonymous blocks.
– You cannot define an entire package as an AT.
– You cannot define a nested anonymous block as 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.
Copyright 2000-2006 Steven Feuerstein - Page 28
Tips and Gotchas, cont.
 The Oracle initialization parameter TRANSACTIONS
specifies the maximum number of concurrent
transactions.
– Which might be exceeded if autonomous transactions
(running concurrently with main transaction) are not taken
into account.
 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
Copyright 2000-2006 Steven Feuerstein - Page 29
Autonomous Transactions - Summary
 Easy to define
 Lots of immediate applications
 Minimal learning curve
 Low implementation risks
 You should immediately explore
opportunities to utilize this feature.
Copyright 2000-2006 Steven Feuerstein - Page 30
Major Oracle10g PL/SQL compiler upgrades
 Optimizing compiler
– Recompile in 10g and experience 100% improvement in
performance (results may vary).
 Compile-time warnings
– Now the PL/SQL compiler tells you more than simply
compilation errors.
 Conditional compilation (10.2 only)
– "ifdef" like pre-processing for PL/SQL
Copyright 2000-2006 Steven Feuerstein - Page 31
Wow! An optimizing compiler!
 Yes, the PL/SQL compiler now has the ability to
automatically optimize your code.
– Possible rearrangements to the code itself (under the covers).
 You can choose the level of optimization through the
plsql_optimize_level setting:
– 2 Most aggressive, maximum possible code transformations,
biggest impact on compile time. [default]
– 1 Smaller scale change, less impact on compile times
– 0 Pre-10g compilation without optimization
ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 1;
10g_optimize_cfl.sql
Oracle10g
Copyright 2000-2006 Steven Feuerstein - Page 32
Learn about the PL/SQL optimizer
 PL/SQL Just Got Faster
– Explains the workings of the PL/SQL compiler and runtime system and
shows how major improvements on this scale are indeed possible.
 PL/SQL Performance Measurement Harness
– Describes a performance experiment whose conclusion is the large
factors quoted above. We’ve provided a downloadable kit to enable you to
repeat the experiment yourself.
 Freedom, Order, and PL/SQL Optimization
– Intended for professional PL/SQL programmers, explores the use and
behavior of the new compiler.
 PL/SQL Performance — Debunking the Myths
– Re-examines some old notions about PL/SQL performance.
http://www.oracle.com/technology/tech/pl_sql/htdocs/new_in_10gr1.htm
Copyright 2000-2006 Steven Feuerstein - Page 33
Optimizing compiler details
 Oracle retains optimizer settings on a
module-by-module basis.
– When you recompile a particular module with
non-default settings, the settings will "stick,"
allowing you to recompile later using REUSE
SETTINGS. For example:
 and then:
ALTER PROCEDURE bigproc COMPILE PLSQL_OPTIMIZE_LEVEL = 1;
ALTER PROCEDURE bigproc COMPILE REUSE SETTINGS;
Copyright 2000-2006 Steven Feuerstein - Page 34
Wow! Compile-time warnings!
 You can now enable compiler warnings, helping
you avoid nuisance issues in your code.
– Generally, these are not severe errors, but potential
problems with code structure or performance.
 To use compiler warnings, you must turn them
on in your session.
[ENABLE | DISABLE | ERROR]:[ALL|SEVERE|INFORMATIONAL|PERFORMANCE|warning_number]
REM To enable all warnings in your session execute:
ALTER SESSION SET plsql_warnings = 'enable:all‘;
REM If you want to enable warning message number 06002 and all warnings in
REM the performance category, and treat warning 5005 as a "hard" compile error:
ALTER SESSION SET plsql_warnings =
'enable:06002', 'enable:performance', 'ERROR:05005';
Oracle10g
Copyright 2000-2006 Steven Feuerstein - Page 35
Compiler time warnings - example
 Check for “unreachable end” code….
SQL> CREATE OR REPLACE PROCEDURE unreachable_code IS
2 x NUMBER := 10;
3 BEGIN
4 IF x = 10 THEN
5 x := 20;
6 ELSE
7 x := 100; -- unreachable code
8 END IF;
9 END unreachable_code;
10 /
SP2-0804: Procedure created with compilation warnings
SQL> show err
Errors for PROCEDURE UNREACHABLE_CODE:
LINE/COL ERROR
-------- -------------------------------------
7/7 PLW-06002: Unreachable code
plw*.sql
Copyright 2000-2006 Steven Feuerstein - Page 36
The ALL_PLSQL_OBJECT_SETTINGS view
 New to Oracle10g, this view provides information
about the characteristics of a PL/SQL object that can
be modified through the ALTER-SET DDL command,
such as the optimization level, debug settings and
more.
– Show all the program units that are not fully leveraging code
optimization:
– Show all objects which have had one or more compile-time
warnings disabled:
SELECT owner, name
FROM all_plsql_object_settings
WHERE plsql_optimize_level IN (1,0);
SELECT owner, NAME, plsql_warnings
FROM all_plsql_object_settings
WHERE plsql_warnings LIKE '%DISABLE%'
AND owner NOT IN ('SYS', 'SYSTEM');
Copyright 2000-2006 Steven Feuerstein - Page 37
Conditional Compilation in 10g Release 2
 Compile selected parts of a program based on
conditions you provide with various compiler directives.
 Conditional compilation will allow you to:
– Write code that will compile and run under different versions
of Oracle (relevant for future releases).
– Run different code for test, debug and production phases.
That is, compile debug statements in and out of your code.
– Expose private modules for unit testing.
 Available in 10gR2 and patch sets of 10gR1, plus 9iR2
(with guidance from Oracle Support)
Copyright 2000-2006 Steven Feuerstein - Page 38
Three types of compiler directives
 Selection directives: $IF
– Use the $IF directive to evaluate expressions and
determine which code should be included or avoided.
 Inquiry directives: $$identifier
– Use the $$identifier syntax to refer to conditional
compilation flags. These inquiry directives can be
referenced within an $IF directive, or used independently
in your code.
 Error directives: $ERROR
– Use the $ERROR directive to report compilation errors
based on conditions evaluated when the preprocessor
prepares your code for compilation.
Copyright 2000-2006 Steven Feuerstein - Page 39
Access to post-processed code
 You can display or retrieve post-processed code
with the DBMS_PREPROCESSOR package.
CREATE OR REPLACE PROCEDURE
post_processed
IS
BEGIN
$IF $$PLSQL_OPTIMIZE_LEVEL = 1
$THEN
-- Slow and easy
NULL;
$ELSE
-- Fast and modern and easy
NULL;
$END
END post_processed;
/
cc_postprocessor.sql
BEGIN
DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE
('PROCEDURE', USER, 'POST_PROCESSED');
END;
/
PROCEDURE post_processed
IS
BEGIN
-- Fast and modern and easy
NULL;
END post_processed;
Copyright 2000-2006 Steven Feuerstein - Page 40
What's available for use in directives?
 CC flags that are set through the ALTER SESSION
command
– Integers and Booleans supported
 Static expressions defined in package specifications
– Values that do not change when a package is recompiled
 Compilation environment settings
– In essence, the data available in the
ALL/USER_PLSQL_OBJECT_SETTINGS data dictionary
 DBMS_DB_VERSION constants
– A new package that tells you how the current version
compares to other versions of Oracle.
Copyright 2000-2006 Steven Feuerstein - Page 41
Compilation environment values
 $$PLSQL_DEBUG
– Debug setting for this compilation unit
 $$PLSQL_OPTIMIZE_LEVEL
– Optimization level for this compilation unit
 $$PLSQL_CODE_TYPE
– Compilation mode for the unit
 $$PLSQL_WARNINGS
– Compilation warnings setting for this compilation unit
 $$NLS_LENGTH_SEMANTICS
– Value set for the NLS length semantics
 Plus $$PLSQL_UNIT and $$PLSQL_LINE
– Name and line number in current program unit
Copyright 2000-2006 Steven Feuerstein - Page 42
Selection directive example
 The $IF directive can reference constants defined in
your own packages or environmental variables.
CREATE OR REPLACE PACKAGE BODY sense_of_humor IS
$IF $$test_humor_package $THEN
PROCEDURE int_test_package IS BEGIN
DBMS_OUTPUT.PUT_LINE (
'Testing of sense_of_humor is enabled.');
END int_test_package;
$END
PROCEDURE test_package IS BEGIN
$IF $$test_humor_package
$THEN
int_test_package;
$ELSE
RAISE PROGRAM_ERROR;
$END
END test_package;
END;
cc_expose_private.sql
Copyright 2000-2006 Steven Feuerstein - Page 43
Inquiry directive example
 Set up conditional compilation of debugging
and tracing with special "CC" flags that are
placed into the compiler settings for a program.
ALTER SESSION SET PLSQL_CCFLAGS = 'oe_debug:true, oe_trace_level:10';
CREATE OR REPLACE PROCEDURE calculate_totals
IS
BEGIN
$IF $$oe_debug AND $$oe_trace_level >= 5
$THEN
DBMS_OUTPUT.PUT_LINE ('Tracing at level 5 or higher');
$END
application_logic;
END calculate_totals;
/
cc_debug_trace.sql
cc_expose_private.sql
cc_max_string.sql
cc_plsql_parameters.sql
Copyright 2000-2006 Steven Feuerstein - Page 44
Error directive example
 If my program has not been compiled with
optimization level 1 (less aggressive level), then
raise an error.
– You can in this way add "meta-requirements" to your
code definitions.
SQL> CREATE OR REPLACE PROCEDURE long_compilation
2 IS
3 BEGIN
4 $IF $$plsql_optimize_level <> 1
5 $THEN
6 $error 'Program must be compiled with optimization level = 1' $end
7 $END
8 NULL;
9 END long_compilation;
10 /
cc_opt_level_check.sql
Copyright 2000-2006 Steven Feuerstein - Page 45
DBMS_DB_VERSION
 Each version of Oracle from Oracle Database 10g
Release 2 will contain a DBMS_DB_VERSION package
with Boolean constants showing absolute and relative
version information.
PROCEDURE insert_rows ( rows_in IN otn_demo_aat ) IS
BEGIN
$IF DBMS_DB_VERSION.VER_LE_9_2
$THEN
BEGIN
...
FORALL indx IN 1 .. l_dense.COUNT
INSERT INTO otn_demo VALUES l_dense (indx);
END;
$ELSE
FORALL indx IN INDICES OF rows_in
INSERT INTO otn_demo VALUES rows_in (indx);
$END
cc-bf_or_number.sql
cc_version_check.sql
Copyright 2000-2006 Steven Feuerstein - Page 46
Conditional compilation summary
 Major step forward for the PL/SQL language.
– Much more flexible debugging and tracking.
– Smoothes the way to write programs that support
multiple versions.
 Its impact will be felt most in the future, when
the DBMS_DB_VERSION package can be
used to automatically compile code
appropriate to different versions of Oracle.
Copyright 2000-2006 Steven Feuerstein - Page 47
PL/SQL Collections
 Collections are single-dimensioned lists of
information, similar to 3GL arrays.
 They are an invaluable data structure.
– All PL/SQL developers should be very comfortable
with collections and use them often.
 Collections take some getting used to.
– They are not the most straightforward
implementation of array-like structures.
– Advanced features like string indexes and multi-
level collections can be a challenge.
Copyright 2000-2006 Steven Feuerstein - Page 48
What we will cover on collections
 Review of basic functionality
 Indexing collections by strings
 Working with collections of collections
 MULTISET operators for nested tables
 Then later in the section on SQL:
– Bulk processing with FORALL and BULK
COLLECT
– Table functions and pipelined functions
Copyright 2000-2006 Steven Feuerstein - Page 49
What is a collection?
 A collection is an "ordered group of elements,
all of the same type." (PL/SQL User Guide and
Reference)
– That's a very general definition; lists, sets, arrays and similar
data structures are all types of collections.
– Each element of a collection may be addressed by a unique
subscript, usually an integer but in some cases also a string.
– Collections are single-dimensional, but you can create
collections of collections to emulate multi-dimensional
structures.
abc def sf q rrr swq
...
1 2 3 4 22 23
Copyright 2000-2006 Steven Feuerstein - Page 50
Why use collections?
 Generally, to manipulate in-program-memory lists of
information.
– Much faster than working through SQL.
 Serve up complex datasets of information to
non-PL/SQL host environments using table functions.
 Dramatically improve multi-row querying, inserting,
updating and deleting the contents of tables.
Combined with BULK COLLECT and FORALL....
 Emulate bi-directional cursors, which are not yet
supported within PL/SQL.
Copyright 2000-2006 Steven Feuerstein - Page 51
Three Types of Collections
 Associative arrays (aka index-by tables)
– Can be used only in PL/SQL blocks.
– Similar to hash tables in other languages, allows you to
access elements via arbitrary subscript values.
 Nested tables and Varrays
– Can be used in PL/SQL blocks, but also can be the
datatype of a column in a relational table.
– Part of the object model in PL/SQL.
– Required for some features, such as table functions
– With Varrays, you specify a maximum number of elements
in the collection, at time of definition.
Copyright 2000-2006 Steven Feuerstein - Page 52
About Associative Arrays
 Unbounded, practically speaking.
– Valid row numbers range from -2,147,483,647 to
2,147,483,647.
– This range allows you to employ the row number as an
intelligent key, such as the primary key or unique index
value, because AAs also are:
 Sparse
– Data does not have to be stored in consecutive rows, as is
required in traditional 3GL arrays and VARRAYs.
 Index values can be integers or strings (Oracle9i R2
and above).
assoc_array_example.sql
Copyright 2000-2006 Steven Feuerstein - Page 53
About Nested Tables
 No pre-defined limit on a nested table.
– Valid row numbers range from 1 to
2,147,483,647.
 Part of object model, requiring initialization.
 Is always dense initially, but can become
sparse after deletes.
 Can be defined as a schema level type and
used as a relational table column type.
nested_table_example.sql
Copyright 2000-2006 Steven Feuerstein - Page 54
About Varrays
 Has a maximum size, associated with its type.
– Can adjust the size at runtime in Oracle10g R2.
 Part of object model, requiring initialization.
 Is always dense; you can only remove
elements from the end of a varray.
 Can be defined as a schema level type and
used as a relational table column type.
varray_example.sql
Copyright 2000-2006 Steven Feuerstein - Page 55
Wide Variety of Collection Methods
 Obtain information about the collection
– COUNT returns number of rows currently defined in collection.
– 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.
Copyright 2000-2006 Steven Feuerstein - Page 56
Useful reminders for PL/SQL collections
 Memory for collections comes out of the PGA or
Process Global Area
– One per session, so a program using collections can
consume a large amount of memory.
 Use the NOCOPY hint to reduce overhead of passing
collections in and out of program units.
 Encapsulate or hide details of collection management.
 Don't always fill collections sequentially. Think about
how you need to manipulate the contents.
 Try to read a row that doesn't exist, and Oracle raises
NO_DATA_FOUND.
mysess.pkg
sess2.sql
nocopy*.*
Copyright 2000-2006 Steven Feuerstein - Page 57
Apply PL/SQL Collections
 We will take a look at the following
application of PL/SQL collections:
– Caching data in the PGA with collections
 Then we will explore advanced features of
collections.
– String-indexed collections
– Multi-level collections
Copyright 2000-2006 Steven Feuerstein - Page 58
Function
PGA
Data Caching with PL/SQL Tables
First access
Subsequent accesses
PGA
Function
Database
Not in cache;
Request data
from database
Pass Data
to Cache
Application
Application
Requests Data
Data retrieved
from cache Data returned
to application
Application
Application
Requests Data
Data returned
to application
Data retrieved
from cache
Database
Data found in
cache. Database
is not needed.
emplu.pkg
emplu.tst
Copyright 2000-2006 Steven Feuerstein - Page 59
New indexing capabilities
for associative arrays
 Prior to Oracle9iR2, you could only index by
BINARY_INTEGER.
 You can now define the index on your associative
array to be:
– Any sub-type derived from BINARY_INTEGER
– VARCHAR2(n), where n is between 1 and 32767
– %TYPE against a database column that is consistent with
the above rules
– A SUBTYPE against any of the above.
 This means that you can now index on string
values! (and concatenated indexes and...)
Oracle9i Release 2
Copyright 2000-2006 Steven Feuerstein - Page 60
Examples of New
TYPE Variants
 All of the following are now valid TYPE declarations in
Oracle9i Release 2
– You cannot use %TYPE against an INTEGER column,
because INTEGER is not a subtype of BINARY_INTEGER.
DECLARE
TYPE array_t1 IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
TYPE array_t2 IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
TYPE array_t3 IS TABLE OF NUMBER INDEX BY POSITIVE;
TYPE array_t4 IS TABLE OF NUMBER INDEX BY NATURAL;
TYPE array_t5 IS TABLE OF NUMBER INDEX BY VARCHAR2(64);
TYPE array_t6 IS TABLE OF NUMBER INDEX BY VARCHAR2(32767);
TYPE array_t7 IS TABLE OF NUMBER INDEX BY
employee.last_name%TYPE;
TYPE array_t8 IS TABLE OF NUMBER INDEX BY
types_pkg.subtype_t;
Oracle9i Release 2
Copyright 2000-2006 Steven Feuerstein - Page 61
Working with string-indexed collections
 Specifying a row via a string takes some getting
used to, but if offers some very powerful advantages.
DECLARE
TYPE population_type IS TABLE OF NUMBER INDEX BY VARCHAR2(64);
country_population population_type;
continent_population population_type;
howmany NUMBER;
BEGIN
country_population ('Greenland') := 100000;
country_population ('Iceland') := 750000;
howmany := country_population ('Greenland');
continent_population ('Australia') := 30000000;
END;
assoc_array*.sql
assoc_array_perf.tst
Copyright 2000-2006 Steven Feuerstein - Page 62
Rapid access to data via strings
 One of the most powerful applications of this
features is to construct very fast pathways to static
data from within PL/SQL programs.
– If you are repeatedly querying the same data from the
database, why not cache it in your PGA inside
collections?
 Emulate the various indexing mechanisms (primary
key, unique indexes) with collections.
Demonstration package:
assoc_array5.sql
Comparison of performance
of different approaches:
vocab*.*
Generate a caching package:
genaa.sql
genaa.tst
Copyright 2000-2006 Steven Feuerstein - Page 63
The String Tracker package (V1)
 Another example: I need to keep track of the
names of variables that I have already used in
my test code generation.
– Can't declare the same variable twice.
CREATE OR REPLACE PACKAGE BODY string_tracker
IS
TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t;
g_names_used used_aat;
FUNCTION string_in_use ( value_in IN maxvarchar2_t ) RETURN BOOLEAN
IS BEGIN
RETURN g_names_used.EXISTS ( value_in );
END string_in_use;
PROCEDURE mark_as_used (value_in IN maxvarchar2_t) IS
BEGIN
g_names_used ( value_in ) := TRUE;
END mark_as_used;
END string_tracker;
string_tracker1.*
Copyright 2000-2006 Steven Feuerstein - Page 64
Multi-level Collections
 Prior to Oracle9i, you could have collections of
records or objects, but only if all fields were
scalars.
– A collection containing another collection was not
allowed.
 Now you can create collections that contain
other collections and complex types.
– Applies to all three types of collections.
 The syntax is non-intuitive and resulting code
can be quite complex.
Oracle9i
Copyright 2000-2006 Steven Feuerstein - Page 65
String Tracker Version 2
 The problem with String Tracker V1 is that it
only supports a single list of strings.
– What if I need to track multiple lists
simultaneously or nested?
 Let's extend the first version to support
multiple lists by using a string-indexed, multi-
level collection.
– A list of lists....
Copyright 2000-2006 Steven Feuerstein - Page 66
The String Tracker package (V2)
CREATE OR REPLACE PACKAGE BODY string_tracker
IS
TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t;
TYPE list_of_lists_aat IS TABLE OF used_aat INDEX BY maxvarchar2_t;
g_list_of_lists list_of_lists_aat;
PROCEDURE mark_as_used (
list_in IN maxvarchar2_t
, value_in IN maxvarchar2_t
, case_sensitive_in IN BOOLEAN DEFAULT FALSE
) IS
l_name maxvarchar2_t :=
CASE case_sensitive_in WHEN TRUE THEN value_in
ELSE UPPER ( value_in ) END;
BEGIN
g_list_of_lists ( list_in ) ( l_name) := TRUE;
END mark_as_used;
END string_tracker;
string_tracker2.*
Copyright 2000-2006 Steven Feuerstein - Page 67
Other multi-level collection examples
 Multi-level collections with intermediate records
and objects.
 Emulation of multi-dimensional arrays
– No native support, but can creates nested
collections to get much the same effect.
– Use the UTL_NLA package (10gR2) for complex
matrix manipulation.
 Four-level nested collection used to track
arguments for a program unit.
– Automatically analyze ambiguous overloading.
multidim*.*
ambig_overloading.sql
OTN: OverloadCheck
multilevel_collections.sql
Copyright 2000-2006 Steven Feuerstein - Page 68
Encapsulate these complex structures!
 When working with multi-level collections, you
can easily and rapidly arrive at completely
unreadable and un-maintainable code.
 What' s a developer to do?
– Hide complexity -- and all data structures -- behind
small modules.
– Work with and through functions to retrieve
contents and procedures to set contents.
cc_smartargs.pkb:
cc_smartargs.next_overloading
cc_smartargs.add_new_parameter
Copyright 2000-2006 Steven Feuerstein - Page 69
Nested Tables unveil their
MULTISET-edness
 Oracle10g introduces high-level set operations
on nested tables (only).
– Nested tables are “multisets,” meaning that
theoretically there is no order to their elements. This
makes set operations of critical importance for
manipulating nested tables. .
 You can now…
– Check for equality and inequality
– Perform UNION, INTERSECT and MINUS operations
– Check for and remove duplicates
Oracle10g
Copyright 2000-2006 Steven Feuerstein - Page 70
Check for equality and inequality
 Just use the basic operators….
Oracle10g
DECLARE
TYPE clientele IS TABLE OF VARCHAR2 (64);
group1 clientele := clientele ('Customer 1', 'Customer 2');
group2 clientele := clientele ('Customer 1', 'Customer 3');
group3 clientele := clientele ('Customer 3', 'Customer 1');
BEGIN
IF group1 = group2 THEN
DBMS_OUTPUT.put_line ('Group 1 = Group 2');
ELSE
DBMS_OUTPUT.put_line ('Group 1 != Group 2');
END IF;
IF group2 != group3 THEN
DBMS_OUTPUT.put_line ('Group 2 != Group 3');
ELSE
DBMS_OUTPUT.put_line ('Group 2 = Group 3');
END IF;
END;
10g_compare.sql
10g_compare2.sql
10g_compare_old.sql
Copyright 2000-2006 Steven Feuerstein - Page 71
UNION, INTERSECT, MINUS
 Straightforward, with the MULTISET keyword.
Oracle10g
BEGIN
our_favorites := my_favorites MULTISET UNION dad_favorites;
show_favorites ('MINE then DAD', our_favorites);
our_favorites := dad_favorites MULTISET UNION my_favorites;
show_favorites ('DAD then MINE', our_favorites);
our_favorites := my_favorites MULTISET UNION DISTINCT dad_favorites;
show_favorites ('MINE then DAD with DISTINCT', our_favorites);
our_favorites := my_favorites MULTISET INTERSECT dad_favorites;
show_favorites ('IN COMMON', our_favorites);
our_favorites := dad_favorites MULTISET EXCEPT my_favorites;
show_favorites ('ONLY DAD''S', our_favorites);
END;
10g_setops.sql
10g_string_nt.sql
10g_favorites.sql
10g*union*.sql
Copyright 2000-2006 Steven Feuerstein - Page 72
Distinct sets of values
 Use the SET operator to work with distinct values, and determine if you have a set
of distinct values.
Oracle10g
DECLARE
keep_it_simple strings_nt := strings_nt ();
BEGIN
keep_it_simple := SET (favorites_pkg.my_favorites);
favorites_pkg.show_favorites ('FULL SET', favorites_pkg.my_favorites);
p.l (favorites_pkg.my_favorites IS A SET, 'My favorites distinct?');
p.l (favorites_pkg.my_favorites IS NOT A SET, 'My favorites NOT distinct?');
favorites_pkg.show_favorites (
'DISTINCT SET', keep_it_simple);
p.l (keep_it_simple IS A SET, 'Keep_it_simple distinct?');
p.l (keep_it_simple IS NOT A SET, 'Keep_it_simple NOT distinct?');
END;
10g_set.sql
10g_favorites.pkg
Copyright 2000-2006 Steven Feuerstein - Page 73
How to choose your collection type
 Use associative arrays when you need to...
– Work within PL/SQL code only
– Sparsely fill and manipulate the collection
– Take advantage of negative index values
 Use nested tables when you need to...
– Access the collection inside SQL (table functions, columns in
tables)
– Want to perform set operations
 Use varrays when you need to...
– If you need to specify a maximum size to your collection
– Access the collection inside SQL (table functions, columns in
tables).
Copyright 2000-2006 Steven Feuerstein - Page 74
Collections: the latest frontier
 Five-plus years ago, many programmers
wrestled with making packages an every-day
part of their PL/SQL coding.
 Today I offer this challenge: learn collections
thoroughly and apply them throughout your
backend code.
– Your code will get faster and in many cases
much simpler than it might have been (though
not always!).
Copyright 2000-2006 Steven Feuerstein - Page 75
> Optimize SQL in PL/SQL programs
 Take advantage of PL/SQL-specific
enhancements for SQL.
– BULK COLLECT and FORALL
– Table functions
 Dynamic SQL: easier and faster than ever.
 Hide your SQL statements behind a procedural
interface so that you can easily change and
upgrade.
Copyright 2000-2006 Steven Feuerstein - Page 76
Turbo-charged SQL with
BULK COLLECT and FORALL
 Improve the performance of multi-row SQL
operations by an order of magnitude or more
with bulk/array processing in PL/SQL!
CREATE OR REPLACE PROCEDURE upd_for_dept (
dept_in IN employee.department_id%TYPE
,newsal_in IN employee.salary%TYPE)
IS
CURSOR emp_cur IS
SELECT employee_id,salary,hire_date
FROM employee WHERE department_id = dept_in;
BEGIN
FOR rec IN emp_cur LOOP
UPDATE employee SET salary = newsal_in
WHERE employee_id = rec.employee_id;
END LOOP;
END upd_for_dept;
“Conventional
binds” (and lots
of them!)
Copyright 2000-2006 Steven Feuerstein - Page 77
Oracle server
PL/SQL Runtime Engine SQL Engine
PL/SQL block
Procedural
statement
executor
SQL
statement
executor
FOR rec IN emp_cur LOOP
UPDATE employee
SET salary = ...
WHERE employee_id =
rec.employee_id;
END LOOP;
Performance penalty
Performance penalty
for many “context
for many “context
switches”
switches”
Conventional Bind
Copyright 2000-2006 Steven Feuerstein - Page 78
Enter the “Bulk Bind”: FORALL
Oracle server
PL/SQL Runtime Engine SQL Engine
PL/SQL block
Procedural
statement
executor
SQL
statement
executor
FORALL indx IN
list_of_emps.FIRST..
list_of_emps.LAST
UPDATE employee
SET salary = ...
WHERE employee_id =
list_of_emps(indx);
Much less overhead for
Much less overhead for
context switching
context switching
Copyright 2000-2006 Steven Feuerstein - Page 79
Use the FORALL Bulk Bind Statement
 Instead of executing repetitive, individual DML
statements, you can write your code like this:
 Things to be aware of:
– You MUST know how to use collections to use this feature!
– Only a single DML statement is allowed per FORALL.
– New cursor attributes: SQL%BULK_ROWCOUNT returns number of
rows affected by each row in array. SQL%BULK_EXCEPTIONS...
– Prior to Oracle10g, the binding array must be sequentially filled.
– Use SAVE EXCEPTIONS to continue past errors.
PROCEDURE upd_for_dept (...) IS
BEGIN
FORALL indx IN list_of_emps.FIRST .. list_of_emps.LAST
UPDATE employee
SET salary = newsal_in
WHERE employee_id = list_of_emps (indx);
END;
bulktiming.sql
bulk_rowcount.sql
Copyright 2000-2006 Steven Feuerstein - Page 80
Better Exception Handling
for Bulk Operations
 Allows you to continue past errors and obtain error
information for each individual operation (for dynamic
and static SQL).
CREATE OR REPLACE PROCEDURE load_books (books_in IN book_obj_list_t)
IS
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT ( bulk_errors, -24381 );
BEGIN
FORALL indx IN books_in.FIRST..books_in.LAST
SAVE EXCEPTIONS
INSERT INTO book values (books_in(indx));
EXCEPTION
WHEN BULK_ERRORS THEN
FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT
LOOP
log_error (SQL%BULK_EXCEPTIONS(indx));
END LOOP;
END;
Allows processing of all
rows, even after an error
occurs.
New cursor
attribute, a pseudo-
collection
bulkexc.sql
Oracle9i
Copyright 2000-2006 Steven Feuerstein - Page 81
Use BULK COLLECT INTO for Queries
DECLARE
TYPE employees_aat IS TABLE OF employees%ROWTYPE
INDEX BY BINARY_INTEGER;
l_employees employees_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
FROM employees;
FOR indx IN 1 .. l_employees.COUNT
LOOP
process_employee (l_employees(indx));
END LOOP;
END;
bulkcoll.sql
Declare a
collection of
records to hold
the queried data.
Use BULK
COLLECT to
retrieve all rows.
Iterate through the
collection
contents with a
loop. WARNING! BULK COLLECT will not raise
NO_DATA_FOUND if no rows are found.
Always check contents of collection to confirm that
something was retrieved.
Copyright 2000-2006 Steven Feuerstein - Page 82
Limit the number of rows returned by
BULK COLLECT
CREATE OR REPLACE PROCEDURE bulk_with_limit
(deptno_in IN dept.deptno%TYPE)
IS
CURSOR emps_in_dept_cur IS
SELECT *
FROM emp
WHERE deptno = deptno_in;
TYPE emp_tt IS TABLE OF emps_in_dept_cur%ROWTYPE;
emps emp_tt;
BEGIN
OPEN emps_in_dept_cur;
LOOP
FETCH emps_in_dept_cur
BULK COLLECT INTO emps
LIMIT 100;
EXIT WHEN emps.COUNT = 0;
process_emps (emps);
END LOOP;
END bulk_with_limit;
Use the LIMIT clause with the
INTO to manage the amount
of memory used with the
BULK COLLECT operation.
Definitely the preferred
approach in production
applications with large or
varying datasets.
bulklimit.sql
Copyright 2000-2006 Steven Feuerstein - Page 83
Tips and Fine Points
 Use bulk binds in these circumstances:
– Recurring SQL statement in PL/SQL loop. Oracle
recommended threshold: five rows!
 Bulk bind rules:
– Can be used with any kind of collection; Collection
subscripts cannot be expressions; The collections
must be densely filled (pre-10gR2).
 Bulk collects:
– Can be used with implicit and explicit cursors
– Collection is always filled sequentially, starting at
row 1.
emplu.pkg
cfl_to_bulk*.*
Copyright 2000-2006 Steven Feuerstein - Page 84
Collections impact on "Rollback segment too
small" and "Snapshot too old" errors
 Rollback segment too small...
– Cause: so many uncommitted changes, the
rollback segment can't handle it all.
– FORALL will cause the error to occur even sooner.
Use a variation on incremental commits with
FORALL.
 Snapshot too old...
– Cause: a cursor is held open too long and Oracle
can no longer maintain the snapshot information.
– Solution: open-close cursor, or use BULK
COLLECT to retrieve information more rapidly.
forall_incr_commit.sql
Copyright 2000-2006 Steven Feuerstein - Page 85
Cursor FOR Loop ... or BULK COLLECT?
 Why would you ever use a cursor FOR loop (or
other LOOP) now that you can perform a BULK
COLLECT?
– If you want to do complex processing on each row
as it is queried – and possibly halt further fetching.
– You are not executing DML within your cursor FOR
loop and you are on Oracle Oracle Database 10g –
Oracle will automatically optimize the code for you.
 Otherwise, moving to BULK COLLECT is a
smart move! cfl_vs_bulkcollect.sql
cfl_to_bulk.sql
Copyright 2000-2006 Steven Feuerstein - Page 86
More flexibility with FORALL
 In Oracle10g, the FORALL driving array no
longer needs to be processed sequentially.
 Use the INDICES OF clause to use only the
row numbers defined in another array.
 Use the VALUES OF clause to use only the
values defined in another array.
Oracle10g
Copyright 2000-2006 Steven Feuerstein - Page 87
Using INDICES OF
 It only
processes
the rows
with row
numbers
matching
the
defined
rows of
the driving
array.
Oracle10g
10g_indices_of.sql
10g_indices_of2.sql
DECLARE
TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
INDEX BY PLS_INTEGER;
l_employees employee_aat;
TYPE boolean_aat IS TABLE OF BOOLEAN
INDEX BY PLS_INTEGER;
l_employee_indices boolean_aat;
BEGIN
l_employees (1) := 7839;
l_employees (100) := 7654;
l_employees (500) := 7950;
--
l_employee_indices (1) := TRUE;
l_employee_indices (500) := TRUE;
l_employee_indices (799) := TRUE
--
FORALL l_index IN INDICES OF l_employee_indices
BETWEEN 1 AND 500
UPDATE employee
SET salary = 10000
WHERE employee_id = l_employees (l_index);
END;
Copyright 2000-2006 Steven Feuerstein - Page 88
Using VALUES OF
 It only
processes
the rows
with row
numbers
matching
the content
of a row in
the driving
array.
Oracle10g
DECLARE
TYPE employee_aat IS TABLE OF employee.employee_id%TYPE
INDEX BY PLS_INTEGER;
l_employees employee_aat;
TYPE values_aat IS TABLE OF PLS_INTEGER
INDEX BY PLS_INTEGER;
l_employee_values values_aat;
BEGIN
l_employees (-77) := 7820;
l_employees (13067) := 7799;
l_employees (99999999) := 7369;
--
l_employee_values (100) := -77;
l_employee_values (200) := 99999999;
--
FORALL l_index IN VALUES OF l_employee_values
UPDATE employee
SET salary = 10000
WHERE employee_id = l_employees (l_index);
END;
10g_values_of.sql
Copyright 2000-2006 Steven Feuerstein - Page 89
The Wonder Of Table Functions
 A table function is a function that you can call in the
FROM clause of a query, and have it be treated as if it
were a relational table.
 Table functions allow you to perform arbitrarily
complex transformations of data and then make that
data available through a query.
– Not everything can be done in SQL.
 Combined with REF CURSORs, you can now more
easily transfer data from within PL/SQL to host
environments.
– Java, for example, works very smoothly with cursor
variables
Copyright 2000-2006 Steven Feuerstein - Page 90
Building a table function
 A table function must return a nested table or
varray based on a schema-defined type (not
in a PL/SQL package).
 The function header and the way it is called
must be SQL-compatible: all parameters use
SQL types; no named notation.
– In some cases (streaming and pipelined
functions), the IN parameter must be a cursor
variable -- a query result set.
Copyright 2000-2006 Steven Feuerstein - Page 91
Simple table function example
 Return a list of names as a nested table, and
then call that function in the FROM clause.
CREATE OR REPLACE FUNCTION lotsa_names (
base_name_in IN VARCHAR2, count_in IN INTEGER
)
RETURN names_nt
IS
retval names_nt := names_nt ();
BEGIN
retval.EXTEND (count_in);
FOR indx IN 1 .. count_in
LOOP
retval (indx) :=
base_name_in || ' ' || indx;
END LOOP;
RETURN retval;
END lotsa_names;
tabfunc_scalar.sql
SELECT column_value
FROM TABLE (
lotsa_names ('Steven'
, 100)) names;
COLUMN_VALUE
------------
Steven 1
...
Steven 100
Copyright 2000-2006 Steven Feuerstein - Page 92
Streaming data with table functions
 You can use table functions to "stream" data through
several stages within a single SQL statement.
– Example: transform one row in the stocktable to two rows in the
tickertable.
CREATE TABLE stocktable (
ticker VARCHAR2(20),
trade_date DATE,
open_price NUMBER,
close_price NUMBER
)
/
CREATE TABLE tickertable (
ticker VARCHAR2(20),
pricedate DATE,
pricetype VARCHAR2(1),
price NUMBER)
/
tabfunc_streaming.sql
Copyright 2000-2006 Steven Feuerstein - Page 93
Streaming data with table functions - 2
 In this example, transform each row of the
stocktable into two rows in the tickertable.
CREATE OR REPLACE PACKAGE refcur_pkg
IS
TYPE refcur_t IS REF CURSOR
RETURN stocktable%ROWTYPE;
END refcur_pkg;
/
CREATE OR REPLACE FUNCTION stockpivot (dataset refcur_pkg.refcur_t)
RETURN tickertypeset ...
BEGIN
INSERT INTO tickertable
SELECT *
FROM TABLE (stockpivot (CURSOR (SELECT *
FROM stocktable)));
END;
/
tabfunc_streaming.sql
Copyright 2000-2006 Steven Feuerstein - Page 94
Use pipelined functions to enhance
performance.
 Pipelined functions allow you to return data
iteratively, asynchronous to termination of the
function.
– As data is produced within the function, it is passed back
to the calling process/query.
 Pipelined functions can only be called within a SQL
statement.
– They make no sense within non-multi-threaded PL/SQL
blocks.
CREATE FUNCTION StockPivot (p refcur_pkg.refcur_t)
RETURN TickerTypeSet PIPELINED
Copyright 2000-2006 Steven Feuerstein - Page 95
Applications for pipelined functions
 Execution functions in parallel.
– In Oracle9i Database Release 2 and above, use the
PARALLEL_ENABLE clause to allow your pipelined
function to participate fully in a parallelized query.
– Critical in data warehouse applications.
 Improve speed of delivery of data to web
pages.
– Use a pipelined function to "serve up" data to the
webpage and allow users to being viewing and
browsing, even before the function has finished
retrieving all of the data.
Copyright 2000-2006 Steven Feuerstein - Page 96
Piping rows out from a pipelined function
CREATE FUNCTION stockpivot (p refcur_pkg.refcur_t)
RETURN tickertypeset
PIPELINED
IS
out_rec tickertype :=
tickertype (NULL, NULL, NULL);
in_rec p%ROWTYPE;
BEGIN
LOOP
FETCH p INTO in_rec;
EXIT WHEN p%NOTFOUND;
out_rec.ticker := in_rec.ticker;
out_rec.pricetype := 'O';
out_rec.price := in_rec.openprice;
PIPE ROW (out_rec);
END LOOP;
CLOSE p;
RETURN;
END;
tabfunc_setup.sql
tabfunc_pipelined.sql
Add PIPELINED
keyword to header
Pipe a row of data
back to calling block
or query
RETURN...nothing at
all!
Copyright 2000-2006 Steven Feuerstein - Page 97
Enabling Parallel Execution
 You can use pipelined functions with the Parallel Query
option to avoid serialization of table function execution.
 Include the PARALLEL_ENABLE hint in the program
header.
– Choose a partition option that specifies how the function's
execution should be partitioned.
– "ANY" means that the results are independent of the order in
which the function receives the input rows (through the REF
CURSOR).
{[ORDER | CLUSTER] BY column_list}
PARALLEL_ENABLE ({PARTITION p BY
[ANY | (HASH | RANGE) column_list]} )
Copyright 2000-2006 Steven Feuerstein - Page 98
Table functions - Summary
 Table functions offer significant new flexibility
for PL/SQL developers.
 Consider using them when you...
– Need to pass back complex result sets of data
through the SQL layer (a query);
– Want to call a user defined function inside a
query and execute it as part of a parallel query.
Copyright 2000-2006 Steven Feuerstein - Page 99
Dynamic SQL in PL/SQL
Dealing with
Compile-time Ignorance
Copyright 2000-2006 Steven Feuerstein - Page 100
What is Dynamic SQL?
 Dynamic SQL actually refers, in the world of
PL/SQL, to two things:
– SQL statements, such as a DELETE or CREATE
TABLE, that are constructed and executed at run-
time.
– Anonymous PL/SQL blocks that are constructed,
compiled and executed at run-time.
'DROP ' ||
l_type || ' ' || l_name
'BEGIN ' ||
l_proc_name || ' (' ||
l_parameters || '); END;'
Copyright 2000-2006 Steven Feuerstein - Page 101
Some of the possibilities with Dynamic SQL
 Build ad-hoc query and update applications.
– The user decides what to do and see.
 Execute DDL statements from within PL/SQL.
– Not otherwise allowed in a PL/SQL block.
 Soft-code your application logic, placing business
rules in tables and executing them dynamically.
– Usually implemented through dynamic PL/SQL
Copyright 2000-2006 Steven Feuerstein - Page 102
Two Methods Available
 DBMS_SQL
– A large and complex built-in package that made
dynamic SQL possible in Oracle7 and Oracle8.
 Native Dynamic SQL
– A new (with Oracle8i), native implementation of
dynamic SQL that does almost all of what
DBMS_SQL can do, but much more easily and
usually more efficiently.
 Which should you use?
Copyright 2000-2006 Steven Feuerstein - Page 103
NDS or DBMS_SQL: Which is best?
 Reasons to go with NDS:
– Ease of use
– Performance (usually
faster)
– Works with all SQL
datatypes (including
user-defined object and
collection types)
– Fetch into records and
collections of records
 Why You'd Use DBMS_SQL:
– Method 4 Dynamic SQL
– DESCRIBE columns of cursor
– SQL statements larger than 32K
– Better reuse of parsed SQL
statements -- persistent cursor
handles!
– Available from client-side
PL/SQL
Bottom line: NDS should be your first choice.
Copyright 2000-2006 Steven Feuerstein - Page 104
DDL within PL/SQL
 Very easy, very dangerous with NDS.
– Here's a procedure that "drops whatever".
CREATE OR REPLACE PROCEDURE drop_whatever (nm IN VARCHAR2)
AUTHID CURRENT_USER
IS
CURSOR type_cur IS
SELECT object_type FROM USER_OBJECTS
WHERE object_name LIKE UPPER (nm);
type_rec type_cur%ROWTYPE;
BEGIN
OPEN type_cur; FETCH type_cur INTO type_rec;
IF type_cur%FOUND THEN
EXECUTE IMMEDIATE
'DROP ' || type_rec.object_type || ' ' || nm;
END IF;
END;
dropwhatever.sp
creind81.sp
health$.pkg
settrig.sp
Copyright 2000-2006 Steven Feuerstein - Page 105
COUNT(*) For Any Table
 Here's a handy and simple utility based on NDS:
IF tabCount ('citizens', 'insured = ''NO''') > 40,000,000
THEN
DBMS_OUTPUT.PUT_LINE (
'Not the best health care system in the world....');
END IF;
tabcount81.sf
tabcount.sf
CREATE OR REPLACE FUNCTION tabCount (
tab IN VARCHAR2, whr IN VARCHAR2 := NULL, sch IN VARCHAR2 := NULL)
RETURN INTEGER
IS
retval INTEGER;
BEGIN
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' || NVL (sch, USER) ||
'.' || tab || ' WHERE ' || NVL (whr, '1=1') INTO retval;
RETURN retval;
END;
Specify schema, table and
WHERE clause...
Copyright 2000-2006 Steven Feuerstein - Page 106
DML with NDS
CREATE OR REPLACE PROCEDURE salary_raise (
raise_percent NUMBER, job VARCHAR2)
IS
TYPE loc_array_type IS TABLE OF offices.location@TYPE
INDEX BY BINARY_INTEGER;
dml_str VARCHAR2 (200);
loc_array loc_array_type;
BEGIN
SELECT location BULK COLLECT INTO loc_array
FROM offices;
FOR i IN loc_array.FIRST .. loc_array.LAST LOOP
dml_str := 'UPDATE emp_' || loc_array (i)
|| ' SET sal = sal * (1+(:XYZ/100))'
|| ' WHERE job = :123';
EXECUTE IMMEDIATE dml_str USING raise_percent, job;
END LOOP;
END;
Different
table for
each location
Copyright 2000-2006 Steven Feuerstein - Page 107
Multiple Row Queries and NDS
 Familiar syntax, tiny learning curve: OPEN FOR
– Here is a simple utility that 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
cv SYS_REFCURSOR; -- Available in Oracle9i
val VARCHAR2(32767);
BEGIN
OPEN cv FOR 'SELECT ' || col || ' FROM ' || tab ||
' WHERE ' || NVL (whr, '1 = 1');
LOOP
FETCH cv INTO val;
EXIT WHEN cv%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (val);
END LOOP;
CLOSE cv;
END;
showcol81.sp
ndsutil.pkg
Copyright 2000-2006 Steven Feuerstein - Page 108
Dynamic FORALL Example
 This example shows the use of bulk binding and
collecting, plus application of the RETURNING clause.
CREATE TYPE NumList IS TABLE OF NUMBER;
CREATE TYPE NameList IS TABLE OF VARCHAR2(15);
PROCEDURE update_emps (
col_in IN VARCHAR2, empnos_in IN numList) IS
enames NameList;
BEGIN
FORALL indx IN empnos_in.FIRST .. empnos_in.LAST
EXECUTE IMMEDIATE
'UPDATE emp SET ' || col_in || ' = ' || col_in
|| ' * 1.1 WHERE empno = :1
RETURNING ename INTO :2'
USING empnos_in (indx )
RETURNING BULK COLLECT INTO enames;
...
END;
Notice that empnos_in is
indexed, but enames is not.
Oracle9i
Copyright 2000-2006 Steven Feuerstein - Page 109
Dynamic BULK COLLECT
 Now you can even avoid the OPEN FOR and just
grab your rows in a single pass!
CREATE OR REPLACE PROCEDURE fetch_by_loc (loc_in IN VARCHAR2)
IS
TYPE numlist_t IS TABLE OF NUMBER;
TYPE namelist_t IS TABLE OF employee.name%TYPE;
TYPE employee_t IS TABLE OF employee%ROWTYPE;
emp_cv sys_refcursor;
empnos numlist_t;
enames namelist_t;
l_employees employee_t;
BEGIN
OPEN emp_cv FOR 'SELECT empno, ename FROM emp_' || loc_in;
FETCH emp_cv BULK COLLECT INTO empnos, enames;
CLOSE emp_cv;
EXECUTE IMMEDIATE 'SELECT * FROM emp_' || loc_in
BULK COLLECT INTO l_employees;
END;
With Oracle9iR2
you can also fetch
into collections of
records.
Oracle9i
Copyright 2000-2006 Steven Feuerstein - Page 110
Quiz!
PROCEDURE process_lineitem (
line_in IN PLS_INTEGER)
IS
BEGIN
IF line_in = 1
THEN
process_line1;
END IF;
IF line_in = 2
THEN
process_line2;
END IF;
...
IF line_in = 22045
THEN
process_line22045;
END IF;
END;
 What's wrong with
this code?
 How would you fix
it?
Copyright 2000-2006 Steven Feuerstein - Page 111
From 22,000 lines of code to 1!
 Identify the pattern and
resolve it either with reusable
modules or dynamic
abstractions.
PROCEDURE process_lineitem (
line_in IN INTEGER)
IS
BEGIN
IF line_in = 1
THEN
process_line1;
END IF;
IF line_in = 2
THEN
process_line2;
END IF;
...
IF line_in = 22045
THEN
process_line22045;
END IF;
END;
PROCEDURE process_lineitem (
line_in IN INTEGER)
IS
BEGIN
EXECUTE IMMEDIATE
'BEGIN process_line'||
line_in ||'; END;';
END;
dynplsql.txt
Copyright 2000-2006 Steven Feuerstein - Page 112
Dynamic PL/SQL
 Dynamically construct, compile and run an
anonymous block with EXECUTE IMMEDIATE.
– Begins with BEGIN or DECLARE.
– Ends with END;. The trailing semi-colon is required;
otherwise it is parsed as an SQL statement.
 You can only reference globally-accessible data
structures (declared in a package specification).
 Exceptions can (and should) be trapped in the
block from which the dynamic PL/SQL was
executed. dynplsql8i.sp
dynplsql_nolocal.sql
Copyright 2000-2006 Steven Feuerstein - Page 113
Dynamic PL/SQL Possibilities
 There are so many possibilities....some things I have
done:
– Dramatically reduce code volume, improve performance.
– Generic string parsing engine: parse any string into your own
collection.
– Generic calculator engine.
– Implement support for "indirect referencing": read and
change values of variables whose names are only
determined at run-time.
 And there are also dangers: code injection.
dynvar81.pkg
dyncalc.pkg
code_injection.sql
Copyright 2000-2006 Steven Feuerstein - Page 114
How to build dynamic PL/SQL code
 1. Build a static version of the logic you want
to execute dynamically.
– Test it thoroughly.
 2. Identify the portions of the static code
which will need to be made dynamic.
 3. Convert the block, concatenating or
binding those portions which are now
dynamic.
Copyright 2000-2006 Steven Feuerstein - Page 115
1. Write and verify the static block code.
 Here is a
static
program to
parse a
string of
directories
for the
path list.
PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim)
IS
v_loc PLS_INTEGER;
v_startloc PLS_INTEGER := 1;
v_item VARCHAR2 (2000);
BEGIN
dirs.DELETE;
LOOP
v_loc := INSTR (str, delim, v_startloc);
IF v_loc = v_startloc
THEN
v_item := NULL;
ELSIF v_loc = 0
THEN
v_item := SUBSTR (str, v_startloc);
ELSE
v_item := SUBSTR (str, v_startloc, v_loc - v_startloc);
END IF;
dirs (dirs.COUNT + 1) := v_item;
IF v_loc = 0
THEN
EXIT;
ELSE
v_startloc := v_loc + 1;
END IF;
END LOOP;
END set_path;
filepath.pkg
Copyright 2000-2006 Steven Feuerstein - Page 116
2. Identify the dynamic elements of the block.
PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim)
IS
v_loc PLS_INTEGER;
v_startloc PLS_INTEGER := 1;
v_item VARCHAR2 (2000);
BEGIN
dirs.DELETE;
LOOP
v_loc := INSTR (str, delim, v_startloc);
IF v_loc = v_startloc
THEN
v_item := NULL;
ELSIF v_loc = 0
THEN
v_item := SUBSTR (str, v_startloc);
ELSE
v_item := SUBSTR (str, v_startloc, v_loc - v_startloc);
END IF;
dirs (dirs.COUNT + 1) := v_item;
IF v_loc = 0
THEN
EXIT;
ELSE
v_startloc := v_loc + 1;
END IF;
END LOOP;
END set_path;
Dynamic code
Bind variable
Copyright 2000-2006 Steven Feuerstein - Page 117
3a. Convert from static to dynamic block.
 Assign the
complex
string to a
variable.
 Makes it
easier to
report
errors and
debug.
dynblock :=
'DECLARE
v_loc PLS_INTEGER;
v_start PLS_INTEGER := 1;
v_item ' || datatype || ';
BEGIN ' ||
collname || '.DELETE;
IF :str IS NOT NULL
THEN
LOOP
v_loc := INSTR (:str, :delim, v_start);
IF v_loc = v_startloc
THEN
v_item := NULL;
ELSIF v_loc = 0
THEN
v_item := SUBSTR (:str, v_start);
ELSE
v_item := SUBSTR (:str, v_start, v_loc - v_start);
END IF;' ||
collname || '(' || nextrowstring || ') := v_item;
IF v_loc = 0 THEN EXIT;
ELSE v_start := v_loc + 1;
END IF;
END LOOP;
END IF;
END;';
str2list.pkg
Copyright 2000-2006 Steven Feuerstein - Page 118
3b. Execute the dynamic block.
 With dynamic PL/SQL, even if you reference
the same bind variable more than once, you
only specify it once in the USING clause.
– In other words, PL/SQL is using a variation of
"named notation" rather than the default
positional notation for dynamic SQL statements.
EXECUTE IMMEDIATE dynblock
USING str, delim;
Copyright 2000-2006 Steven Feuerstein - Page 119
Method 4 Dynamic SQL with DBMS_SQL
 Method 4 dynamic SQL is the most generalized
and most complex - by far!
– You don't know at compile time either the number of
columns or the number of bind variables.
– With DBMS_SQL, you must put calls to
DBMS_SQL.DEFINE_COLUMN and/or
DBMS_SQL.BIND_VARIABLE into loops.
 With NDS, you must shift from dynamic SQL to
dynamic PL/SQL.
– How else can you have a variable INTO or USING
clause?
Copyright 2000-2006 Steven Feuerstein - Page 120
Dynamic "SELECT * FROM <table>" in PL/SQL
 You provide the table and WHERE clause. I
display all the data.
– I don't know in advance which or how many rows
to query.
 I can obtain the column information from
ALL_TAB_COLUMNS...and from there the
fun begins!
 A relatively simple example to use as a
starting point. intab_dbms_sql.sp - uses DBMS_SQL
intab_nds.sp - uses NDS
intab.tst
Copyright 2000-2006 Steven Feuerstein - Page 121
Pseudo-code flow for
DBMS_SQL implementation
BEGIN
FOR each-column-in-table LOOP
add-column-to-select-list;
END LOOP;
DBMS_SQL.PARSE (cur, select_string, DBMS_SQL.NATIVE);
FOR each-column-in-table LOOP
DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype);
END LOOP;
fdbk := DBMS_SQL.EXECUTE (cur);
LOOP
fetch-a-row;
FOR each-column-in-table LOOP
DBMS_SQL.COLUMN_VALUE (cur, nth_col, val);
END LOOP;
END LOOP;
END;
Build the
SELECT list
Define each
column
Extract each
value
Parse the
variable SQL
Execute the
query
Lots of code, but relatively
straightforward
Copyright 2000-2006 Steven Feuerstein - Page 122
Parsing very long strings
 One problem with EXECUTE IMMEDIATE is
that you pass it a single VARCHAR2 string.
– Maximum length 32K.
 So what do you do when your string is longer?
– Very likely to happen when you are generating SQL
statements based on tables with many columns.
– Also when you want to dynamically compile a
program.
 Time to switch to DBMS_SQL!
Copyright 2000-2006 Steven Feuerstein - Page 123
DBMS_SQL.PARSE overloading for collections
 Oracle offers an overloading of
DBMS_SQL.PARSE that accepts a collection
of strings, rather than a single string.
 DBMS_SQL offers two different array types:
– DBMS_SQL.VARCHAR2S - each string max 255
bytes.
– DBMS_SQL.VARCHAR2A - each string max
32,767 bytes (new in Oracle9i).
Copyright 2000-2006 Steven Feuerstein - Page 124
Compile DDL from a file with DBMS_SQL
CREATE OR REPLACE PROCEDURE exec_from_ddl_file (
dir_in IN VARCHAR2
, file_in IN VARCHAR2
)
IS
l_file UTL_FILE.file_type;
l_lines DBMS_SQL.varchar2s;
l_cur PLS_INTEGER := DBMS_SQL.open_cursor;
PROCEDURE read_file (lines_out IN OUT DBMS_SQL.varchar2s) IS
BEGIN
... Read each line into array; see compile_from_file.sql
l_file := UTL_FILE.fopen (dir_in, file_in, 'R');
END read_file;
BEGIN
read_file (l_lines);
DBMS_SQL.parse (l_cur
, l_lines
, l_lines.FIRST
, l_lines.LAST
, TRUE
, DBMS_SQL.native
);
DBMS_SQL.close_cursor (l_cur); exec_ddl_from_file.sql
You can specify a
subset of lines in
the array.
Copyright 2000-2006 Steven Feuerstein - Page 125
Describe columns in a query
 DBMS_SQL offers the ability to "ask" a
cursor to describe the columns defined in
that cursor.
 By using the DESCRIBE_COLUMNS
procedure, you can sometimes avoid
complex parsing and analysis logic.
– Particularly useful with method 4 dynamic SQL.
desccols.pkg
desccols.tst
Copyright 2000-2006 Steven Feuerstein - Page 126
Don’t take SQL for granted: hide it!
 I moan and groan about SQL because it is the
"Achilles Heel" of PL/SQL.
– It's so easy to write SQL, it is too easy. Let's take a
look...
"Why does Steven make such a
big deal about writing SQL inside
PL/SQL? It's a no-brainer in
PL/SQL, the last thing we have to
worry about!"
Copyright 2000-2006 Steven Feuerstein - Page 127
Why We Write PL/SQL Code
 PL/SQL is an embedded language. Its purpose
is to provide high-speed, easy access to the
Oracle RDBMS.
 The layer of PL/SQL code should support the
data model, not disrupt our ability to evolve it.
Order
Table
Item
Table
Order Entry
Program
Bottom line: if everyone
writes SQL whenever and
wherever they want to, it is
very difficult to maintain and
optimize the code.
Copyright 2000-2006 Steven Feuerstein - Page 128
Single Point of (SQL) Robust Definition
 The same (logically equivalent) SQL statement
should not appear more than once in your
application code.
– Make sure that no SQL statement is repeated.
 How can this be accomplished?
– Intense personal discipline and peer pressure?
– Endless and very detailed code review?
– Automated analysis based on dependency views?
 None of these will do the trick.
Copyright 2000-2006 Steven Feuerstein - Page 129
How to Avoid SQL Repetition
 You should, as a rule, not even
write SQL in your PL/SQL
programs
– You can't repeat it if you don't write
it
 Instead, rely on pre-built, pre-
tested, written-once, used-often
PL/SQL programs.
– "Hide" both individual SQL
statements and entire transactions.
SQL
Copyright 2000-2006 Steven Feuerstein - Page 130
Best option: comprehensive table APIs
 Many (not all!) of the SQL statements we need to
write against underlying tables and views are very
common and predictable.
– Get me all rows for a foreign key.
– Get me one row for a primary key.
– Insert a row; insert a collection of rows.
 Why write these over and over? Instead, rely on a
standard, preferably generated, programmatic
interface that takes care of this "basic plumbing."
Qnxo
aka the Quest CodeGen Utility
www.qnxo.com
SOA for PL/SQL Developers!
SQL is a service.
Error mgt is a service.
Copyright 2000-2006 Steven Feuerstein - Page 131
Clear benefits of encapsulated SQL
 Change/improve implementation without
affecting application layer of code.
– Switch between types of queries (implicit vs explicit)
– Take advantage of data caching, bulk processing,
SQL enhancements like MERGE.
 Consistent error handling
– INSERT: dup_val_on_index?
– SELECT: too_many_rows?
– Much less likely to be ignored when the developer
writes SQL directly in the application.
Copyright 2000-2006 Steven Feuerstein - Page 132
Example: Quest Code Tester backend
 For each table, we have
three generated packages:
– <table>_CP for DML
– <table>_QP for queries
– <table>_TP for types
 And for many an "extra
stuff" package with custom
SQL logic and related code:
– <table>_XP
qu_outcome_xp.qu_outcomes
qu_outcome_xp.int_create_outcomes
Copyright 2000-2006 Steven Feuerstein - Page 133
Hide single row queries
 Let's look at specific examples of encapsulations.
First: single row queries.
– Does a row exist? Get me the row for a unique value.
 Steps to follow:
– Do not write your query directly in application code.
– Establish clear rules: how NO_DATA_FOUND and
other common errors are handled; how are single row
queries implemented?
– Build or generate a function to return the information,
usually in the form of a record.
single_row_api.sql
Copyright 2000-2006 Steven Feuerstein - Page 134
l_name employee_rp.fullname_t;
BEGIN
l_name :=
employee_rp.fullname (
employee_id_in);
...
END;
CREATE OR REPLACE PACKAGE employee_rp
AS
SUBTYPE fullname_t IS VARCHAR2 (200);
-- The formula
FUNCTION fullname (
l employee.last_name%TYPE,
f employee.first_name%TYPE
)
RETURN fullname_t;
-- Retrieval function
FUNCTION fullname (
employee_id_in IN
employee.employee_id%TYPE
)
RETURN fullname_t;
END;
/
Encapsulate SQL and rules...
CREATE OR REPLACE PROCEDURE
process_employee (
employee_id IN number)
IS
l_name VARCHAR2(100);
BEGIN
SELECT last_name || ',' ||
first_name
INTO l_name
FROM employee
WHERE employee_id =
employee_id;
...
END;
And now call the function...
fullname.pkg
explimpl.pkg
Get me the name for an ID...
Copyright 2000-2006 Steven Feuerstein - Page 135
Hide multi-row queries
 A trickier encapsulation challenge: how do
you return multiple rows?
– We will need a "container" or mechanism that is
not just a single instance of a row.
 Options in PL/SQL from Oracle9i upwards:
– Collection - use BULK COLLECT!
– Cursor variable - especially handy when
returning data to a non-PL/SQL host environment
Copyright 2000-2006 Steven Feuerstein - Page 136
Return multiple rows into a collection
 Collection type
must be
declared!
– Can do so in
package
specification or
even as a
schema level
object.
CREATE OR REPLACE PACKAGE BODY multirows
IS
FUNCTION emps_in_dept (
dept_in IN employee.department_id%TYPE )
RETURN employees_aat
IS
l_employees employees_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
FROM employees
WHERE department_id = dept_in;
RETURN l_employees;
END emps_in_dept;
END multirows;
multirows.sql
Copyright 2000-2006 Steven Feuerstein - Page 137
Return multiple rows w/ cursor variable
 A cursor variable is a variable that points to a
result set.
– You can pass CVs from one program unit to
another, and even to non-PL/SQL programs!
– Java, .Net, VB, etc. generally recognize and can
work with cursor variables (fetch and even
close).
 Uses the OPEN...FOR statement to
associate the variable with a query. return_refcur1.sql
return_refcur.tst
ref_cursor_example.sql
Copyright 2000-2006 Steven Feuerstein - Page 138
Hide complex data transformations
 Sometimes you need to return multiple rows of
data that are the result of a complex
transformation.
– Can't fit it all (easily) into a SELECT statement.
 Table functions to the rescue!
– A table function is a function that returns a collection
and can be called in the FROM clause of a query.
– Combine with cursor variables to return these
datasets through a function interface.
tabfunc_scalar.sql
tabfunc_streaming.sql
tabfunc_pipelined.sql
Copyright 2000-2006 Steven Feuerstein - Page 139
Hide single and multi-row DML
operations
 As crucial as it is to hide queries, it is even
more important to encapsulate DML.
– Error management is more complex and critical.
– Performance impact is greater.
 A generalized UPDATE is usually the most
complicated.
– Probably will need to hand-code specific update
column combinations yourself.
employees_cp.pkb
Copyright 2000-2006 Steven Feuerstein - Page 140
SQL in PL/SQL: Think "services"
 Don't take SQL for granted.
– Just because it's easy, doesn't mean it's not
significant.
 Hide SQL behind an API: serve up the SQL
via procedures and functions.
 Take advantage of key features to improve
performance and usability.
– BULK COLLECT, FORALL, table functions,
dynamic SQL
Copyright 2000-2006 Steven Feuerstein - Page 141
> Manage errors effectively and consistently
 A significant challenge in any programming
environment.
– Ideally, errors are raised, handled, logged and
communicated in a consistent, robust manner
 Some special issues for PL/SQL developers
– The EXCEPTION datatype
– How to find the line on which the error is raised?
– Communication with non-PL/SQL host
environments
Copyright 2000-2006 Steven Feuerstein - Page 142
Achieving ideal error management
 Define your requirements clearly
 Understand PL/SQL error management
features and make full use of what PL/SQL
has to offer
 Apply best practices.
– Compensate for PL/SQL weaknesses
– Single point of definition: use reusable
components to ensure consistent, robust error
management
Copyright 2000-2006 Steven Feuerstein - Page 143
Define your requirements clearly
 When will errors be raised, when handled?
– Do you let errors go unhandled to the host, trap
locally, or trap at the top-most level?
 How should errors be raised and handled?
– Will users do whatever they want or will there be
standard approaches that everyone will follow?
 Useful to conceptualize errors into three
categories:
– Deliberate, unfortunate, unexpected
Copyright 2000-2006 Steven Feuerstein - Page 144
Different types of exceptions
 Deliberate
– The code architecture itself deliberately relies on an
exception. Example: UTL_FILE.GET_LINE
 Unfortunate
– It is an error, but one that is to be expected and may not
even indicate a problem. Example: SELECT INTO ->
NO_DATA_FOUND
 Unexpected
– A "hard" error that indicates a problem within the application.
Example: Primary key lookup raises TOO_MANY ROWS
exec_ddl_from_file.sql
get_nextline.sf
fullname.pkb
fullname.pkb
Copyright 2000-2006 Steven Feuerstein - Page 145
PL/SQL error management features
 Defining exceptions
 Raising exceptions
 Handling exceptions
 Exceptions and DML
Copyright 2000-2006 Steven Feuerstein - Page 146
Quiz! Test your exception handling know-how
DECLARE
aname VARCHAR2(5);
BEGIN
BEGIN
aname := 'Justice';
DBMS_OUTPUT.PUT_LINE (aname);
EXCEPTION
WHEN VALUE_ERROR
THEN
DBMS_OUTPUT.PUT_LINE ('Inner block');
END;
DBMS_OUTPUT.PUT_LINE ('What error?');
EXCEPTION
WHEN VALUE_ERROR
THEN
DBMS_OUTPUT.PUT_LINE ('Outer block');
END;
excquiz1.sql
 What do you see after running this block?
Copyright 2000-2006 Steven Feuerstein - Page 147
Defining Exceptions
 The EXCEPTION is a limited type of data.
– Has just two attributes: code and message.
– You can RAISE and handle an exception, but it
cannot be passed as an argument in a program.
 Give names to error numbers with the
EXCEPTION_INIT PRAGMA.
CREATE OR REPLACE PROCEDURE upd_for_dept (
dept_in IN employee.department_id%TYPE
, newsal_in IN employee.salary%TYPE
)
IS
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT (bulk_errors, -24381);
Copyright 2000-2006 Steven Feuerstein - Page 148
Raising Exceptions
 RAISE raises the specified exception by
name.
– RAISE; re-raises current exception. Callable only
within the exception section.
 RAISE_APPLICATION_ERROR
– Communicates an application specific error back
to a non-PL/SQL host environment.
– Error numbers restricted to the -20,999 - -20,000
range.
Copyright 2000-2006 Steven Feuerstein - Page 149
Using RAISE_APPLICATION_ERROR
IF :NEW.birthdate > ADD_MONTHS (SYSDATE, -1 * 18 * 12)
THEN
RAISE_APPLICATION_ERROR
(-20070, ‘Employee must be 18.’);
END IF;
 Communicate an error number and message to a
non-PL/SQL host environment.
– The following code from a database triggers shows a
typical (and problematic) usage of
RAISE_APPLICATION_ERROR:
RAISE_APPLICATION_ERROR
(num binary_integer, msg varchar2,
keeperrorstack boolean default FALSE);
Copyright 2000-2006 Steven Feuerstein - Page 150
Quiz: An Exceptional Package
 So I create the valerr package and then execute the
following command. What is displayed on the screen?
PACKAGE valerr
IS
FUNCTION
get RETURN VARCHAR2;
END valerr;
SQL> EXECUTE p.l (valerr.get);
PACKAGE BODY valerr
IS
v VARCHAR2(1) := ‘abc’;
FUNCTION get RETURN VARCHAR2 IS
BEGIN
RETURN v;
END;
BEGIN
p.l ('Before I show you v...');
EXCEPTION
WHEN OTHERS THEN
p.l (‘Trapped the error!’);
END valerr;
valerr.pkg
valerr2.pkg
Copyright 2000-2006 Steven Feuerstein - Page 151
Handling Exceptions
 The EXCEPTION section consolidates all error handling
logic in a block.
– But only traps errors raised in the executable section of the
block.
 Several useful functions usually come into play:
– SQLCODE and SQLERRM
– DBMS_UTILITY.FORMAT_ERROR_STACK
– DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
 The DBMS_ERRLOG package
– Quick and easy logging of DML errors
 The AFTER SERVERERROR trigger
– Instance-wide error handling
Copyright 2000-2006 Steven Feuerstein - Page 152
DBMS_UTILITY error functions
 Get the full error message with
DBMS_UTILITY.FORMAT_ERROR_STACK
– SQLERRM might truncate the message.
– Use SQLERRM went you want to obtain the
message associated with an error number.
 Find line number on which error was raised with
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
– Introduced in Oracle10g Release 2, it returns the full
stack of errors with line number information.
– Formerly, this stack was available only if you let the
error go unhandled.
backtrace.sql
Copyright 2000-2006 Steven Feuerstein - Page 153
DBMS_ERRLOG (Oracle10gR2)
 Allows DML statements to execute against all
rows, even if an error occurs.
– The LOG ERRORS clause specifies how logging
should occur.
– Use the DBMS_ERRLOG package to associate
a log table with DML operations on a base table.
 Much faster than trapping errors, logging,
and then continuing/recovering.
 Note: FORALL with SAVE EXCEPTIONS
offers similar capabilities.
dbms_errlog.*
Copyright 2000-2006 Steven Feuerstein - Page 154
The AFTER SERVERERROR trigger
 Provides a relatively simple way to use a
single table and single procedure for
exception handling in an entire instance.
 Drawbacks:
– Error must go unhandled out of your PL/SQL
block for the trigger to kick in.
– Does not fire for all errors (NO: -600, -1403, -
1422...)
 Most useful for non-PL/SQL front ends
executing SQL statements directly.
afterservererror.sql
Copyright 2000-2006 Steven Feuerstein - Page 155
Exceptions and DML
 DML statements generally are not rolled back when an
exception is raised.
– This gives you more control over your transaction.
 Rollbacks occur with...
– Unhandled exception from the outermost PL/SQL block;
– Exit from autonomous transaction without commit/rollback;
– Other serious errors, such as "Rollback segment too small".
 Corollary: error logs should rely on autonomous
transactions to avoid sharing the same transaction as
the application.
– Log information is committed, while leaving the business
transaction unresolved.
log8i.pkg
Copyright 2000-2006 Steven Feuerstein - Page 156
Best practices for error management
 Compensate for PL/SQL weaknesses.
 Some general guidelines:
– Avoid hard-coding of error numbers and messages.
– Build and use reusable components for raising, handling
and logging errors.
 Application-level code should not contain:
– RAISE_APPLICATION_ERROR: don't leave it to the
developer to decide how to raise.
– PRAGMA EXCEPTION_INIT: avoid duplication of error
definitions.
Copyright 2000-2006 Steven Feuerstein - Page 157
Compensate for PL/SQL weaknesses
 The EXCEPTION datatype does not allow you
to store the full set of information about an
error.
– What was the context in which the error occurred?
 Difficult to ensure execution of common error
handling logic.
– Usually end up with lots of repetition.
– No "finally" section available in PL/SQL - yet.
 Restrictions on how you can specify the error
– Only 1000 for application-specific errors....
Copyright 2000-2006 Steven Feuerstein - Page 158
Object-like representation of an exception
 An error is a row in the error table, with many
more attributes than simply code and
message, including:
– Dynamic message (substitution variables)
– Help message (how to recover from the problem)
 An error instance is one particular
occurrence of an error.
– Associated with it are one or more values that
reflect the context in which the error was raised.
Copyright 2000-2006 Steven Feuerstein - Page 159
ERD for error definition tables
qd_error.erd
qd_runtime.pkb
Copyright 2000-2006 Steven Feuerstein - Page 160
Hard to avoid code repetition in handlers
 If every developer writes exception handler code
on their own, you end up with an unmanageable
situation.
– Different logging mechanisms, no standards for error
message text, inconsistent handling of the same errors, etc.
WHEN NO_DATA_FOUND THEN
INSERT INTO errlog
VALUES ( SQLCODE
, 'No company for id ' || TO_CHAR ( v_id )
, 'fixdebt', SYSDATE, USER );
WHEN OTHERS THEN
INSERT INTO errlog
VALUES (SQLCODE, SQLERRM, 'fixdebt', SYSDATE, USER );
RAISE;
END;
Copyright 2000-2006 Steven Feuerstein - Page 161
Prototype exception manager package
PACKAGE errpkg
IS
PROCEDURE raise (err_in IN PLS_INTEGER);
PROCEDURE raise (err_in in VARCHAR2);
PROCEDURE record_and_stop (
err_in IN PLS_INTEGER := SQLCODE
,msg_in IN VARCHAR2 := NULL);
PROCEDURE record_and_continue (
err_in IN PLS_INTEGER := SQLCODE
,msg_in IN VARCHAR2 := NULL);
END errpkg;
Generic Raises
Record
and Stop
Record
and Continue
errpkg.pkg
Copyright 2000-2006 Steven Feuerstein - Page 162
Invoking standard handlers
 The rule: developers should only call a pre-defined handler
inside an exception section
– Make it easy for developers to write consistent, high-quality code
– They don't have to make decisions about the form of the log and
how the process should be stopped
EXCEPTION
WHEN NO_DATA_FOUND
THEN
errpkg.record_and_continue (
SQLCODE,
' No company for id ' || TO_CHAR (v_id));
WHEN OTHERS
THEN
errpkg.record_and_stop;
END;
The developer simply
describes
the desired action.
Copyright 2000-2006 Steven Feuerstein - Page 163
Specifying the error
Options for communicating an
application-specific error:
• Just use -20000 all the time?
* Pick a number from -20999 to -20000?
* Use any positive error number
besides 1 and 100?
Use error names instead of numbers?
Perhaps the error number is of no real importance!
Copyright 2000-2006 Steven Feuerstein - Page 164
Avoid hard-coding of -20,NNN Errors
 Give your
error numbers
names and
associate
them with
named
exceptions.
PACKAGE errnums
IS
en_general_error CONSTANT NUMBER := -20000;
exc_general_error EXCEPTION;
PRAGMA EXCEPTION_INIT
(exc_general_error, -20000);
en_must_be_18 CONSTANT NUMBER := -20001;
exc_must_be_18 EXCEPTION;
PRAGMA EXCEPTION_INIT
(exc_must_be_18, -20001);
en_sal_too_low CONSTANT NUMBER := -20002;
exc_sal_too_low EXCEPTION;
PRAGMA EXCEPTION_INIT
(exc_sal_too_low , -20002);
max_error_used CONSTANT NUMBER := -20002;
END errnums; msginfo.pkg
msginfo.fmb/fmx
But don't write this
code manually!
Copyright 2000-2006 Steven Feuerstein - Page 165
Using the standard raise program
 Rather than have individual programmers call
RAISE_APPLICATION_ERROR, simply call the standard raise
program. Benefits:
– Easier to avoid hard-codings of numbers.
– Support positive error numbers!
 Let's revisit that trigger logic using the infrastructure elements...
PROCEDURE validate_emp (birthdate_in IN DATE) IS
BEGIN
IF ADD_MONTHS (SYSDATE, 18 * 12 * -1) < birthdate_in
THEN
errpkg.raise (errnums.en_too_young);
END IF;
END; No more hard-coded
strings or numbers.
Copyright 2000-2006 Steven Feuerstein - Page 166
Raise/handle errors by number...or name?
 The above trigger fragment illustrates a common
problem: Hard-coding of error numbers and
messages.
 Certainly, it is better to use named constants, as in:
BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
RAISE_APPLICATION_ERROR (
-20175, 'You must be at least 18 years old!');
END IF;
BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
RAISE_APPLICATION_ERROR (
employee_rp.en_too_young
, employee_rp.em_too_young);
END IF;
But now we have a
centralized
dependency.
Copyright 2000-2006 Steven Feuerstein - Page 167
Raising errors by name
 Use an error name (literal value).
– The code compiles now.
– Later, I define that error in the repository.
– No central point of failure.
 Downsides: risk of typos, runtime notification of
"undefined error."
BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
qd_runtime.raise_error (
'EMPLOYEE-TOO-YOUNG'
, name1_in => 'LAST_NAME'
, value1_in => :new.last_name);
END IF;
Qnxo
qd_runtime.*
Copyright 2000-2006 Steven Feuerstein - Page 168
Summary: error management with PL/SQL
 Make sure you understand how it all works
– Exception handling is tricky stuff
 Set standards before you start coding
– It's not the kind of thing you can easily add in later
 Use standard infrastructure components
– Everyone and all programs need to handle errors
the same way
 Don't accept the limitations of Oracle's current
implementation.
– You can do lots to improve the situation.
Copyright 2000-2006 Steven Feuerstein - Page 169
Six
Simple
Steps
to
Unit Testing
Happiness
> Unit test PL/SQL programs or....
Copyright 2000-2006 Steven Feuerstein - Page 170
Writing software is.....
Copyright 2000-2006 Steven Feuerstein - Page 171
Testing software is.....
Copyright 2000-2006 Steven Feuerstein - Page 172
Buggy software is....
Embarrassing
Expensive
Deadly
Copyright 2000-2006 Steven Feuerstein - Page 173
Buggy software is embarrassing
 There can be as many as 20 to 30 bugs per 1,000 lines
of software code. —Sustainable Computing Consortium
 32% of organizations say that they release software
with too many defects.—Cutter Consortium
 38% of organizations believe they lack an adequate
software quality assurance program.—Cutter
Consortium
 27% of organizations do not conduct any formal quality
reviews.—Cutter Consortium
 Developers spend about 80% of development costs on
identifying and correcting defects.—The National
Institute of Standards and Technology
Copyright 2000-2006 Steven Feuerstein - Page 174
Buggy software is expensive -
$60B per year in US alone!?
 JUNE 25, 2002 (COMPUTERWORLD) -
WASHINGTON -- Software bugs are costing the U.S.
economy an estimated $59.5 billion each year. Of
the total $59.5 billion cost, users incurred 64% of
the cost and developers 36%.
 There are very few markets where "buyers are
willing to accept products that they know are going
to malfunction," said Gregory Tassey, the National
Institute of Standards and Technology senior
economist who headed the study. "But software is
at the extreme end in terms of errors or bugs that
are in the typical product when it is sold."
 Oh, yes and Y2K: $300B? $600B?
Copyright 2000-2006 Steven Feuerstein - Page 175
Buggy software is deadly
 2003 Software failure contributes to power outage across the
Northeastern U.S. and Canada, killing 3 people.
 2001 Five Panamanian cancer patients die following
overdoses of radiation, amounts of which were determined
by faulty use of software.
 2000 Crash of a Marine Corps Osprey tilt-rotor aircraft
partially blamed on “software anomaly" kills four soldiers.
 1997 Radar that could have prevented Korean jet crash
(killing 225) hobbled by software problem.
 1995 American Airlines jet, descending into Cali, Colombia,
crashes into a mountain, killing 159. Jury holds maker of
flight-management system 17% responsible. A report by the
University of Bielefeld in Germany found that the software
presented insufficient and conflicting information to the
pilots, who got lost.
Copyright 2000-2006 Steven Feuerstein - Page 176
How do we avoid buggy software?
 Clear and accurate requirements
 Careful design
 Excellent tools
 Best practices, standards, guidelines (that is,
follow them)
 Code review
 Thorough testing
Uh oh...
the world is in
big trouble.
Copyright 2000-2006 Steven Feuerstein - Page 177
Wouldn't it be great if...
 It was easy to construct tests
– An agreed-upon and effective approach to test
construction that everyone can understand and follow
 It was easy to run tests
– And see the results, instantly and automatically.
 Testing were completely integrated into my
development, QA, and maintenance processes
– No program goes to QA until it passes its unit tests
– Anyone can maintain with confidence, because my
test suite automatically validates my changes
Copyright 2000-2006 Steven Feuerstein - Page 178
Different types of testing
 There are many types of testing:
functional/system tests, stress tests, unit tests.
 A "unit test" is the test of a single unit of code.
– Also known as "programmer tests"
 Unit tests are the responsibility of developers -
that is, us, the people in this room.
– Not fundamentally a job for the QA department,
which generally focuses on functional and system
tests.
Copyright 2000-2006 Steven Feuerstein - Page 179
Truth or Dare
 How do you (or your team) unit test
your PL/SQL code today?
–We use automated testing software.
–We have a formal test process that we
each follow, but otherwise a manual
process.
–Everyone does their own thing and we
hope for the best.
–Our users test our code.
?
?
?
?
Copyright 2000-2006 Steven Feuerstein - Page 180
Unit testing reality
 Let's face it: we PL/SQL developers don't spend
nearly enough time unit testing our code.
– For the most part, we run a script that displays
output on the screen and then we stare at all until
we decide if the test succeeded or failed.
 There are some understandable reasons:
– Very few tools and utilities have been available, to
date, for PL/SQL testing.
– Managers don't give us enough time to prepare and
execute tests.
Copyright 2000-2006 Steven Feuerstein - Page 181
Typical Testing
 DBMS_OUTPUT.PUT_LINE - unit testing
mechanism of choice?
betwnstr.sf
betwnstr.tst
BEGIN
DBMS_OUTPUT.PUT_LINE (betwnstr (NULL, 3, 5, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 0, 5, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, 5, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', -3, -5, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', NULL, 5, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, NULL, true));
DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, 100, true));
END;
Copyright 2000-2006 Steven Feuerstein - Page 182
Problems with Typical Testing
 Almost entirely ad hoc
– No comprehensive effort to compile test cases
– No infrastructure to record cases and administer tests
 Difficult to verify correctness
– Non-automated verification is slow and error-prone.
 Relies on the user community to test
– Since we are never really sure we’ve tested properly,
we rely on our users to finish our job.
There has got to be a better way!
Copyright 2000-2006 Steven Feuerstein - Page 183
Moving towards a Better Way
 Change from within: your code will not test itself.
– You must accept the responsibility and then be
disciplined (sigh...that's not fun at all).
– Commit to testing and see how much that changes
the way you write your programs.
 Change from without: you need tools to help you
do your testing.
– utPLSQL
– Quest Code Tester for Oracle
 Ah, but what about those six, simple steps?
http://utplsql.sourceforge.net/
http://www.ToadWorld.com
Copyright 2000-2006 Steven Feuerstein - Page 184
Six Simple Steps to Unit Testing Happiness
Define
Req’ments
1 Construct
Header
2 Define
Tests
3 Build
Test Code
4
Build /
Fix Code
5
Test Code
Debug
6
The Build
Cycle
Bug
Report
Enhance.
Request
Post-
Production
Copyright 2000-2006 Steven Feuerstein - Page 185
Six Simple Steps...Elaborated
 1. Describe fully the required functionality of the program.
– Don’t start coding until you are sure you understand what is needed, and be ready to
engage with your user on all levels (technical, business, ethical, etc.)
 2. Define the header of the program (name, parameter list, return value).
– Once you do that, you can compile a “stub” of that program.
 3. Come up with at least an initial set of test cases for the program.
– Think about what it will take to prove your program works.
 4. Build test code that implements all test cases.
– Ah...the truly challenging step.
 5. Write the program unit.
– Back to your comfort zone, but now you are building against a test plan.
 6. Test, debug, fix, test, debug, fix, test, debug....
– We never get it right the first time.
 Then...repeat steps 3-6 for each enhancement and bug report.
– Every bug report highlights two bugs: one in your code and another in your test case.
Copyright 2000-2006 Steven Feuerstein - Page 186
Describe required functionality
 I need a variation of SUBSTR that will return the
portion of a string between specified start and end
locations.
 Some specific requirements:
– It should work like SUBSTR as much as makes sense (treat
a start location of 0 as 1, for example; if the end location is
past the end of the string, the treat it as the end of the string).
– Negative start and end should return a substring at the end of
the string.
– Allow the user to specify whether or not the endpoints should
be included.
Copyright 2000-2006 Steven Feuerstein - Page 187
Define the program specification
 My specification or header should be compatible with
all requirements.
– I also self-document that the function is deterministic: no
side effects.
 I can (and will) now create a compile-able stub for the
program.
– Then fully define and implement my test code!
FUNCTION betwnstr (
string_in IN VARCHAR2
, start_in IN PLS_INTEGER
, end_in IN PLS_INTEGER
, inclusive_in IN BOOLEAN DEFAULT TRUE
)
RETURN VARCHAR2 DETERMINISTIC
betwnstr0.sf
Copyright 2000-2006 Steven Feuerstein - Page 188
Elaborate the test cases
 Before I write any code, I will come up with as many of
the test cases as possible -- and write my test code.
– This is known as "test-driven development". TDD is a very hot
topic among developers and is associated with Agile Software
(http://agilemanifesto.org/) and Extreme Programming.
 Putting aside the fancy names and methodologies, TDD
makes perfect sense -- when you stop to think about it.
If you write your program before you define your
tests, how do you know you when you're done?
TNT or TDD?
And if you write your tests afterward, you are likely
to prejudice your tests to show "success."
Copyright 2000-2006 Steven Feuerstein - Page 189
Brainstorm the test cases
 Even a simple program will have many test
cases!
– You don't have to think of every one before you
implement your program and start your testing.
– You should aim at least for a "representative"
sampling.
 But where do you store/define the test cases?
– You can certainly put the information in and work
from a document or spreadsheet.
– Best of all, however, is to link the test case
definitions as tightly as possible to the code.
Copyright 2000-2006 Steven Feuerstein - Page 190
Some of the test cases for BETWNSTR
 Start and end within the string ("normal" usage)
 Start of 0, End past end of string
 Null string, string of single character, 32767 len character
 Null start and/or end
 Negative start and end
 Start larger than end (positive and negative)
 Variations of the above with different inclusive values
Don't be overwhelmed by the total number of cases. Start out
with a representative sampling. You can always add from there.
But now a big challenge: where do you put this test case
information? Tie it as closely as possible to your code.
Copyright 2000-2006 Steven Feuerstein - Page 191
Test Cases and Test Code
 The challenge (terror?) of the blank screen....
– How do I define the test cases?
– How do I set up those tests?
– How do I verify the results?
 Let's see how Quest Code Tester helps me
tackle these challenges.
– Define and maintain your test cases through a
graphical interface, then let it do all the work.
Copyright 2000-2006 Steven Feuerstein - Page 192
Don't write test code – describe tests
 Rather than write test code, describe the tests you
need through a graphical interface.
– It’s just like SQL vs. programming.
 Quest Code Tester saves your descriptions in a test
repository.
– Critical information, especially for IT management
 It generates a PL/SQL test package based on your
descriptions.
 It runs the test at your request and displays the
results.
Time for some software magic....
Copyright 2000-2006 Steven Feuerstein - Page 193
Test, debug, fix, test, debug, fix, test, debug...
 With a test script in place, I can quickly and easily
move back and forth between running my program,
identifying errors, debugging and fixing the code,
running the program again.
 I also then have my test process and regression
test in place so that as I make enhancements or fix
bugs, I can fall back on this foundation.
– It is critical that you maintain your test case definitions
and test code as your program evolves.
– And update those first -- before you change the program!
Copyright 2000-2006 Steven Feuerstein - Page 194
Change Your Testing Ways
 Quest Code Tester (and even utPLSQL) can
make a dramatic difference in your ability to
test and your confidence in the resulting code.
 Build a comprehensive "library" of unit tests
as you build your application
– These tests and all their test cases can be
passed on to other developers
– Anyone can now enhance or maintain the code
with confidence. Make your changes and run the
tests. If you get a green light, you're OK!
Copyright 2000-2006 Steven Feuerstein - Page 195
Testing: Baby steps better than paralysis.
 Unit testing can be an intimidating process.
– You are never really done; You have to maintain
your test code along with your application code.
 But every incremental improvement in testing
yields immediate and long-term benefits.
– Don't worry about 100% test coverage.
– Give Quest Code Tester a try!
– Everything shown in this class will be part of the
freeware version.
www.ToadWorld.com
Copyright 2000-2006 Steven Feuerstein - Page 196
> Write readable, maintainable code
 PL/SQL allows you to write very readable,
self-documenting and easily-maintained code.
– This should be a primary objective for any
program.
 Let's look at...
– Readability features you should use
– Modular construction in PL/SQL
Copyright 2000-2006 Steven Feuerstein - Page 197
Readability features you should use
 END labels
– For program units, loops, nested blocks
 SUBTYPEs
– Create application-specific datatypes!
 Named notation
– Sometimes the extra typing is worth it!
 Naming conventions
– Being consistent is the most important thing.
 Local or nested modules
– Key technique, to be covered under "Modular construction..."
end_labels.sql
plsql_limits.pks
explimpl.pkg
namednot.sql
www.oracle.oreilly.com OraclePL/SQL Best Practices Examples
Copyright 2000-2006 Steven Feuerstein - Page 198
Modular construction in PL/SQL
 Packages: some quick reminders...
–Logical containers for related elements
–Overloading
–Package-level data and caching
–Initialization section
 Local or nested modules
–Avoid spaghetti code!
–Keep your executable sections small/tiny.
Copyright 2000-2006 Steven Feuerstein - Page 199
Packages: key PL/SQL building block
 Employ object-oriented design principles
– Build at higher levels of abstraction
– Enforce information hiding - control what people see and do
– Call packaged code from object types and triggers
 Encourages top-down design and bottom-up
construction
– TD: Design the interfaces required by the different
components of your application without addressing
implementation details
– BU: Existing packages contain building blocks for new code
 Organize your stored code more effectively
 Implements session-persistent data
Copyright 2000-2006 Steven Feuerstein - Page 200
Overloading in packages:
key usability technique
 Overloading (static polymorphism): two or more
programs with the same name, but different signature.
– You can overload in the declaration section of any PL/SQL
block, including the package body (most common).
 Overloading is a critical feature when building
comprehensive programmatic interfaces (APIs) or
components using packages.
– 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.
myproc
myfunc
myproc
Compare:
DBMS_OUTPUT and p
Copyright 2000-2006 Steven Feuerstein - Page 201
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.
– Another name for overloading is "static polymorphism."
 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.
Copyright 2000-2006 Steven Feuerstein - Page 202
How Overloading Works, continued
 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.
Copyright 2000-2006 Steven Feuerstein - Page 203
Quiz! Nuances of Overloading
 Is this a valid overloading? Will it compile? How can I use
it?
CREATE OR REPLACE PACKAGE sales
IS
PROCEDURE calc_total (zone_in IN VARCHAR2);
PROCEDURE calc_total (reg_in IN VARCHAR2);
END sales;
BEGIN
sales.calc_total ('NORTHWEST');
sales.calc_total ('ZONE2');
END;
ambig_overloading.sql
?
Copyright 2000-2006 Steven Feuerstein - Page 204
Package Data: Useful and Sticky
 The scope of a package is your session, and any
data defined at the "package level" also has
session scope.
– If defined in the package specification, any program can
directly read/write the data.
– Ideal for program-specific caching.
 General best practice: hide your package data in
the body so that you can control access to it.
 Use the SERIALLY_REUSABLE pragma to move
data to SGA and have memory released after each
usage. thisuser.pkg
thisuser.tst
serial.sql
Copyright 2000-2006 Steven Feuerstein - Page 205
Package Initialization Structure
 The initialization section:
– Is defined after and outside of any programs
in the package.
– Is not required.
– Can have its own exception handling
section.
 Useful for:
– Perform complex setting of default or initial
values.
– Set up package data which does not
change for the duration of a session.
– Confirm that package is properly
instantiated.
PACKAGE BODY pkg
IS
PROCEDURE proc IS
BEGIN
END;
FUNCTION func RETURN
BEGIN
END;
BEGIN
...initialize...
END pkg;
BEGIN after/outside
of any program
defined in the pkg.
init.pkg
init.tst
datemgr.pkg
Copyright 2000-2006 Steven Feuerstein - Page 206
Packages vs. Stand-alone programs
 General recommendation: use packages instead
of stand-alone programs.
– Better way to organize code.
– Can hide implementation and reduce need to
recompile programs using the package.
 Other considerations....
– Entire package loaded when any single program is
called.
– Central packages can become a "bottleneck" when
changes are needed.
recompile.sql
Copyright 2000-2006 Steven Feuerstein - Page 207
Limit executable sections to <= 50 lines
 It is virtually impossible to
understand and therefore debug
or maintain code that has long,
meandering executable sections.
 How do you follow this guideline?
– Don't skimp on the packages.
– Top-down design / step-wise
refinement
– Use lots of local or nested modules.
?!?!
Copyright 2000-2006 Steven Feuerstein - Page 208
PROCEDURE assign_workload (department_in IN NUMBER)
IS
CURSOR emps_in_dept_cur
IS
SELECT * FROM emp WHERE deptno = department_in;
PROCEDURE assign_next_open_case
(emp_id_in IN NUMBER, case_out OUT NUMBER) IS BEGIN … END;
FUNCTION next_appointment (case_id_in IN NUMBER) IS BEGIN … END;
PROCEDURE schedule_case
(case_in IN NUMBER, date_in IN DATE) IS BEGIN … END;
BEGIN /* main */
FOR emp_rec IN emps_in_dept_cur
LOOP
IF analysis.caseload (emp_rec.emp_id) <
analysis.avg_cases (department_in)
THEN
assign_next_open_case (emp_rec.emp_id, case#);
schedule_case
(case#, next_appointment (case#));
END IF;
END LOOP
END assign_workload;
 Move blocks of
complex code into
the declaration
section
 Replace them with
descriptive names
 The code is now
easier to read and
maintain
 You can more
easily identify
areas for
improvement Check out my series
on the OverloadCheck
utility on OTN
Let's read some code!
locmod.sp
10g_indices_of.sql
Copyright 2000-2006 Steven Feuerstein - Page 209
Challenges of local modules
 Requires discipline.
– Always be on the lookout for opportunities to refactor.
 Need to read from the bottom, up.
– Takes some getting used to.
 Your IDE needs to reveal the internal structure
of the program.
 Sometimes can feel like a "wild goose chase".
– Where is the darned thing actually implemented?
Copyright 2000-2006 Steven Feuerstein - Page 210
Thoughts on Deterministic Programs
 Some clear benefits to deterministic code:
– They are easier to test, since they have no side-
effects.
– Oracle will optimize/avoid execution - sometimes!
 My suggestions:
– Segregate as much of your code into deterministic
and non-deterministic program units.
– Carefully declare all deterministic programs.
show_deterministc.sql
deterministc.sql
Copyright 2000-2006 Steven Feuerstein - Page 211
Acknowledgements and Resources
 Very few of my ideas are truly
original. I have learned from
every one of these books and
authors – and you can, too!
Copyright 2000-2006 Steven Feuerstein - Page 212
A guide to my mentors/resources
 A Timeless Way of Building – a beautiful and deeply spiritual book on
architecture that changed the way many developers approach writing software.
 On Intelligence – a truly astonishing book that lays out very concisely a new
paradigm for understanding how our brains work.
 Peopleware – a classic text on the human element behind writing software.
 Refactoring – formalized techniques for improving the internals of one's code
without affect its behavior.
 Code Complete – another classic programming book covering many aspects of
code construction.
 The Cult of Information – thought-provoking analysis of some of the down-
sides of our information age.
 Patterns of Software – a book that wrestles with the realities and problems with
code reuse and design patterns.
 Extreme Programming Explained – excellent introduction to XP.
 Code and Other Laws of Cyberspace – a groundbreaking book that recasts
the role of software developers as law-writers, and questions the direction that
software is today taking us.
Copyright 2000-2006 Steven Feuerstein - Page 213
(Mostly) Free PL/SQL Resources
 Oracle Technology Network PL/SQL page
 OTN Best Practice PL/SQL
 Oracle documentation
 OraclePLSQLProgramming.com
 Quest Pipelines
 Quest Code Tester for Oracle
 PL/Vision
http://www.oracle.com/technology/tech/pl_sql/index.html
http://www.oracle.com/technology/pub/columns/plsql/index.html
http://tahiti.oracle.com/
http://oracleplsqlprogramming.com/
http://quest-pipelines.com/
http://quest-pipelines.com/pipelines/dba/PLVision/plvision.htm
http://www.ToadWorld.com http://unittest.inside.quest.com/index.jspa
Copyright 2000-2006 Steven Feuerstein - Page 214
So Much to Learn...
 Don't panic -- but don't stick your head in the
sand, either.
– You won't thrive as an Oracle7, Oracle8 or Oracle8i
developer!
 You can do so much more from within PL/SQL
than you ever could before.
– Familiarity with new features will greatly ease the
challenges you face.
Copyright 2000-2006 Steven Feuerstein - Page 215
And I end with my...
 1. Drink lots of water.
 2. Write tiny chunks of code.
 3. Stop writing so much SQL.
 4. Stop guessing and start testing.
Thank you for attending this Best of PL/SQL seminar!
Top Four Tips for Excellent PL/SQL

More Related Content

PDF
D34010.pdf
PDF
Secure pl-sql-coding
PPT
08 Dynamic SQL and Metadata
PDF
New Stuff in the Oracle PL/SQL Language
PDF
New(er) Stuff in PL/SQL
PPTX
Intro.pptx
PPTX
New PLSQL in Oracle Database 12c
D34010.pdf
Secure pl-sql-coding
08 Dynamic SQL and Metadata
New Stuff in the Oracle PL/SQL Language
New(er) Stuff in PL/SQL
Intro.pptx
New PLSQL in Oracle Database 12c

Similar to Best of Oracle PL/SQL | Wonderful Features (20)

PPTX
Plsql guide 2
PPT
PLSQL.ppt
PDF
Pl sql student guide v 1
PDF
Pl sql student guide v 3
PDF
Introduction to oracle 9i pl sql - part 2
PDF
Plsql 9i vol2
PDF
Oracle sql developer_slides
PDF
PDF
Oracle PL SQL Programming Sixth Edition Steven Feuerstein
PDF
Pl sql student guide v 4
PDF
Java Programming With Oracle Jdbc 1st Ed Donald Bales
PDF
Oracle PL SQL Programming Animal Guide 5th Edition Steven Feuerstein
PDF
Oracle PL SQL Programming Sixth Edition Steven Feuerstein
PPT
Oracle_PLSQL.ppt ..
PPT
Oracle_PLSQL (1).ppt .
PDF
PostgreSQL Server Programming Second Edition Usama Dar Hannu Krosing Jim Mlod...
PPTX
PL/SQL All the Things in Oracle SQL Developer
PDF
Download full ebook of Oracle Sql Developer Narayanan Ajith instant download pdf
PDF
(Ebook) Java Programming with Oracle JDBC by Donald Bales ISBN 9780596000882,...
Plsql guide 2
PLSQL.ppt
Pl sql student guide v 1
Pl sql student guide v 3
Introduction to oracle 9i pl sql - part 2
Plsql 9i vol2
Oracle sql developer_slides
Oracle PL SQL Programming Sixth Edition Steven Feuerstein
Pl sql student guide v 4
Java Programming With Oracle Jdbc 1st Ed Donald Bales
Oracle PL SQL Programming Animal Guide 5th Edition Steven Feuerstein
Oracle PL SQL Programming Sixth Edition Steven Feuerstein
Oracle_PLSQL.ppt ..
Oracle_PLSQL (1).ppt .
PostgreSQL Server Programming Second Edition Usama Dar Hannu Krosing Jim Mlod...
PL/SQL All the Things in Oracle SQL Developer
Download full ebook of Oracle Sql Developer Narayanan Ajith instant download pdf
(Ebook) Java Programming with Oracle JDBC by Donald Bales ISBN 9780596000882,...
Ad

Recently uploaded (20)

PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
KodekX | Application Modernization Development
PDF
Machine learning based COVID-19 study performance prediction
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Cloud computing and distributed systems.
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
cuic standard and advanced reporting.pdf
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Approach and Philosophy of On baking technology
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PPTX
MYSQL Presentation for SQL database connectivity
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Network Security Unit 5.pdf for BCA BBA.
KodekX | Application Modernization Development
Machine learning based COVID-19 study performance prediction
Building Integrated photovoltaic BIPV_UPV.pdf
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Digital-Transformation-Roadmap-for-Companies.pptx
Cloud computing and distributed systems.
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Spectral efficient network and resource selection model in 5G networks
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
cuic standard and advanced reporting.pdf
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Approach and Philosophy of On baking technology
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
MYSQL Presentation for SQL database connectivity
Understanding_Digital_Forensics_Presentation.pptx
“AI and Expert System Decision Support & Business Intelligence Systems”
Ad

Best of Oracle PL/SQL | Wonderful Features

  • 1. Copyright 2000-2006 Steven Feuerstein - Page 1 Some of the most wonderful features of this most wonderful programming language Steven Feuerstein PL/SQL Evangelist, Quest Software steven.feuerstein@quest.com www.quest.com www.oracleplsqlprogramming.com "Best of" Oracle PL/SQL
  • 2. Copyright 2000-2006 Steven Feuerstein - Page 2 Two days of fun with PL/SQL! (1)  Understanding PL/SQL architecture – Memory mgt, autonomous transactions, invoker rights  The brilliant Oracle10g PL/SQL compiler – Optimizing compiler, compile-time warnings, conditional compilation  Collections – Array-like structures that are fantastically useful.  Optimizing SQL in PL/SQL – BULK COLLECT and FORALL: turbocharged SQL – Table functions – Dynamic SQL in PL/SQL – Best practices for SQL in PL/SQL: Stop writing so much SQL!
  • 3. Copyright 2000-2006 Steven Feuerstein - Page 3 Two days of fun with PL/SQL! (2)  Managing errors in PL/SQL – A review of available functionality for raising, handling and logging errors in PL/SQL, plus best practices  Unit testing PL/SQL programs – I will recommend a methodology and show you a new tool for testing PL/SQL programs  Write readable, maintainable code – Packages, local modules, and more
  • 4. Copyright 2000-2006 Steven Feuerstein - Page 4 Eleven Years Writing Ten Books on the Oracle PL/SQL Language
  • 5. Copyright 2000-2006 Steven Feuerstein - Page 5 How to benefit most from this class  Watch, listen, ask questions.  Download the training materials and supporting scripts: – http://oracleplsqlprogramming.com/resources.html – "Demo zip": all the scripts I run in my class available at http://oracleplsqlprogramming.com/downloads/demo.zip  Use these materials as an accelerator as you venture into new territory and need to apply new techniques.  Play games! Keep your brain fresh and active by mixing hard work with challenging games – MasterMind and Set (www.setgame.com) filename_from_demo_zip.sql
  • 6. Copyright 2000-2006 Steven Feuerstein - Page 6 High Level, High Impact  Drink lots of water.  Write tiny chunks of code.  Stop writing so much SQL.  Stop guessing and start testing. Top four tips....  Assume everything will change.  Aim for a single point of definition (a SPOD!). Important principles...
  • 7. Copyright 2000-2006 Steven Feuerstein - Page 7 Drink lots of water!  And lots less coffee.  OK, don't go cold turkey on caffeine.  But drink lots (and lots more) water. – Coffee dehydrates you and a dehydrated brain just doesn’t work as effectively.  Generally, we need to take care of our host body, so that our brain can keep on earning that big fat paycheck! – Get away from your computer, take breaks. – Exercise and stretch.
  • 8. Copyright 2000-2006 Steven Feuerstein - Page 8 Some PL/SQL Fundamentals  The STANDARD package and how to be a PL/SQL sleuth  The PL/SQL run-time memory architecture  Invoker rights – Running code under the current user's authority  Autonomous transactions – Control the scope of commits and rollbacks
  • 9. Copyright 2000-2006 Steven Feuerstein - Page 9 The STANDARD Package and the Rdbms/Admin Directory  Much of what we consider to be the base PL/SQL language is defined in the STANDARD package. – One of two "default" packages; the other is DBMS_STANDARD.  You can view the contents of the STANDARD package (and other "supplied" packages by visiting the appropriate variation of: $ORACLE_HOME/Rdbms/Admin
  • 10. Copyright 2000-2006 Steven Feuerstein - Page 10 Code and Data in Shared Memory  PL/SQL is an interpretative language. The source code is “partially compiled” into an intermediate form (“p-code”). – The p-code is loaded into the shared pool when any element of that code (package or stand-alone program) is referenced.  The partially-compiled code is shared among all users who have EXECUTE authority on the program/package.  Each user (Oracle session) has its own copy of any data structures defined within the program/package. – Distinct sets of in-memory data (not shared among different users) are stored in the PGA.
  • 11. Copyright 2000-2006 Steven Feuerstein - Page 11 System Global Area (SGA) of RDBMS Instance PL/SQL in Shared Memory Shared Pool Large Pool Reserved Pool show_emps calc_totals upd_salaries Select * from emp Shared SQL Pre-parsed Update emp Set sal=... Library cache Session 1 memory (PGA/UGA) emp_rec emp%rowtype; tot_tab tottabtype; Session 2 memory (PGA/UGA) emp_rec emp%rowtype; tot_tab tottabtype; Session 1 Session 2 mysess.pkg Sess2.sql
  • 12. Copyright 2000-2006 Steven Feuerstein - Page 12 Execution Mode Options for PL/SQL: Invoker and Definer Rights Execution Mode Options
  • 13. Copyright 2000-2006 Steven Feuerstein - Page 13 Two options for resolving data references  Definer Rights –Whenever you executed a stored program, it runs under the privileges of the schema in which the program was compiled or defined.  Invoker Rights –Oracle resolves all data references (table, view, etc.) at run-time, based on the currently-connect user and its privileges (directly granted or available through roles).
  • 14. Copyright 2000-2006 Steven Feuerstein - Page 14 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. Orders OE Data OE Code Order_Mgt Cancel Sam_Sales Place Close Old Orders X Cannot alter table directly. Note: Oracle built-in packages have long had the capability of running under the invoker's authority.
  • 15. Copyright 2000-2006 Steven Feuerstein - Page 15 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. – Difficult to audit privileges  Sure would be nice to have a choice...and now you do!
  • 16. Copyright 2000-2006 Steven Feuerstein - Page 16  For top level modules:  For modules with separate spec and body, AUTHID goes only in spec, and must be at the package level. – Holds true for packages and object types. Invoker Rights Syntax CREATE [ OR REPLACE ] <module type> [ AUTHID { DEFINER | CURRENT_USER } ] AS ...
  • 17. Copyright 2000-2006 Steven Feuerstein - Page 17 "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. User/Data schema accounts table PROCEDURE mng_account IS BEGIN ... code.acct_mgr.destroy(...); END; Central Code schema PACKAGE acct_mgr ...FROM accounts WHERE... destroy modify make AUTHID CURRENT_USER
  • 18. Copyright 2000-2006 Steven Feuerstein - Page 18 When Invoker Rights Applies  Resolution against invoker's privileges is made for these statements: – SELECT, INSERT, UPDATE, and DELETE data manipulation statements – The LOCK TABLE transaction control statement – OPEN and OPEN-FOR cursor control statements – EXECUTE IMMEDIATE and OPEN-FOR-USING dynamic SQL statements – SQL statements parsed using DBMS_SQL.PARSE()  For all other statements, resolution is by the owner's privileges. – This includes ALL code references.
  • 19. Copyright 2000-2006 Steven Feuerstein - Page 19 Roles and Privileges  With definer rights, roles are disabled and ignored. – All references are resolved against directly granted privileges.  With invoker rights, roles are enabled and used for privilege checking. – You can even use dynamic SQL to set roles for the session, altering how the reference is resolved at run-time. – Exception: if the CURRENT_USER programs was called directly or indirectly by a definer-rights subprogram. invrole.sql
  • 20. Copyright 2000-2006 Steven Feuerstein - Page 20  When writing code with the intention of relying on invoker rights, the data object referenced may not be present in the code's schema. – You need some kind of "template" against which to successfully compile the code.  Two options; either create a... – Synonym to any of the possible resolved objects. – Local, "dummy" object to allow the code to compile, knowing that it will never be used at run-time. Compiling with "Template" Objects
  • 21. Copyright 2000-2006 Steven Feuerstein - Page 21 Invoker-Definer Precedence  If the first program in the execution stack is defined with invoker rights, then it executes under the session user's authority.  When and if a definer rights program is called in the stack, the "current user" is set to the owner of the definer rights program.  All subsequent calls in the stack are resolved according to the privileges of that schema. invdefinv.sql invdefinv.tst irdynsql.sql invoker_rights_mode.sf
  • 22. Copyright 2000-2006 Steven Feuerstein - Page 22 Invoker Rights and Distributed Databases  Invoker rights affect only one kind of database link-- current-user links, which are created as follows:  A current-user link lets you connect to a remote database as another user, with that user's privileges.  To connect, Oracle uses the username of the current user (who must be a global user). – Suppose an invoker-rights subprogram owned by user blake references the database link below. If global user scott calls the subprogram, it connects to the Dallas database as user scott, who is the current user. CREATE DATABASE LINK link_name CONNECT TO CURRENT_USER USING connect_string;
  • 23. Copyright 2000-2006 Steven Feuerstein - Page 23 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.  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;
  • 24. Copyright 2000-2006 Steven Feuerstein - Page 24 When to Use Autonomous Transactions  Reusable Application Components – ATs are more or less required in the new distributed application architecture of the Internet.  Logging Mechanism – Solves problems of error logs in database tables, with log entries a part of your transaction.  Call functions within SQL that change the database.  Issue commits and rollbacks inside DB triggers.
  • 25. Copyright 2000-2006 Steven Feuerstein - Page 25 Autonomous vs. Nested Transactions  An AT is started by another transaction, but is not a nested transaction for the following reasons: – It does not share transactional resources (such as locks) with the main transaction. – It does not depend on the main transaction. For example, if the main transaction rolls back, nested transactions roll back, but autonomous transactions do not. – Its committed changes are visible to other transactions immediately. (A nested transaction's committed changes are not visible to other transactions until the main transaction commits.)
  • 26. Copyright 2000-2006 Steven Feuerstein - Page 26 Logging with ATs logger.sp log81.pkg log81*.tst 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; retry.pkg retry.tst Save on successful exit Avoid inter- dependencies with the main transaction. Don't forget to rollback on error!
  • 27. Copyright 2000-2006 Steven Feuerstein - Page 27 Tips and Gotchas with ATs  An AT program that executes DML must COMMIT or ROLLBACK before terminating, or an error is raised. – If you only query, COMMIT/ROLLBACK is not required.  The AT PRAGMA can be used only with individual programs and top-level anonymous blocks. – You cannot define an entire package as an AT. – You cannot define a nested anonymous block as 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.
  • 28. Copyright 2000-2006 Steven Feuerstein - Page 28 Tips and Gotchas, cont.  The Oracle initialization parameter TRANSACTIONS specifies the maximum number of concurrent transactions. – Which might be exceeded if autonomous transactions (running concurrently with main transaction) are not taken into account.  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
  • 29. Copyright 2000-2006 Steven Feuerstein - Page 29 Autonomous Transactions - Summary  Easy to define  Lots of immediate applications  Minimal learning curve  Low implementation risks  You should immediately explore opportunities to utilize this feature.
  • 30. Copyright 2000-2006 Steven Feuerstein - Page 30 Major Oracle10g PL/SQL compiler upgrades  Optimizing compiler – Recompile in 10g and experience 100% improvement in performance (results may vary).  Compile-time warnings – Now the PL/SQL compiler tells you more than simply compilation errors.  Conditional compilation (10.2 only) – "ifdef" like pre-processing for PL/SQL
  • 31. Copyright 2000-2006 Steven Feuerstein - Page 31 Wow! An optimizing compiler!  Yes, the PL/SQL compiler now has the ability to automatically optimize your code. – Possible rearrangements to the code itself (under the covers).  You can choose the level of optimization through the plsql_optimize_level setting: – 2 Most aggressive, maximum possible code transformations, biggest impact on compile time. [default] – 1 Smaller scale change, less impact on compile times – 0 Pre-10g compilation without optimization ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 1; 10g_optimize_cfl.sql Oracle10g
  • 32. Copyright 2000-2006 Steven Feuerstein - Page 32 Learn about the PL/SQL optimizer  PL/SQL Just Got Faster – Explains the workings of the PL/SQL compiler and runtime system and shows how major improvements on this scale are indeed possible.  PL/SQL Performance Measurement Harness – Describes a performance experiment whose conclusion is the large factors quoted above. We’ve provided a downloadable kit to enable you to repeat the experiment yourself.  Freedom, Order, and PL/SQL Optimization – Intended for professional PL/SQL programmers, explores the use and behavior of the new compiler.  PL/SQL Performance — Debunking the Myths – Re-examines some old notions about PL/SQL performance. http://www.oracle.com/technology/tech/pl_sql/htdocs/new_in_10gr1.htm
  • 33. Copyright 2000-2006 Steven Feuerstein - Page 33 Optimizing compiler details  Oracle retains optimizer settings on a module-by-module basis. – When you recompile a particular module with non-default settings, the settings will "stick," allowing you to recompile later using REUSE SETTINGS. For example:  and then: ALTER PROCEDURE bigproc COMPILE PLSQL_OPTIMIZE_LEVEL = 1; ALTER PROCEDURE bigproc COMPILE REUSE SETTINGS;
  • 34. Copyright 2000-2006 Steven Feuerstein - Page 34 Wow! Compile-time warnings!  You can now enable compiler warnings, helping you avoid nuisance issues in your code. – Generally, these are not severe errors, but potential problems with code structure or performance.  To use compiler warnings, you must turn them on in your session. [ENABLE | DISABLE | ERROR]:[ALL|SEVERE|INFORMATIONAL|PERFORMANCE|warning_number] REM To enable all warnings in your session execute: ALTER SESSION SET plsql_warnings = 'enable:all‘; REM If you want to enable warning message number 06002 and all warnings in REM the performance category, and treat warning 5005 as a "hard" compile error: ALTER SESSION SET plsql_warnings = 'enable:06002', 'enable:performance', 'ERROR:05005'; Oracle10g
  • 35. Copyright 2000-2006 Steven Feuerstein - Page 35 Compiler time warnings - example  Check for “unreachable end” code…. SQL> CREATE OR REPLACE PROCEDURE unreachable_code IS 2 x NUMBER := 10; 3 BEGIN 4 IF x = 10 THEN 5 x := 20; 6 ELSE 7 x := 100; -- unreachable code 8 END IF; 9 END unreachable_code; 10 / SP2-0804: Procedure created with compilation warnings SQL> show err Errors for PROCEDURE UNREACHABLE_CODE: LINE/COL ERROR -------- ------------------------------------- 7/7 PLW-06002: Unreachable code plw*.sql
  • 36. Copyright 2000-2006 Steven Feuerstein - Page 36 The ALL_PLSQL_OBJECT_SETTINGS view  New to Oracle10g, this view provides information about the characteristics of a PL/SQL object that can be modified through the ALTER-SET DDL command, such as the optimization level, debug settings and more. – Show all the program units that are not fully leveraging code optimization: – Show all objects which have had one or more compile-time warnings disabled: SELECT owner, name FROM all_plsql_object_settings WHERE plsql_optimize_level IN (1,0); SELECT owner, NAME, plsql_warnings FROM all_plsql_object_settings WHERE plsql_warnings LIKE '%DISABLE%' AND owner NOT IN ('SYS', 'SYSTEM');
  • 37. Copyright 2000-2006 Steven Feuerstein - Page 37 Conditional Compilation in 10g Release 2  Compile selected parts of a program based on conditions you provide with various compiler directives.  Conditional compilation will allow you to: – Write code that will compile and run under different versions of Oracle (relevant for future releases). – Run different code for test, debug and production phases. That is, compile debug statements in and out of your code. – Expose private modules for unit testing.  Available in 10gR2 and patch sets of 10gR1, plus 9iR2 (with guidance from Oracle Support)
  • 38. Copyright 2000-2006 Steven Feuerstein - Page 38 Three types of compiler directives  Selection directives: $IF – Use the $IF directive to evaluate expressions and determine which code should be included or avoided.  Inquiry directives: $$identifier – Use the $$identifier syntax to refer to conditional compilation flags. These inquiry directives can be referenced within an $IF directive, or used independently in your code.  Error directives: $ERROR – Use the $ERROR directive to report compilation errors based on conditions evaluated when the preprocessor prepares your code for compilation.
  • 39. Copyright 2000-2006 Steven Feuerstein - Page 39 Access to post-processed code  You can display or retrieve post-processed code with the DBMS_PREPROCESSOR package. CREATE OR REPLACE PROCEDURE post_processed IS BEGIN $IF $$PLSQL_OPTIMIZE_LEVEL = 1 $THEN -- Slow and easy NULL; $ELSE -- Fast and modern and easy NULL; $END END post_processed; / cc_postprocessor.sql BEGIN DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE ('PROCEDURE', USER, 'POST_PROCESSED'); END; / PROCEDURE post_processed IS BEGIN -- Fast and modern and easy NULL; END post_processed;
  • 40. Copyright 2000-2006 Steven Feuerstein - Page 40 What's available for use in directives?  CC flags that are set through the ALTER SESSION command – Integers and Booleans supported  Static expressions defined in package specifications – Values that do not change when a package is recompiled  Compilation environment settings – In essence, the data available in the ALL/USER_PLSQL_OBJECT_SETTINGS data dictionary  DBMS_DB_VERSION constants – A new package that tells you how the current version compares to other versions of Oracle.
  • 41. Copyright 2000-2006 Steven Feuerstein - Page 41 Compilation environment values  $$PLSQL_DEBUG – Debug setting for this compilation unit  $$PLSQL_OPTIMIZE_LEVEL – Optimization level for this compilation unit  $$PLSQL_CODE_TYPE – Compilation mode for the unit  $$PLSQL_WARNINGS – Compilation warnings setting for this compilation unit  $$NLS_LENGTH_SEMANTICS – Value set for the NLS length semantics  Plus $$PLSQL_UNIT and $$PLSQL_LINE – Name and line number in current program unit
  • 42. Copyright 2000-2006 Steven Feuerstein - Page 42 Selection directive example  The $IF directive can reference constants defined in your own packages or environmental variables. CREATE OR REPLACE PACKAGE BODY sense_of_humor IS $IF $$test_humor_package $THEN PROCEDURE int_test_package IS BEGIN DBMS_OUTPUT.PUT_LINE ( 'Testing of sense_of_humor is enabled.'); END int_test_package; $END PROCEDURE test_package IS BEGIN $IF $$test_humor_package $THEN int_test_package; $ELSE RAISE PROGRAM_ERROR; $END END test_package; END; cc_expose_private.sql
  • 43. Copyright 2000-2006 Steven Feuerstein - Page 43 Inquiry directive example  Set up conditional compilation of debugging and tracing with special "CC" flags that are placed into the compiler settings for a program. ALTER SESSION SET PLSQL_CCFLAGS = 'oe_debug:true, oe_trace_level:10'; CREATE OR REPLACE PROCEDURE calculate_totals IS BEGIN $IF $$oe_debug AND $$oe_trace_level >= 5 $THEN DBMS_OUTPUT.PUT_LINE ('Tracing at level 5 or higher'); $END application_logic; END calculate_totals; / cc_debug_trace.sql cc_expose_private.sql cc_max_string.sql cc_plsql_parameters.sql
  • 44. Copyright 2000-2006 Steven Feuerstein - Page 44 Error directive example  If my program has not been compiled with optimization level 1 (less aggressive level), then raise an error. – You can in this way add "meta-requirements" to your code definitions. SQL> CREATE OR REPLACE PROCEDURE long_compilation 2 IS 3 BEGIN 4 $IF $$plsql_optimize_level <> 1 5 $THEN 6 $error 'Program must be compiled with optimization level = 1' $end 7 $END 8 NULL; 9 END long_compilation; 10 / cc_opt_level_check.sql
  • 45. Copyright 2000-2006 Steven Feuerstein - Page 45 DBMS_DB_VERSION  Each version of Oracle from Oracle Database 10g Release 2 will contain a DBMS_DB_VERSION package with Boolean constants showing absolute and relative version information. PROCEDURE insert_rows ( rows_in IN otn_demo_aat ) IS BEGIN $IF DBMS_DB_VERSION.VER_LE_9_2 $THEN BEGIN ... FORALL indx IN 1 .. l_dense.COUNT INSERT INTO otn_demo VALUES l_dense (indx); END; $ELSE FORALL indx IN INDICES OF rows_in INSERT INTO otn_demo VALUES rows_in (indx); $END cc-bf_or_number.sql cc_version_check.sql
  • 46. Copyright 2000-2006 Steven Feuerstein - Page 46 Conditional compilation summary  Major step forward for the PL/SQL language. – Much more flexible debugging and tracking. – Smoothes the way to write programs that support multiple versions.  Its impact will be felt most in the future, when the DBMS_DB_VERSION package can be used to automatically compile code appropriate to different versions of Oracle.
  • 47. Copyright 2000-2006 Steven Feuerstein - Page 47 PL/SQL Collections  Collections are single-dimensioned lists of information, similar to 3GL arrays.  They are an invaluable data structure. – All PL/SQL developers should be very comfortable with collections and use them often.  Collections take some getting used to. – They are not the most straightforward implementation of array-like structures. – Advanced features like string indexes and multi- level collections can be a challenge.
  • 48. Copyright 2000-2006 Steven Feuerstein - Page 48 What we will cover on collections  Review of basic functionality  Indexing collections by strings  Working with collections of collections  MULTISET operators for nested tables  Then later in the section on SQL: – Bulk processing with FORALL and BULK COLLECT – Table functions and pipelined functions
  • 49. Copyright 2000-2006 Steven Feuerstein - Page 49 What is a collection?  A collection is an "ordered group of elements, all of the same type." (PL/SQL User Guide and Reference) – That's a very general definition; lists, sets, arrays and similar data structures are all types of collections. – Each element of a collection may be addressed by a unique subscript, usually an integer but in some cases also a string. – Collections are single-dimensional, but you can create collections of collections to emulate multi-dimensional structures. abc def sf q rrr swq ... 1 2 3 4 22 23
  • 50. Copyright 2000-2006 Steven Feuerstein - Page 50 Why use collections?  Generally, to manipulate in-program-memory lists of information. – Much faster than working through SQL.  Serve up complex datasets of information to non-PL/SQL host environments using table functions.  Dramatically improve multi-row querying, inserting, updating and deleting the contents of tables. Combined with BULK COLLECT and FORALL....  Emulate bi-directional cursors, which are not yet supported within PL/SQL.
  • 51. Copyright 2000-2006 Steven Feuerstein - Page 51 Three Types of Collections  Associative arrays (aka index-by tables) – Can be used only in PL/SQL blocks. – Similar to hash tables in other languages, allows you to access elements via arbitrary subscript values.  Nested tables and Varrays – Can be used in PL/SQL blocks, but also can be the datatype of a column in a relational table. – Part of the object model in PL/SQL. – Required for some features, such as table functions – With Varrays, you specify a maximum number of elements in the collection, at time of definition.
  • 52. Copyright 2000-2006 Steven Feuerstein - Page 52 About Associative Arrays  Unbounded, practically speaking. – Valid row numbers range from -2,147,483,647 to 2,147,483,647. – This range allows you to employ the row number as an intelligent key, such as the primary key or unique index value, because AAs also are:  Sparse – Data does not have to be stored in consecutive rows, as is required in traditional 3GL arrays and VARRAYs.  Index values can be integers or strings (Oracle9i R2 and above). assoc_array_example.sql
  • 53. Copyright 2000-2006 Steven Feuerstein - Page 53 About Nested Tables  No pre-defined limit on a nested table. – Valid row numbers range from 1 to 2,147,483,647.  Part of object model, requiring initialization.  Is always dense initially, but can become sparse after deletes.  Can be defined as a schema level type and used as a relational table column type. nested_table_example.sql
  • 54. Copyright 2000-2006 Steven Feuerstein - Page 54 About Varrays  Has a maximum size, associated with its type. – Can adjust the size at runtime in Oracle10g R2.  Part of object model, requiring initialization.  Is always dense; you can only remove elements from the end of a varray.  Can be defined as a schema level type and used as a relational table column type. varray_example.sql
  • 55. Copyright 2000-2006 Steven Feuerstein - Page 55 Wide Variety of Collection Methods  Obtain information about the collection – COUNT returns number of rows currently defined in collection. – 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.
  • 56. Copyright 2000-2006 Steven Feuerstein - Page 56 Useful reminders for PL/SQL collections  Memory for collections comes out of the PGA or Process Global Area – One per session, so a program using collections can consume a large amount of memory.  Use the NOCOPY hint to reduce overhead of passing collections in and out of program units.  Encapsulate or hide details of collection management.  Don't always fill collections sequentially. Think about how you need to manipulate the contents.  Try to read a row that doesn't exist, and Oracle raises NO_DATA_FOUND. mysess.pkg sess2.sql nocopy*.*
  • 57. Copyright 2000-2006 Steven Feuerstein - Page 57 Apply PL/SQL Collections  We will take a look at the following application of PL/SQL collections: – Caching data in the PGA with collections  Then we will explore advanced features of collections. – String-indexed collections – Multi-level collections
  • 58. Copyright 2000-2006 Steven Feuerstein - Page 58 Function PGA Data Caching with PL/SQL Tables First access Subsequent accesses PGA Function Database Not in cache; Request data from database Pass Data to Cache Application Application Requests Data Data retrieved from cache Data returned to application Application Application Requests Data Data returned to application Data retrieved from cache Database Data found in cache. Database is not needed. emplu.pkg emplu.tst
  • 59. Copyright 2000-2006 Steven Feuerstein - Page 59 New indexing capabilities for associative arrays  Prior to Oracle9iR2, you could only index by BINARY_INTEGER.  You can now define the index on your associative array to be: – Any sub-type derived from BINARY_INTEGER – VARCHAR2(n), where n is between 1 and 32767 – %TYPE against a database column that is consistent with the above rules – A SUBTYPE against any of the above.  This means that you can now index on string values! (and concatenated indexes and...) Oracle9i Release 2
  • 60. Copyright 2000-2006 Steven Feuerstein - Page 60 Examples of New TYPE Variants  All of the following are now valid TYPE declarations in Oracle9i Release 2 – You cannot use %TYPE against an INTEGER column, because INTEGER is not a subtype of BINARY_INTEGER. DECLARE TYPE array_t1 IS TABLE OF NUMBER INDEX BY BINARY_INTEGER; TYPE array_t2 IS TABLE OF NUMBER INDEX BY PLS_INTEGER; TYPE array_t3 IS TABLE OF NUMBER INDEX BY POSITIVE; TYPE array_t4 IS TABLE OF NUMBER INDEX BY NATURAL; TYPE array_t5 IS TABLE OF NUMBER INDEX BY VARCHAR2(64); TYPE array_t6 IS TABLE OF NUMBER INDEX BY VARCHAR2(32767); TYPE array_t7 IS TABLE OF NUMBER INDEX BY employee.last_name%TYPE; TYPE array_t8 IS TABLE OF NUMBER INDEX BY types_pkg.subtype_t; Oracle9i Release 2
  • 61. Copyright 2000-2006 Steven Feuerstein - Page 61 Working with string-indexed collections  Specifying a row via a string takes some getting used to, but if offers some very powerful advantages. DECLARE TYPE population_type IS TABLE OF NUMBER INDEX BY VARCHAR2(64); country_population population_type; continent_population population_type; howmany NUMBER; BEGIN country_population ('Greenland') := 100000; country_population ('Iceland') := 750000; howmany := country_population ('Greenland'); continent_population ('Australia') := 30000000; END; assoc_array*.sql assoc_array_perf.tst
  • 62. Copyright 2000-2006 Steven Feuerstein - Page 62 Rapid access to data via strings  One of the most powerful applications of this features is to construct very fast pathways to static data from within PL/SQL programs. – If you are repeatedly querying the same data from the database, why not cache it in your PGA inside collections?  Emulate the various indexing mechanisms (primary key, unique indexes) with collections. Demonstration package: assoc_array5.sql Comparison of performance of different approaches: vocab*.* Generate a caching package: genaa.sql genaa.tst
  • 63. Copyright 2000-2006 Steven Feuerstein - Page 63 The String Tracker package (V1)  Another example: I need to keep track of the names of variables that I have already used in my test code generation. – Can't declare the same variable twice. CREATE OR REPLACE PACKAGE BODY string_tracker IS TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t; g_names_used used_aat; FUNCTION string_in_use ( value_in IN maxvarchar2_t ) RETURN BOOLEAN IS BEGIN RETURN g_names_used.EXISTS ( value_in ); END string_in_use; PROCEDURE mark_as_used (value_in IN maxvarchar2_t) IS BEGIN g_names_used ( value_in ) := TRUE; END mark_as_used; END string_tracker; string_tracker1.*
  • 64. Copyright 2000-2006 Steven Feuerstein - Page 64 Multi-level Collections  Prior to Oracle9i, you could have collections of records or objects, but only if all fields were scalars. – A collection containing another collection was not allowed.  Now you can create collections that contain other collections and complex types. – Applies to all three types of collections.  The syntax is non-intuitive and resulting code can be quite complex. Oracle9i
  • 65. Copyright 2000-2006 Steven Feuerstein - Page 65 String Tracker Version 2  The problem with String Tracker V1 is that it only supports a single list of strings. – What if I need to track multiple lists simultaneously or nested?  Let's extend the first version to support multiple lists by using a string-indexed, multi- level collection. – A list of lists....
  • 66. Copyright 2000-2006 Steven Feuerstein - Page 66 The String Tracker package (V2) CREATE OR REPLACE PACKAGE BODY string_tracker IS TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t; TYPE list_of_lists_aat IS TABLE OF used_aat INDEX BY maxvarchar2_t; g_list_of_lists list_of_lists_aat; PROCEDURE mark_as_used ( list_in IN maxvarchar2_t , value_in IN maxvarchar2_t , case_sensitive_in IN BOOLEAN DEFAULT FALSE ) IS l_name maxvarchar2_t := CASE case_sensitive_in WHEN TRUE THEN value_in ELSE UPPER ( value_in ) END; BEGIN g_list_of_lists ( list_in ) ( l_name) := TRUE; END mark_as_used; END string_tracker; string_tracker2.*
  • 67. Copyright 2000-2006 Steven Feuerstein - Page 67 Other multi-level collection examples  Multi-level collections with intermediate records and objects.  Emulation of multi-dimensional arrays – No native support, but can creates nested collections to get much the same effect. – Use the UTL_NLA package (10gR2) for complex matrix manipulation.  Four-level nested collection used to track arguments for a program unit. – Automatically analyze ambiguous overloading. multidim*.* ambig_overloading.sql OTN: OverloadCheck multilevel_collections.sql
  • 68. Copyright 2000-2006 Steven Feuerstein - Page 68 Encapsulate these complex structures!  When working with multi-level collections, you can easily and rapidly arrive at completely unreadable and un-maintainable code.  What' s a developer to do? – Hide complexity -- and all data structures -- behind small modules. – Work with and through functions to retrieve contents and procedures to set contents. cc_smartargs.pkb: cc_smartargs.next_overloading cc_smartargs.add_new_parameter
  • 69. Copyright 2000-2006 Steven Feuerstein - Page 69 Nested Tables unveil their MULTISET-edness  Oracle10g introduces high-level set operations on nested tables (only). – Nested tables are “multisets,” meaning that theoretically there is no order to their elements. This makes set operations of critical importance for manipulating nested tables. .  You can now… – Check for equality and inequality – Perform UNION, INTERSECT and MINUS operations – Check for and remove duplicates Oracle10g
  • 70. Copyright 2000-2006 Steven Feuerstein - Page 70 Check for equality and inequality  Just use the basic operators…. Oracle10g DECLARE TYPE clientele IS TABLE OF VARCHAR2 (64); group1 clientele := clientele ('Customer 1', 'Customer 2'); group2 clientele := clientele ('Customer 1', 'Customer 3'); group3 clientele := clientele ('Customer 3', 'Customer 1'); BEGIN IF group1 = group2 THEN DBMS_OUTPUT.put_line ('Group 1 = Group 2'); ELSE DBMS_OUTPUT.put_line ('Group 1 != Group 2'); END IF; IF group2 != group3 THEN DBMS_OUTPUT.put_line ('Group 2 != Group 3'); ELSE DBMS_OUTPUT.put_line ('Group 2 = Group 3'); END IF; END; 10g_compare.sql 10g_compare2.sql 10g_compare_old.sql
  • 71. Copyright 2000-2006 Steven Feuerstein - Page 71 UNION, INTERSECT, MINUS  Straightforward, with the MULTISET keyword. Oracle10g BEGIN our_favorites := my_favorites MULTISET UNION dad_favorites; show_favorites ('MINE then DAD', our_favorites); our_favorites := dad_favorites MULTISET UNION my_favorites; show_favorites ('DAD then MINE', our_favorites); our_favorites := my_favorites MULTISET UNION DISTINCT dad_favorites; show_favorites ('MINE then DAD with DISTINCT', our_favorites); our_favorites := my_favorites MULTISET INTERSECT dad_favorites; show_favorites ('IN COMMON', our_favorites); our_favorites := dad_favorites MULTISET EXCEPT my_favorites; show_favorites ('ONLY DAD''S', our_favorites); END; 10g_setops.sql 10g_string_nt.sql 10g_favorites.sql 10g*union*.sql
  • 72. Copyright 2000-2006 Steven Feuerstein - Page 72 Distinct sets of values  Use the SET operator to work with distinct values, and determine if you have a set of distinct values. Oracle10g DECLARE keep_it_simple strings_nt := strings_nt (); BEGIN keep_it_simple := SET (favorites_pkg.my_favorites); favorites_pkg.show_favorites ('FULL SET', favorites_pkg.my_favorites); p.l (favorites_pkg.my_favorites IS A SET, 'My favorites distinct?'); p.l (favorites_pkg.my_favorites IS NOT A SET, 'My favorites NOT distinct?'); favorites_pkg.show_favorites ( 'DISTINCT SET', keep_it_simple); p.l (keep_it_simple IS A SET, 'Keep_it_simple distinct?'); p.l (keep_it_simple IS NOT A SET, 'Keep_it_simple NOT distinct?'); END; 10g_set.sql 10g_favorites.pkg
  • 73. Copyright 2000-2006 Steven Feuerstein - Page 73 How to choose your collection type  Use associative arrays when you need to... – Work within PL/SQL code only – Sparsely fill and manipulate the collection – Take advantage of negative index values  Use nested tables when you need to... – Access the collection inside SQL (table functions, columns in tables) – Want to perform set operations  Use varrays when you need to... – If you need to specify a maximum size to your collection – Access the collection inside SQL (table functions, columns in tables).
  • 74. Copyright 2000-2006 Steven Feuerstein - Page 74 Collections: the latest frontier  Five-plus years ago, many programmers wrestled with making packages an every-day part of their PL/SQL coding.  Today I offer this challenge: learn collections thoroughly and apply them throughout your backend code. – Your code will get faster and in many cases much simpler than it might have been (though not always!).
  • 75. Copyright 2000-2006 Steven Feuerstein - Page 75 > Optimize SQL in PL/SQL programs  Take advantage of PL/SQL-specific enhancements for SQL. – BULK COLLECT and FORALL – Table functions  Dynamic SQL: easier and faster than ever.  Hide your SQL statements behind a procedural interface so that you can easily change and upgrade.
  • 76. Copyright 2000-2006 Steven Feuerstein - Page 76 Turbo-charged SQL with BULK COLLECT and FORALL  Improve the performance of multi-row SQL operations by an order of magnitude or more with bulk/array processing in PL/SQL! CREATE OR REPLACE PROCEDURE upd_for_dept ( dept_in IN employee.department_id%TYPE ,newsal_in IN employee.salary%TYPE) IS CURSOR emp_cur IS SELECT employee_id,salary,hire_date FROM employee WHERE department_id = dept_in; BEGIN FOR rec IN emp_cur LOOP UPDATE employee SET salary = newsal_in WHERE employee_id = rec.employee_id; END LOOP; END upd_for_dept; “Conventional binds” (and lots of them!)
  • 77. Copyright 2000-2006 Steven Feuerstein - Page 77 Oracle server PL/SQL Runtime Engine SQL Engine PL/SQL block Procedural statement executor SQL statement executor FOR rec IN emp_cur LOOP UPDATE employee SET salary = ... WHERE employee_id = rec.employee_id; END LOOP; Performance penalty Performance penalty for many “context for many “context switches” switches” Conventional Bind
  • 78. Copyright 2000-2006 Steven Feuerstein - Page 78 Enter the “Bulk Bind”: FORALL Oracle server PL/SQL Runtime Engine SQL Engine PL/SQL block Procedural statement executor SQL statement executor FORALL indx IN list_of_emps.FIRST.. list_of_emps.LAST UPDATE employee SET salary = ... WHERE employee_id = list_of_emps(indx); Much less overhead for Much less overhead for context switching context switching
  • 79. Copyright 2000-2006 Steven Feuerstein - Page 79 Use the FORALL Bulk Bind Statement  Instead of executing repetitive, individual DML statements, you can write your code like this:  Things to be aware of: – You MUST know how to use collections to use this feature! – Only a single DML statement is allowed per FORALL. – New cursor attributes: SQL%BULK_ROWCOUNT returns number of rows affected by each row in array. SQL%BULK_EXCEPTIONS... – Prior to Oracle10g, the binding array must be sequentially filled. – Use SAVE EXCEPTIONS to continue past errors. PROCEDURE upd_for_dept (...) IS BEGIN FORALL indx IN list_of_emps.FIRST .. list_of_emps.LAST UPDATE employee SET salary = newsal_in WHERE employee_id = list_of_emps (indx); END; bulktiming.sql bulk_rowcount.sql
  • 80. Copyright 2000-2006 Steven Feuerstein - Page 80 Better Exception Handling for Bulk Operations  Allows you to continue past errors and obtain error information for each individual operation (for dynamic and static SQL). CREATE OR REPLACE PROCEDURE load_books (books_in IN book_obj_list_t) IS bulk_errors EXCEPTION; PRAGMA EXCEPTION_INIT ( bulk_errors, -24381 ); BEGIN FORALL indx IN books_in.FIRST..books_in.LAST SAVE EXCEPTIONS INSERT INTO book values (books_in(indx)); EXCEPTION WHEN BULK_ERRORS THEN FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT LOOP log_error (SQL%BULK_EXCEPTIONS(indx)); END LOOP; END; Allows processing of all rows, even after an error occurs. New cursor attribute, a pseudo- collection bulkexc.sql Oracle9i
  • 81. Copyright 2000-2006 Steven Feuerstein - Page 81 Use BULK COLLECT INTO for Queries DECLARE TYPE employees_aat IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER; l_employees employees_aat; BEGIN SELECT * BULK COLLECT INTO l_employees FROM employees; FOR indx IN 1 .. l_employees.COUNT LOOP process_employee (l_employees(indx)); END LOOP; END; bulkcoll.sql Declare a collection of records to hold the queried data. Use BULK COLLECT to retrieve all rows. Iterate through the collection contents with a loop. WARNING! BULK COLLECT will not raise NO_DATA_FOUND if no rows are found. Always check contents of collection to confirm that something was retrieved.
  • 82. Copyright 2000-2006 Steven Feuerstein - Page 82 Limit the number of rows returned by BULK COLLECT CREATE OR REPLACE PROCEDURE bulk_with_limit (deptno_in IN dept.deptno%TYPE) IS CURSOR emps_in_dept_cur IS SELECT * FROM emp WHERE deptno = deptno_in; TYPE emp_tt IS TABLE OF emps_in_dept_cur%ROWTYPE; emps emp_tt; BEGIN OPEN emps_in_dept_cur; LOOP FETCH emps_in_dept_cur BULK COLLECT INTO emps LIMIT 100; EXIT WHEN emps.COUNT = 0; process_emps (emps); END LOOP; END bulk_with_limit; Use the LIMIT clause with the INTO to manage the amount of memory used with the BULK COLLECT operation. Definitely the preferred approach in production applications with large or varying datasets. bulklimit.sql
  • 83. Copyright 2000-2006 Steven Feuerstein - Page 83 Tips and Fine Points  Use bulk binds in these circumstances: – Recurring SQL statement in PL/SQL loop. Oracle recommended threshold: five rows!  Bulk bind rules: – Can be used with any kind of collection; Collection subscripts cannot be expressions; The collections must be densely filled (pre-10gR2).  Bulk collects: – Can be used with implicit and explicit cursors – Collection is always filled sequentially, starting at row 1. emplu.pkg cfl_to_bulk*.*
  • 84. Copyright 2000-2006 Steven Feuerstein - Page 84 Collections impact on "Rollback segment too small" and "Snapshot too old" errors  Rollback segment too small... – Cause: so many uncommitted changes, the rollback segment can't handle it all. – FORALL will cause the error to occur even sooner. Use a variation on incremental commits with FORALL.  Snapshot too old... – Cause: a cursor is held open too long and Oracle can no longer maintain the snapshot information. – Solution: open-close cursor, or use BULK COLLECT to retrieve information more rapidly. forall_incr_commit.sql
  • 85. Copyright 2000-2006 Steven Feuerstein - Page 85 Cursor FOR Loop ... or BULK COLLECT?  Why would you ever use a cursor FOR loop (or other LOOP) now that you can perform a BULK COLLECT? – If you want to do complex processing on each row as it is queried – and possibly halt further fetching. – You are not executing DML within your cursor FOR loop and you are on Oracle Oracle Database 10g – Oracle will automatically optimize the code for you.  Otherwise, moving to BULK COLLECT is a smart move! cfl_vs_bulkcollect.sql cfl_to_bulk.sql
  • 86. Copyright 2000-2006 Steven Feuerstein - Page 86 More flexibility with FORALL  In Oracle10g, the FORALL driving array no longer needs to be processed sequentially.  Use the INDICES OF clause to use only the row numbers defined in another array.  Use the VALUES OF clause to use only the values defined in another array. Oracle10g
  • 87. Copyright 2000-2006 Steven Feuerstein - Page 87 Using INDICES OF  It only processes the rows with row numbers matching the defined rows of the driving array. Oracle10g 10g_indices_of.sql 10g_indices_of2.sql DECLARE TYPE employee_aat IS TABLE OF employee.employee_id%TYPE INDEX BY PLS_INTEGER; l_employees employee_aat; TYPE boolean_aat IS TABLE OF BOOLEAN INDEX BY PLS_INTEGER; l_employee_indices boolean_aat; BEGIN l_employees (1) := 7839; l_employees (100) := 7654; l_employees (500) := 7950; -- l_employee_indices (1) := TRUE; l_employee_indices (500) := TRUE; l_employee_indices (799) := TRUE -- FORALL l_index IN INDICES OF l_employee_indices BETWEEN 1 AND 500 UPDATE employee SET salary = 10000 WHERE employee_id = l_employees (l_index); END;
  • 88. Copyright 2000-2006 Steven Feuerstein - Page 88 Using VALUES OF  It only processes the rows with row numbers matching the content of a row in the driving array. Oracle10g DECLARE TYPE employee_aat IS TABLE OF employee.employee_id%TYPE INDEX BY PLS_INTEGER; l_employees employee_aat; TYPE values_aat IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER; l_employee_values values_aat; BEGIN l_employees (-77) := 7820; l_employees (13067) := 7799; l_employees (99999999) := 7369; -- l_employee_values (100) := -77; l_employee_values (200) := 99999999; -- FORALL l_index IN VALUES OF l_employee_values UPDATE employee SET salary = 10000 WHERE employee_id = l_employees (l_index); END; 10g_values_of.sql
  • 89. Copyright 2000-2006 Steven Feuerstein - Page 89 The Wonder Of Table Functions  A table function is a function that you can call in the FROM clause of a query, and have it be treated as if it were a relational table.  Table functions allow you to perform arbitrarily complex transformations of data and then make that data available through a query. – Not everything can be done in SQL.  Combined with REF CURSORs, you can now more easily transfer data from within PL/SQL to host environments. – Java, for example, works very smoothly with cursor variables
  • 90. Copyright 2000-2006 Steven Feuerstein - Page 90 Building a table function  A table function must return a nested table or varray based on a schema-defined type (not in a PL/SQL package).  The function header and the way it is called must be SQL-compatible: all parameters use SQL types; no named notation. – In some cases (streaming and pipelined functions), the IN parameter must be a cursor variable -- a query result set.
  • 91. Copyright 2000-2006 Steven Feuerstein - Page 91 Simple table function example  Return a list of names as a nested table, and then call that function in the FROM clause. CREATE OR REPLACE FUNCTION lotsa_names ( base_name_in IN VARCHAR2, count_in IN INTEGER ) RETURN names_nt IS retval names_nt := names_nt (); BEGIN retval.EXTEND (count_in); FOR indx IN 1 .. count_in LOOP retval (indx) := base_name_in || ' ' || indx; END LOOP; RETURN retval; END lotsa_names; tabfunc_scalar.sql SELECT column_value FROM TABLE ( lotsa_names ('Steven' , 100)) names; COLUMN_VALUE ------------ Steven 1 ... Steven 100
  • 92. Copyright 2000-2006 Steven Feuerstein - Page 92 Streaming data with table functions  You can use table functions to "stream" data through several stages within a single SQL statement. – Example: transform one row in the stocktable to two rows in the tickertable. CREATE TABLE stocktable ( ticker VARCHAR2(20), trade_date DATE, open_price NUMBER, close_price NUMBER ) / CREATE TABLE tickertable ( ticker VARCHAR2(20), pricedate DATE, pricetype VARCHAR2(1), price NUMBER) / tabfunc_streaming.sql
  • 93. Copyright 2000-2006 Steven Feuerstein - Page 93 Streaming data with table functions - 2  In this example, transform each row of the stocktable into two rows in the tickertable. CREATE OR REPLACE PACKAGE refcur_pkg IS TYPE refcur_t IS REF CURSOR RETURN stocktable%ROWTYPE; END refcur_pkg; / CREATE OR REPLACE FUNCTION stockpivot (dataset refcur_pkg.refcur_t) RETURN tickertypeset ... BEGIN INSERT INTO tickertable SELECT * FROM TABLE (stockpivot (CURSOR (SELECT * FROM stocktable))); END; / tabfunc_streaming.sql
  • 94. Copyright 2000-2006 Steven Feuerstein - Page 94 Use pipelined functions to enhance performance.  Pipelined functions allow you to return data iteratively, asynchronous to termination of the function. – As data is produced within the function, it is passed back to the calling process/query.  Pipelined functions can only be called within a SQL statement. – They make no sense within non-multi-threaded PL/SQL blocks. CREATE FUNCTION StockPivot (p refcur_pkg.refcur_t) RETURN TickerTypeSet PIPELINED
  • 95. Copyright 2000-2006 Steven Feuerstein - Page 95 Applications for pipelined functions  Execution functions in parallel. – In Oracle9i Database Release 2 and above, use the PARALLEL_ENABLE clause to allow your pipelined function to participate fully in a parallelized query. – Critical in data warehouse applications.  Improve speed of delivery of data to web pages. – Use a pipelined function to "serve up" data to the webpage and allow users to being viewing and browsing, even before the function has finished retrieving all of the data.
  • 96. Copyright 2000-2006 Steven Feuerstein - Page 96 Piping rows out from a pipelined function CREATE FUNCTION stockpivot (p refcur_pkg.refcur_t) RETURN tickertypeset PIPELINED IS out_rec tickertype := tickertype (NULL, NULL, NULL); in_rec p%ROWTYPE; BEGIN LOOP FETCH p INTO in_rec; EXIT WHEN p%NOTFOUND; out_rec.ticker := in_rec.ticker; out_rec.pricetype := 'O'; out_rec.price := in_rec.openprice; PIPE ROW (out_rec); END LOOP; CLOSE p; RETURN; END; tabfunc_setup.sql tabfunc_pipelined.sql Add PIPELINED keyword to header Pipe a row of data back to calling block or query RETURN...nothing at all!
  • 97. Copyright 2000-2006 Steven Feuerstein - Page 97 Enabling Parallel Execution  You can use pipelined functions with the Parallel Query option to avoid serialization of table function execution.  Include the PARALLEL_ENABLE hint in the program header. – Choose a partition option that specifies how the function's execution should be partitioned. – "ANY" means that the results are independent of the order in which the function receives the input rows (through the REF CURSOR). {[ORDER | CLUSTER] BY column_list} PARALLEL_ENABLE ({PARTITION p BY [ANY | (HASH | RANGE) column_list]} )
  • 98. Copyright 2000-2006 Steven Feuerstein - Page 98 Table functions - Summary  Table functions offer significant new flexibility for PL/SQL developers.  Consider using them when you... – Need to pass back complex result sets of data through the SQL layer (a query); – Want to call a user defined function inside a query and execute it as part of a parallel query.
  • 99. Copyright 2000-2006 Steven Feuerstein - Page 99 Dynamic SQL in PL/SQL Dealing with Compile-time Ignorance
  • 100. Copyright 2000-2006 Steven Feuerstein - Page 100 What is Dynamic SQL?  Dynamic SQL actually refers, in the world of PL/SQL, to two things: – SQL statements, such as a DELETE or CREATE TABLE, that are constructed and executed at run- time. – Anonymous PL/SQL blocks that are constructed, compiled and executed at run-time. 'DROP ' || l_type || ' ' || l_name 'BEGIN ' || l_proc_name || ' (' || l_parameters || '); END;'
  • 101. Copyright 2000-2006 Steven Feuerstein - Page 101 Some of the possibilities with Dynamic SQL  Build ad-hoc query and update applications. – The user decides what to do and see.  Execute DDL statements from within PL/SQL. – Not otherwise allowed in a PL/SQL block.  Soft-code your application logic, placing business rules in tables and executing them dynamically. – Usually implemented through dynamic PL/SQL
  • 102. Copyright 2000-2006 Steven Feuerstein - Page 102 Two Methods Available  DBMS_SQL – A large and complex built-in package that made dynamic SQL possible in Oracle7 and Oracle8.  Native Dynamic SQL – A new (with Oracle8i), native implementation of dynamic SQL that does almost all of what DBMS_SQL can do, but much more easily and usually more efficiently.  Which should you use?
  • 103. Copyright 2000-2006 Steven Feuerstein - Page 103 NDS or DBMS_SQL: Which is best?  Reasons to go with NDS: – Ease of use – Performance (usually faster) – Works with all SQL datatypes (including user-defined object and collection types) – Fetch into records and collections of records  Why You'd Use DBMS_SQL: – Method 4 Dynamic SQL – DESCRIBE columns of cursor – SQL statements larger than 32K – Better reuse of parsed SQL statements -- persistent cursor handles! – Available from client-side PL/SQL Bottom line: NDS should be your first choice.
  • 104. Copyright 2000-2006 Steven Feuerstein - Page 104 DDL within PL/SQL  Very easy, very dangerous with NDS. – Here's a procedure that "drops whatever". CREATE OR REPLACE PROCEDURE drop_whatever (nm IN VARCHAR2) AUTHID CURRENT_USER IS CURSOR type_cur IS SELECT object_type FROM USER_OBJECTS WHERE object_name LIKE UPPER (nm); type_rec type_cur%ROWTYPE; BEGIN OPEN type_cur; FETCH type_cur INTO type_rec; IF type_cur%FOUND THEN EXECUTE IMMEDIATE 'DROP ' || type_rec.object_type || ' ' || nm; END IF; END; dropwhatever.sp creind81.sp health$.pkg settrig.sp
  • 105. Copyright 2000-2006 Steven Feuerstein - Page 105 COUNT(*) For Any Table  Here's a handy and simple utility based on NDS: IF tabCount ('citizens', 'insured = ''NO''') > 40,000,000 THEN DBMS_OUTPUT.PUT_LINE ( 'Not the best health care system in the world....'); END IF; tabcount81.sf tabcount.sf CREATE OR REPLACE FUNCTION tabCount ( tab IN VARCHAR2, whr IN VARCHAR2 := NULL, sch IN VARCHAR2 := NULL) RETURN INTEGER IS retval INTEGER; BEGIN EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ' || NVL (sch, USER) || '.' || tab || ' WHERE ' || NVL (whr, '1=1') INTO retval; RETURN retval; END; Specify schema, table and WHERE clause...
  • 106. Copyright 2000-2006 Steven Feuerstein - Page 106 DML with NDS CREATE OR REPLACE PROCEDURE salary_raise ( raise_percent NUMBER, job VARCHAR2) IS TYPE loc_array_type IS TABLE OF offices.location@TYPE INDEX BY BINARY_INTEGER; dml_str VARCHAR2 (200); loc_array loc_array_type; BEGIN SELECT location BULK COLLECT INTO loc_array FROM offices; FOR i IN loc_array.FIRST .. loc_array.LAST LOOP dml_str := 'UPDATE emp_' || loc_array (i) || ' SET sal = sal * (1+(:XYZ/100))' || ' WHERE job = :123'; EXECUTE IMMEDIATE dml_str USING raise_percent, job; END LOOP; END; Different table for each location
  • 107. Copyright 2000-2006 Steven Feuerstein - Page 107 Multiple Row Queries and NDS  Familiar syntax, tiny learning curve: OPEN FOR – Here is a simple utility that 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 cv SYS_REFCURSOR; -- Available in Oracle9i val VARCHAR2(32767); BEGIN OPEN cv FOR 'SELECT ' || col || ' FROM ' || tab || ' WHERE ' || NVL (whr, '1 = 1'); LOOP FETCH cv INTO val; EXIT WHEN cv%NOTFOUND; DBMS_OUTPUT.PUT_LINE (val); END LOOP; CLOSE cv; END; showcol81.sp ndsutil.pkg
  • 108. Copyright 2000-2006 Steven Feuerstein - Page 108 Dynamic FORALL Example  This example shows the use of bulk binding and collecting, plus application of the RETURNING clause. CREATE TYPE NumList IS TABLE OF NUMBER; CREATE TYPE NameList IS TABLE OF VARCHAR2(15); PROCEDURE update_emps ( col_in IN VARCHAR2, empnos_in IN numList) IS enames NameList; BEGIN FORALL indx IN empnos_in.FIRST .. empnos_in.LAST EXECUTE IMMEDIATE 'UPDATE emp SET ' || col_in || ' = ' || col_in || ' * 1.1 WHERE empno = :1 RETURNING ename INTO :2' USING empnos_in (indx ) RETURNING BULK COLLECT INTO enames; ... END; Notice that empnos_in is indexed, but enames is not. Oracle9i
  • 109. Copyright 2000-2006 Steven Feuerstein - Page 109 Dynamic BULK COLLECT  Now you can even avoid the OPEN FOR and just grab your rows in a single pass! CREATE OR REPLACE PROCEDURE fetch_by_loc (loc_in IN VARCHAR2) IS TYPE numlist_t IS TABLE OF NUMBER; TYPE namelist_t IS TABLE OF employee.name%TYPE; TYPE employee_t IS TABLE OF employee%ROWTYPE; emp_cv sys_refcursor; empnos numlist_t; enames namelist_t; l_employees employee_t; BEGIN OPEN emp_cv FOR 'SELECT empno, ename FROM emp_' || loc_in; FETCH emp_cv BULK COLLECT INTO empnos, enames; CLOSE emp_cv; EXECUTE IMMEDIATE 'SELECT * FROM emp_' || loc_in BULK COLLECT INTO l_employees; END; With Oracle9iR2 you can also fetch into collections of records. Oracle9i
  • 110. Copyright 2000-2006 Steven Feuerstein - Page 110 Quiz! PROCEDURE process_lineitem ( line_in IN PLS_INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 22045 THEN process_line22045; END IF; END;  What's wrong with this code?  How would you fix it?
  • 111. Copyright 2000-2006 Steven Feuerstein - Page 111 From 22,000 lines of code to 1!  Identify the pattern and resolve it either with reusable modules or dynamic abstractions. PROCEDURE process_lineitem ( line_in IN INTEGER) IS BEGIN IF line_in = 1 THEN process_line1; END IF; IF line_in = 2 THEN process_line2; END IF; ... IF line_in = 22045 THEN process_line22045; END IF; END; PROCEDURE process_lineitem ( line_in IN INTEGER) IS BEGIN EXECUTE IMMEDIATE 'BEGIN process_line'|| line_in ||'; END;'; END; dynplsql.txt
  • 112. Copyright 2000-2006 Steven Feuerstein - Page 112 Dynamic PL/SQL  Dynamically construct, compile and run an anonymous block with EXECUTE IMMEDIATE. – Begins with BEGIN or DECLARE. – Ends with END;. The trailing semi-colon is required; otherwise it is parsed as an SQL statement.  You can only reference globally-accessible data structures (declared in a package specification).  Exceptions can (and should) be trapped in the block from which the dynamic PL/SQL was executed. dynplsql8i.sp dynplsql_nolocal.sql
  • 113. Copyright 2000-2006 Steven Feuerstein - Page 113 Dynamic PL/SQL Possibilities  There are so many possibilities....some things I have done: – Dramatically reduce code volume, improve performance. – Generic string parsing engine: parse any string into your own collection. – Generic calculator engine. – Implement support for "indirect referencing": read and change values of variables whose names are only determined at run-time.  And there are also dangers: code injection. dynvar81.pkg dyncalc.pkg code_injection.sql
  • 114. Copyright 2000-2006 Steven Feuerstein - Page 114 How to build dynamic PL/SQL code  1. Build a static version of the logic you want to execute dynamically. – Test it thoroughly.  2. Identify the portions of the static code which will need to be made dynamic.  3. Convert the block, concatenating or binding those portions which are now dynamic.
  • 115. Copyright 2000-2006 Steven Feuerstein - Page 115 1. Write and verify the static block code.  Here is a static program to parse a string of directories for the path list. PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim) IS v_loc PLS_INTEGER; v_startloc PLS_INTEGER := 1; v_item VARCHAR2 (2000); BEGIN dirs.DELETE; LOOP v_loc := INSTR (str, delim, v_startloc); IF v_loc = v_startloc THEN v_item := NULL; ELSIF v_loc = 0 THEN v_item := SUBSTR (str, v_startloc); ELSE v_item := SUBSTR (str, v_startloc, v_loc - v_startloc); END IF; dirs (dirs.COUNT + 1) := v_item; IF v_loc = 0 THEN EXIT; ELSE v_startloc := v_loc + 1; END IF; END LOOP; END set_path; filepath.pkg
  • 116. Copyright 2000-2006 Steven Feuerstein - Page 116 2. Identify the dynamic elements of the block. PROCEDURE setpath (str IN VARCHAR2, delim IN VARCHAR2 := c_delim) IS v_loc PLS_INTEGER; v_startloc PLS_INTEGER := 1; v_item VARCHAR2 (2000); BEGIN dirs.DELETE; LOOP v_loc := INSTR (str, delim, v_startloc); IF v_loc = v_startloc THEN v_item := NULL; ELSIF v_loc = 0 THEN v_item := SUBSTR (str, v_startloc); ELSE v_item := SUBSTR (str, v_startloc, v_loc - v_startloc); END IF; dirs (dirs.COUNT + 1) := v_item; IF v_loc = 0 THEN EXIT; ELSE v_startloc := v_loc + 1; END IF; END LOOP; END set_path; Dynamic code Bind variable
  • 117. Copyright 2000-2006 Steven Feuerstein - Page 117 3a. Convert from static to dynamic block.  Assign the complex string to a variable.  Makes it easier to report errors and debug. dynblock := 'DECLARE v_loc PLS_INTEGER; v_start PLS_INTEGER := 1; v_item ' || datatype || '; BEGIN ' || collname || '.DELETE; IF :str IS NOT NULL THEN LOOP v_loc := INSTR (:str, :delim, v_start); IF v_loc = v_startloc THEN v_item := NULL; ELSIF v_loc = 0 THEN v_item := SUBSTR (:str, v_start); ELSE v_item := SUBSTR (:str, v_start, v_loc - v_start); END IF;' || collname || '(' || nextrowstring || ') := v_item; IF v_loc = 0 THEN EXIT; ELSE v_start := v_loc + 1; END IF; END LOOP; END IF; END;'; str2list.pkg
  • 118. Copyright 2000-2006 Steven Feuerstein - Page 118 3b. Execute the dynamic block.  With dynamic PL/SQL, even if you reference the same bind variable more than once, you only specify it once in the USING clause. – In other words, PL/SQL is using a variation of "named notation" rather than the default positional notation for dynamic SQL statements. EXECUTE IMMEDIATE dynblock USING str, delim;
  • 119. Copyright 2000-2006 Steven Feuerstein - Page 119 Method 4 Dynamic SQL with DBMS_SQL  Method 4 dynamic SQL is the most generalized and most complex - by far! – You don't know at compile time either the number of columns or the number of bind variables. – With DBMS_SQL, you must put calls to DBMS_SQL.DEFINE_COLUMN and/or DBMS_SQL.BIND_VARIABLE into loops.  With NDS, you must shift from dynamic SQL to dynamic PL/SQL. – How else can you have a variable INTO or USING clause?
  • 120. Copyright 2000-2006 Steven Feuerstein - Page 120 Dynamic "SELECT * FROM <table>" in PL/SQL  You provide the table and WHERE clause. I display all the data. – I don't know in advance which or how many rows to query.  I can obtain the column information from ALL_TAB_COLUMNS...and from there the fun begins!  A relatively simple example to use as a starting point. intab_dbms_sql.sp - uses DBMS_SQL intab_nds.sp - uses NDS intab.tst
  • 121. Copyright 2000-2006 Steven Feuerstein - Page 121 Pseudo-code flow for DBMS_SQL implementation BEGIN FOR each-column-in-table LOOP add-column-to-select-list; END LOOP; DBMS_SQL.PARSE (cur, select_string, DBMS_SQL.NATIVE); FOR each-column-in-table LOOP DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype); END LOOP; fdbk := DBMS_SQL.EXECUTE (cur); LOOP fetch-a-row; FOR each-column-in-table LOOP DBMS_SQL.COLUMN_VALUE (cur, nth_col, val); END LOOP; END LOOP; END; Build the SELECT list Define each column Extract each value Parse the variable SQL Execute the query Lots of code, but relatively straightforward
  • 122. Copyright 2000-2006 Steven Feuerstein - Page 122 Parsing very long strings  One problem with EXECUTE IMMEDIATE is that you pass it a single VARCHAR2 string. – Maximum length 32K.  So what do you do when your string is longer? – Very likely to happen when you are generating SQL statements based on tables with many columns. – Also when you want to dynamically compile a program.  Time to switch to DBMS_SQL!
  • 123. Copyright 2000-2006 Steven Feuerstein - Page 123 DBMS_SQL.PARSE overloading for collections  Oracle offers an overloading of DBMS_SQL.PARSE that accepts a collection of strings, rather than a single string.  DBMS_SQL offers two different array types: – DBMS_SQL.VARCHAR2S - each string max 255 bytes. – DBMS_SQL.VARCHAR2A - each string max 32,767 bytes (new in Oracle9i).
  • 124. Copyright 2000-2006 Steven Feuerstein - Page 124 Compile DDL from a file with DBMS_SQL CREATE OR REPLACE PROCEDURE exec_from_ddl_file ( dir_in IN VARCHAR2 , file_in IN VARCHAR2 ) IS l_file UTL_FILE.file_type; l_lines DBMS_SQL.varchar2s; l_cur PLS_INTEGER := DBMS_SQL.open_cursor; PROCEDURE read_file (lines_out IN OUT DBMS_SQL.varchar2s) IS BEGIN ... Read each line into array; see compile_from_file.sql l_file := UTL_FILE.fopen (dir_in, file_in, 'R'); END read_file; BEGIN read_file (l_lines); DBMS_SQL.parse (l_cur , l_lines , l_lines.FIRST , l_lines.LAST , TRUE , DBMS_SQL.native ); DBMS_SQL.close_cursor (l_cur); exec_ddl_from_file.sql You can specify a subset of lines in the array.
  • 125. Copyright 2000-2006 Steven Feuerstein - Page 125 Describe columns in a query  DBMS_SQL offers the ability to "ask" a cursor to describe the columns defined in that cursor.  By using the DESCRIBE_COLUMNS procedure, you can sometimes avoid complex parsing and analysis logic. – Particularly useful with method 4 dynamic SQL. desccols.pkg desccols.tst
  • 126. Copyright 2000-2006 Steven Feuerstein - Page 126 Don’t take SQL for granted: hide it!  I moan and groan about SQL because it is the "Achilles Heel" of PL/SQL. – It's so easy to write SQL, it is too easy. Let's take a look... "Why does Steven make such a big deal about writing SQL inside PL/SQL? It's a no-brainer in PL/SQL, the last thing we have to worry about!"
  • 127. Copyright 2000-2006 Steven Feuerstein - Page 127 Why We Write PL/SQL Code  PL/SQL is an embedded language. Its purpose is to provide high-speed, easy access to the Oracle RDBMS.  The layer of PL/SQL code should support the data model, not disrupt our ability to evolve it. Order Table Item Table Order Entry Program Bottom line: if everyone writes SQL whenever and wherever they want to, it is very difficult to maintain and optimize the code.
  • 128. Copyright 2000-2006 Steven Feuerstein - Page 128 Single Point of (SQL) Robust Definition  The same (logically equivalent) SQL statement should not appear more than once in your application code. – Make sure that no SQL statement is repeated.  How can this be accomplished? – Intense personal discipline and peer pressure? – Endless and very detailed code review? – Automated analysis based on dependency views?  None of these will do the trick.
  • 129. Copyright 2000-2006 Steven Feuerstein - Page 129 How to Avoid SQL Repetition  You should, as a rule, not even write SQL in your PL/SQL programs – You can't repeat it if you don't write it  Instead, rely on pre-built, pre- tested, written-once, used-often PL/SQL programs. – "Hide" both individual SQL statements and entire transactions. SQL
  • 130. Copyright 2000-2006 Steven Feuerstein - Page 130 Best option: comprehensive table APIs  Many (not all!) of the SQL statements we need to write against underlying tables and views are very common and predictable. – Get me all rows for a foreign key. – Get me one row for a primary key. – Insert a row; insert a collection of rows.  Why write these over and over? Instead, rely on a standard, preferably generated, programmatic interface that takes care of this "basic plumbing." Qnxo aka the Quest CodeGen Utility www.qnxo.com SOA for PL/SQL Developers! SQL is a service. Error mgt is a service.
  • 131. Copyright 2000-2006 Steven Feuerstein - Page 131 Clear benefits of encapsulated SQL  Change/improve implementation without affecting application layer of code. – Switch between types of queries (implicit vs explicit) – Take advantage of data caching, bulk processing, SQL enhancements like MERGE.  Consistent error handling – INSERT: dup_val_on_index? – SELECT: too_many_rows? – Much less likely to be ignored when the developer writes SQL directly in the application.
  • 132. Copyright 2000-2006 Steven Feuerstein - Page 132 Example: Quest Code Tester backend  For each table, we have three generated packages: – <table>_CP for DML – <table>_QP for queries – <table>_TP for types  And for many an "extra stuff" package with custom SQL logic and related code: – <table>_XP qu_outcome_xp.qu_outcomes qu_outcome_xp.int_create_outcomes
  • 133. Copyright 2000-2006 Steven Feuerstein - Page 133 Hide single row queries  Let's look at specific examples of encapsulations. First: single row queries. – Does a row exist? Get me the row for a unique value.  Steps to follow: – Do not write your query directly in application code. – Establish clear rules: how NO_DATA_FOUND and other common errors are handled; how are single row queries implemented? – Build or generate a function to return the information, usually in the form of a record. single_row_api.sql
  • 134. Copyright 2000-2006 Steven Feuerstein - Page 134 l_name employee_rp.fullname_t; BEGIN l_name := employee_rp.fullname ( employee_id_in); ... END; CREATE OR REPLACE PACKAGE employee_rp AS SUBTYPE fullname_t IS VARCHAR2 (200); -- The formula FUNCTION fullname ( l employee.last_name%TYPE, f employee.first_name%TYPE ) RETURN fullname_t; -- Retrieval function FUNCTION fullname ( employee_id_in IN employee.employee_id%TYPE ) RETURN fullname_t; END; / Encapsulate SQL and rules... CREATE OR REPLACE PROCEDURE process_employee ( employee_id IN number) IS l_name VARCHAR2(100); BEGIN SELECT last_name || ',' || first_name INTO l_name FROM employee WHERE employee_id = employee_id; ... END; And now call the function... fullname.pkg explimpl.pkg Get me the name for an ID...
  • 135. Copyright 2000-2006 Steven Feuerstein - Page 135 Hide multi-row queries  A trickier encapsulation challenge: how do you return multiple rows? – We will need a "container" or mechanism that is not just a single instance of a row.  Options in PL/SQL from Oracle9i upwards: – Collection - use BULK COLLECT! – Cursor variable - especially handy when returning data to a non-PL/SQL host environment
  • 136. Copyright 2000-2006 Steven Feuerstein - Page 136 Return multiple rows into a collection  Collection type must be declared! – Can do so in package specification or even as a schema level object. CREATE OR REPLACE PACKAGE BODY multirows IS FUNCTION emps_in_dept ( dept_in IN employee.department_id%TYPE ) RETURN employees_aat IS l_employees employees_aat; BEGIN SELECT * BULK COLLECT INTO l_employees FROM employees WHERE department_id = dept_in; RETURN l_employees; END emps_in_dept; END multirows; multirows.sql
  • 137. Copyright 2000-2006 Steven Feuerstein - Page 137 Return multiple rows w/ cursor variable  A cursor variable is a variable that points to a result set. – You can pass CVs from one program unit to another, and even to non-PL/SQL programs! – Java, .Net, VB, etc. generally recognize and can work with cursor variables (fetch and even close).  Uses the OPEN...FOR statement to associate the variable with a query. return_refcur1.sql return_refcur.tst ref_cursor_example.sql
  • 138. Copyright 2000-2006 Steven Feuerstein - Page 138 Hide complex data transformations  Sometimes you need to return multiple rows of data that are the result of a complex transformation. – Can't fit it all (easily) into a SELECT statement.  Table functions to the rescue! – A table function is a function that returns a collection and can be called in the FROM clause of a query. – Combine with cursor variables to return these datasets through a function interface. tabfunc_scalar.sql tabfunc_streaming.sql tabfunc_pipelined.sql
  • 139. Copyright 2000-2006 Steven Feuerstein - Page 139 Hide single and multi-row DML operations  As crucial as it is to hide queries, it is even more important to encapsulate DML. – Error management is more complex and critical. – Performance impact is greater.  A generalized UPDATE is usually the most complicated. – Probably will need to hand-code specific update column combinations yourself. employees_cp.pkb
  • 140. Copyright 2000-2006 Steven Feuerstein - Page 140 SQL in PL/SQL: Think "services"  Don't take SQL for granted. – Just because it's easy, doesn't mean it's not significant.  Hide SQL behind an API: serve up the SQL via procedures and functions.  Take advantage of key features to improve performance and usability. – BULK COLLECT, FORALL, table functions, dynamic SQL
  • 141. Copyright 2000-2006 Steven Feuerstein - Page 141 > Manage errors effectively and consistently  A significant challenge in any programming environment. – Ideally, errors are raised, handled, logged and communicated in a consistent, robust manner  Some special issues for PL/SQL developers – The EXCEPTION datatype – How to find the line on which the error is raised? – Communication with non-PL/SQL host environments
  • 142. Copyright 2000-2006 Steven Feuerstein - Page 142 Achieving ideal error management  Define your requirements clearly  Understand PL/SQL error management features and make full use of what PL/SQL has to offer  Apply best practices. – Compensate for PL/SQL weaknesses – Single point of definition: use reusable components to ensure consistent, robust error management
  • 143. Copyright 2000-2006 Steven Feuerstein - Page 143 Define your requirements clearly  When will errors be raised, when handled? – Do you let errors go unhandled to the host, trap locally, or trap at the top-most level?  How should errors be raised and handled? – Will users do whatever they want or will there be standard approaches that everyone will follow?  Useful to conceptualize errors into three categories: – Deliberate, unfortunate, unexpected
  • 144. Copyright 2000-2006 Steven Feuerstein - Page 144 Different types of exceptions  Deliberate – The code architecture itself deliberately relies on an exception. Example: UTL_FILE.GET_LINE  Unfortunate – It is an error, but one that is to be expected and may not even indicate a problem. Example: SELECT INTO -> NO_DATA_FOUND  Unexpected – A "hard" error that indicates a problem within the application. Example: Primary key lookup raises TOO_MANY ROWS exec_ddl_from_file.sql get_nextline.sf fullname.pkb fullname.pkb
  • 145. Copyright 2000-2006 Steven Feuerstein - Page 145 PL/SQL error management features  Defining exceptions  Raising exceptions  Handling exceptions  Exceptions and DML
  • 146. Copyright 2000-2006 Steven Feuerstein - Page 146 Quiz! Test your exception handling know-how DECLARE aname VARCHAR2(5); BEGIN BEGIN aname := 'Justice'; DBMS_OUTPUT.PUT_LINE (aname); EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE ('Inner block'); END; DBMS_OUTPUT.PUT_LINE ('What error?'); EXCEPTION WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE ('Outer block'); END; excquiz1.sql  What do you see after running this block?
  • 147. Copyright 2000-2006 Steven Feuerstein - Page 147 Defining Exceptions  The EXCEPTION is a limited type of data. – Has just two attributes: code and message. – You can RAISE and handle an exception, but it cannot be passed as an argument in a program.  Give names to error numbers with the EXCEPTION_INIT PRAGMA. CREATE OR REPLACE PROCEDURE upd_for_dept ( dept_in IN employee.department_id%TYPE , newsal_in IN employee.salary%TYPE ) IS bulk_errors EXCEPTION; PRAGMA EXCEPTION_INIT (bulk_errors, -24381);
  • 148. Copyright 2000-2006 Steven Feuerstein - Page 148 Raising Exceptions  RAISE raises the specified exception by name. – RAISE; re-raises current exception. Callable only within the exception section.  RAISE_APPLICATION_ERROR – Communicates an application specific error back to a non-PL/SQL host environment. – Error numbers restricted to the -20,999 - -20,000 range.
  • 149. Copyright 2000-2006 Steven Feuerstein - Page 149 Using RAISE_APPLICATION_ERROR IF :NEW.birthdate > ADD_MONTHS (SYSDATE, -1 * 18 * 12) THEN RAISE_APPLICATION_ERROR (-20070, ‘Employee must be 18.’); END IF;  Communicate an error number and message to a non-PL/SQL host environment. – The following code from a database triggers shows a typical (and problematic) usage of RAISE_APPLICATION_ERROR: RAISE_APPLICATION_ERROR (num binary_integer, msg varchar2, keeperrorstack boolean default FALSE);
  • 150. Copyright 2000-2006 Steven Feuerstein - Page 150 Quiz: An Exceptional Package  So I create the valerr package and then execute the following command. What is displayed on the screen? PACKAGE valerr IS FUNCTION get RETURN VARCHAR2; END valerr; SQL> EXECUTE p.l (valerr.get); PACKAGE BODY valerr IS v VARCHAR2(1) := ‘abc’; FUNCTION get RETURN VARCHAR2 IS BEGIN RETURN v; END; BEGIN p.l ('Before I show you v...'); EXCEPTION WHEN OTHERS THEN p.l (‘Trapped the error!’); END valerr; valerr.pkg valerr2.pkg
  • 151. Copyright 2000-2006 Steven Feuerstein - Page 151 Handling Exceptions  The EXCEPTION section consolidates all error handling logic in a block. – But only traps errors raised in the executable section of the block.  Several useful functions usually come into play: – SQLCODE and SQLERRM – DBMS_UTILITY.FORMAT_ERROR_STACK – DBMS_UTILITY.FORMAT_ERROR_BACKTRACE  The DBMS_ERRLOG package – Quick and easy logging of DML errors  The AFTER SERVERERROR trigger – Instance-wide error handling
  • 152. Copyright 2000-2006 Steven Feuerstein - Page 152 DBMS_UTILITY error functions  Get the full error message with DBMS_UTILITY.FORMAT_ERROR_STACK – SQLERRM might truncate the message. – Use SQLERRM went you want to obtain the message associated with an error number.  Find line number on which error was raised with DBMS_UTILITY.FORMAT_ERROR_BACKTRACE – Introduced in Oracle10g Release 2, it returns the full stack of errors with line number information. – Formerly, this stack was available only if you let the error go unhandled. backtrace.sql
  • 153. Copyright 2000-2006 Steven Feuerstein - Page 153 DBMS_ERRLOG (Oracle10gR2)  Allows DML statements to execute against all rows, even if an error occurs. – The LOG ERRORS clause specifies how logging should occur. – Use the DBMS_ERRLOG package to associate a log table with DML operations on a base table.  Much faster than trapping errors, logging, and then continuing/recovering.  Note: FORALL with SAVE EXCEPTIONS offers similar capabilities. dbms_errlog.*
  • 154. Copyright 2000-2006 Steven Feuerstein - Page 154 The AFTER SERVERERROR trigger  Provides a relatively simple way to use a single table and single procedure for exception handling in an entire instance.  Drawbacks: – Error must go unhandled out of your PL/SQL block for the trigger to kick in. – Does not fire for all errors (NO: -600, -1403, - 1422...)  Most useful for non-PL/SQL front ends executing SQL statements directly. afterservererror.sql
  • 155. Copyright 2000-2006 Steven Feuerstein - Page 155 Exceptions and DML  DML statements generally are not rolled back when an exception is raised. – This gives you more control over your transaction.  Rollbacks occur with... – Unhandled exception from the outermost PL/SQL block; – Exit from autonomous transaction without commit/rollback; – Other serious errors, such as "Rollback segment too small".  Corollary: error logs should rely on autonomous transactions to avoid sharing the same transaction as the application. – Log information is committed, while leaving the business transaction unresolved. log8i.pkg
  • 156. Copyright 2000-2006 Steven Feuerstein - Page 156 Best practices for error management  Compensate for PL/SQL weaknesses.  Some general guidelines: – Avoid hard-coding of error numbers and messages. – Build and use reusable components for raising, handling and logging errors.  Application-level code should not contain: – RAISE_APPLICATION_ERROR: don't leave it to the developer to decide how to raise. – PRAGMA EXCEPTION_INIT: avoid duplication of error definitions.
  • 157. Copyright 2000-2006 Steven Feuerstein - Page 157 Compensate for PL/SQL weaknesses  The EXCEPTION datatype does not allow you to store the full set of information about an error. – What was the context in which the error occurred?  Difficult to ensure execution of common error handling logic. – Usually end up with lots of repetition. – No "finally" section available in PL/SQL - yet.  Restrictions on how you can specify the error – Only 1000 for application-specific errors....
  • 158. Copyright 2000-2006 Steven Feuerstein - Page 158 Object-like representation of an exception  An error is a row in the error table, with many more attributes than simply code and message, including: – Dynamic message (substitution variables) – Help message (how to recover from the problem)  An error instance is one particular occurrence of an error. – Associated with it are one or more values that reflect the context in which the error was raised.
  • 159. Copyright 2000-2006 Steven Feuerstein - Page 159 ERD for error definition tables qd_error.erd qd_runtime.pkb
  • 160. Copyright 2000-2006 Steven Feuerstein - Page 160 Hard to avoid code repetition in handlers  If every developer writes exception handler code on their own, you end up with an unmanageable situation. – Different logging mechanisms, no standards for error message text, inconsistent handling of the same errors, etc. WHEN NO_DATA_FOUND THEN INSERT INTO errlog VALUES ( SQLCODE , 'No company for id ' || TO_CHAR ( v_id ) , 'fixdebt', SYSDATE, USER ); WHEN OTHERS THEN INSERT INTO errlog VALUES (SQLCODE, SQLERRM, 'fixdebt', SYSDATE, USER ); RAISE; END;
  • 161. Copyright 2000-2006 Steven Feuerstein - Page 161 Prototype exception manager package PACKAGE errpkg IS PROCEDURE raise (err_in IN PLS_INTEGER); PROCEDURE raise (err_in in VARCHAR2); PROCEDURE record_and_stop ( err_in IN PLS_INTEGER := SQLCODE ,msg_in IN VARCHAR2 := NULL); PROCEDURE record_and_continue ( err_in IN PLS_INTEGER := SQLCODE ,msg_in IN VARCHAR2 := NULL); END errpkg; Generic Raises Record and Stop Record and Continue errpkg.pkg
  • 162. Copyright 2000-2006 Steven Feuerstein - Page 162 Invoking standard handlers  The rule: developers should only call a pre-defined handler inside an exception section – Make it easy for developers to write consistent, high-quality code – They don't have to make decisions about the form of the log and how the process should be stopped EXCEPTION WHEN NO_DATA_FOUND THEN errpkg.record_and_continue ( SQLCODE, ' No company for id ' || TO_CHAR (v_id)); WHEN OTHERS THEN errpkg.record_and_stop; END; The developer simply describes the desired action.
  • 163. Copyright 2000-2006 Steven Feuerstein - Page 163 Specifying the error Options for communicating an application-specific error: • Just use -20000 all the time? * Pick a number from -20999 to -20000? * Use any positive error number besides 1 and 100? Use error names instead of numbers? Perhaps the error number is of no real importance!
  • 164. Copyright 2000-2006 Steven Feuerstein - Page 164 Avoid hard-coding of -20,NNN Errors  Give your error numbers names and associate them with named exceptions. PACKAGE errnums IS en_general_error CONSTANT NUMBER := -20000; exc_general_error EXCEPTION; PRAGMA EXCEPTION_INIT (exc_general_error, -20000); en_must_be_18 CONSTANT NUMBER := -20001; exc_must_be_18 EXCEPTION; PRAGMA EXCEPTION_INIT (exc_must_be_18, -20001); en_sal_too_low CONSTANT NUMBER := -20002; exc_sal_too_low EXCEPTION; PRAGMA EXCEPTION_INIT (exc_sal_too_low , -20002); max_error_used CONSTANT NUMBER := -20002; END errnums; msginfo.pkg msginfo.fmb/fmx But don't write this code manually!
  • 165. Copyright 2000-2006 Steven Feuerstein - Page 165 Using the standard raise program  Rather than have individual programmers call RAISE_APPLICATION_ERROR, simply call the standard raise program. Benefits: – Easier to avoid hard-codings of numbers. – Support positive error numbers!  Let's revisit that trigger logic using the infrastructure elements... PROCEDURE validate_emp (birthdate_in IN DATE) IS BEGIN IF ADD_MONTHS (SYSDATE, 18 * 12 * -1) < birthdate_in THEN errpkg.raise (errnums.en_too_young); END IF; END; No more hard-coded strings or numbers.
  • 166. Copyright 2000-2006 Steven Feuerstein - Page 166 Raise/handle errors by number...or name?  The above trigger fragment illustrates a common problem: Hard-coding of error numbers and messages.  Certainly, it is better to use named constants, as in: BEGIN IF employee_rp.is_to_young (:new.hire_date) THEN RAISE_APPLICATION_ERROR ( -20175, 'You must be at least 18 years old!'); END IF; BEGIN IF employee_rp.is_to_young (:new.hire_date) THEN RAISE_APPLICATION_ERROR ( employee_rp.en_too_young , employee_rp.em_too_young); END IF; But now we have a centralized dependency.
  • 167. Copyright 2000-2006 Steven Feuerstein - Page 167 Raising errors by name  Use an error name (literal value). – The code compiles now. – Later, I define that error in the repository. – No central point of failure.  Downsides: risk of typos, runtime notification of "undefined error." BEGIN IF employee_rp.is_to_young (:new.hire_date) THEN qd_runtime.raise_error ( 'EMPLOYEE-TOO-YOUNG' , name1_in => 'LAST_NAME' , value1_in => :new.last_name); END IF; Qnxo qd_runtime.*
  • 168. Copyright 2000-2006 Steven Feuerstein - Page 168 Summary: error management with PL/SQL  Make sure you understand how it all works – Exception handling is tricky stuff  Set standards before you start coding – It's not the kind of thing you can easily add in later  Use standard infrastructure components – Everyone and all programs need to handle errors the same way  Don't accept the limitations of Oracle's current implementation. – You can do lots to improve the situation.
  • 169. Copyright 2000-2006 Steven Feuerstein - Page 169 Six Simple Steps to Unit Testing Happiness > Unit test PL/SQL programs or....
  • 170. Copyright 2000-2006 Steven Feuerstein - Page 170 Writing software is.....
  • 171. Copyright 2000-2006 Steven Feuerstein - Page 171 Testing software is.....
  • 172. Copyright 2000-2006 Steven Feuerstein - Page 172 Buggy software is.... Embarrassing Expensive Deadly
  • 173. Copyright 2000-2006 Steven Feuerstein - Page 173 Buggy software is embarrassing  There can be as many as 20 to 30 bugs per 1,000 lines of software code. —Sustainable Computing Consortium  32% of organizations say that they release software with too many defects.—Cutter Consortium  38% of organizations believe they lack an adequate software quality assurance program.—Cutter Consortium  27% of organizations do not conduct any formal quality reviews.—Cutter Consortium  Developers spend about 80% of development costs on identifying and correcting defects.—The National Institute of Standards and Technology
  • 174. Copyright 2000-2006 Steven Feuerstein - Page 174 Buggy software is expensive - $60B per year in US alone!?  JUNE 25, 2002 (COMPUTERWORLD) - WASHINGTON -- Software bugs are costing the U.S. economy an estimated $59.5 billion each year. Of the total $59.5 billion cost, users incurred 64% of the cost and developers 36%.  There are very few markets where "buyers are willing to accept products that they know are going to malfunction," said Gregory Tassey, the National Institute of Standards and Technology senior economist who headed the study. "But software is at the extreme end in terms of errors or bugs that are in the typical product when it is sold."  Oh, yes and Y2K: $300B? $600B?
  • 175. Copyright 2000-2006 Steven Feuerstein - Page 175 Buggy software is deadly  2003 Software failure contributes to power outage across the Northeastern U.S. and Canada, killing 3 people.  2001 Five Panamanian cancer patients die following overdoses of radiation, amounts of which were determined by faulty use of software.  2000 Crash of a Marine Corps Osprey tilt-rotor aircraft partially blamed on “software anomaly" kills four soldiers.  1997 Radar that could have prevented Korean jet crash (killing 225) hobbled by software problem.  1995 American Airlines jet, descending into Cali, Colombia, crashes into a mountain, killing 159. Jury holds maker of flight-management system 17% responsible. A report by the University of Bielefeld in Germany found that the software presented insufficient and conflicting information to the pilots, who got lost.
  • 176. Copyright 2000-2006 Steven Feuerstein - Page 176 How do we avoid buggy software?  Clear and accurate requirements  Careful design  Excellent tools  Best practices, standards, guidelines (that is, follow them)  Code review  Thorough testing Uh oh... the world is in big trouble.
  • 177. Copyright 2000-2006 Steven Feuerstein - Page 177 Wouldn't it be great if...  It was easy to construct tests – An agreed-upon and effective approach to test construction that everyone can understand and follow  It was easy to run tests – And see the results, instantly and automatically.  Testing were completely integrated into my development, QA, and maintenance processes – No program goes to QA until it passes its unit tests – Anyone can maintain with confidence, because my test suite automatically validates my changes
  • 178. Copyright 2000-2006 Steven Feuerstein - Page 178 Different types of testing  There are many types of testing: functional/system tests, stress tests, unit tests.  A "unit test" is the test of a single unit of code. – Also known as "programmer tests"  Unit tests are the responsibility of developers - that is, us, the people in this room. – Not fundamentally a job for the QA department, which generally focuses on functional and system tests.
  • 179. Copyright 2000-2006 Steven Feuerstein - Page 179 Truth or Dare  How do you (or your team) unit test your PL/SQL code today? –We use automated testing software. –We have a formal test process that we each follow, but otherwise a manual process. –Everyone does their own thing and we hope for the best. –Our users test our code. ? ? ? ?
  • 180. Copyright 2000-2006 Steven Feuerstein - Page 180 Unit testing reality  Let's face it: we PL/SQL developers don't spend nearly enough time unit testing our code. – For the most part, we run a script that displays output on the screen and then we stare at all until we decide if the test succeeded or failed.  There are some understandable reasons: – Very few tools and utilities have been available, to date, for PL/SQL testing. – Managers don't give us enough time to prepare and execute tests.
  • 181. Copyright 2000-2006 Steven Feuerstein - Page 181 Typical Testing  DBMS_OUTPUT.PUT_LINE - unit testing mechanism of choice? betwnstr.sf betwnstr.tst BEGIN DBMS_OUTPUT.PUT_LINE (betwnstr (NULL, 3, 5, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 0, 5, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, 5, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', -3, -5, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', NULL, 5, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, NULL, true)); DBMS_OUTPUT.PUT_LINE (betwnstr ('abcdefgh', 3, 100, true)); END;
  • 182. Copyright 2000-2006 Steven Feuerstein - Page 182 Problems with Typical Testing  Almost entirely ad hoc – No comprehensive effort to compile test cases – No infrastructure to record cases and administer tests  Difficult to verify correctness – Non-automated verification is slow and error-prone.  Relies on the user community to test – Since we are never really sure we’ve tested properly, we rely on our users to finish our job. There has got to be a better way!
  • 183. Copyright 2000-2006 Steven Feuerstein - Page 183 Moving towards a Better Way  Change from within: your code will not test itself. – You must accept the responsibility and then be disciplined (sigh...that's not fun at all). – Commit to testing and see how much that changes the way you write your programs.  Change from without: you need tools to help you do your testing. – utPLSQL – Quest Code Tester for Oracle  Ah, but what about those six, simple steps? http://utplsql.sourceforge.net/ http://www.ToadWorld.com
  • 184. Copyright 2000-2006 Steven Feuerstein - Page 184 Six Simple Steps to Unit Testing Happiness Define Req’ments 1 Construct Header 2 Define Tests 3 Build Test Code 4 Build / Fix Code 5 Test Code Debug 6 The Build Cycle Bug Report Enhance. Request Post- Production
  • 185. Copyright 2000-2006 Steven Feuerstein - Page 185 Six Simple Steps...Elaborated  1. Describe fully the required functionality of the program. – Don’t start coding until you are sure you understand what is needed, and be ready to engage with your user on all levels (technical, business, ethical, etc.)  2. Define the header of the program (name, parameter list, return value). – Once you do that, you can compile a “stub” of that program.  3. Come up with at least an initial set of test cases for the program. – Think about what it will take to prove your program works.  4. Build test code that implements all test cases. – Ah...the truly challenging step.  5. Write the program unit. – Back to your comfort zone, but now you are building against a test plan.  6. Test, debug, fix, test, debug, fix, test, debug.... – We never get it right the first time.  Then...repeat steps 3-6 for each enhancement and bug report. – Every bug report highlights two bugs: one in your code and another in your test case.
  • 186. Copyright 2000-2006 Steven Feuerstein - Page 186 Describe required functionality  I need a variation of SUBSTR that will return the portion of a string between specified start and end locations.  Some specific requirements: – It should work like SUBSTR as much as makes sense (treat a start location of 0 as 1, for example; if the end location is past the end of the string, the treat it as the end of the string). – Negative start and end should return a substring at the end of the string. – Allow the user to specify whether or not the endpoints should be included.
  • 187. Copyright 2000-2006 Steven Feuerstein - Page 187 Define the program specification  My specification or header should be compatible with all requirements. – I also self-document that the function is deterministic: no side effects.  I can (and will) now create a compile-able stub for the program. – Then fully define and implement my test code! FUNCTION betwnstr ( string_in IN VARCHAR2 , start_in IN PLS_INTEGER , end_in IN PLS_INTEGER , inclusive_in IN BOOLEAN DEFAULT TRUE ) RETURN VARCHAR2 DETERMINISTIC betwnstr0.sf
  • 188. Copyright 2000-2006 Steven Feuerstein - Page 188 Elaborate the test cases  Before I write any code, I will come up with as many of the test cases as possible -- and write my test code. – This is known as "test-driven development". TDD is a very hot topic among developers and is associated with Agile Software (http://agilemanifesto.org/) and Extreme Programming.  Putting aside the fancy names and methodologies, TDD makes perfect sense -- when you stop to think about it. If you write your program before you define your tests, how do you know you when you're done? TNT or TDD? And if you write your tests afterward, you are likely to prejudice your tests to show "success."
  • 189. Copyright 2000-2006 Steven Feuerstein - Page 189 Brainstorm the test cases  Even a simple program will have many test cases! – You don't have to think of every one before you implement your program and start your testing. – You should aim at least for a "representative" sampling.  But where do you store/define the test cases? – You can certainly put the information in and work from a document or spreadsheet. – Best of all, however, is to link the test case definitions as tightly as possible to the code.
  • 190. Copyright 2000-2006 Steven Feuerstein - Page 190 Some of the test cases for BETWNSTR  Start and end within the string ("normal" usage)  Start of 0, End past end of string  Null string, string of single character, 32767 len character  Null start and/or end  Negative start and end  Start larger than end (positive and negative)  Variations of the above with different inclusive values Don't be overwhelmed by the total number of cases. Start out with a representative sampling. You can always add from there. But now a big challenge: where do you put this test case information? Tie it as closely as possible to your code.
  • 191. Copyright 2000-2006 Steven Feuerstein - Page 191 Test Cases and Test Code  The challenge (terror?) of the blank screen.... – How do I define the test cases? – How do I set up those tests? – How do I verify the results?  Let's see how Quest Code Tester helps me tackle these challenges. – Define and maintain your test cases through a graphical interface, then let it do all the work.
  • 192. Copyright 2000-2006 Steven Feuerstein - Page 192 Don't write test code – describe tests  Rather than write test code, describe the tests you need through a graphical interface. – It’s just like SQL vs. programming.  Quest Code Tester saves your descriptions in a test repository. – Critical information, especially for IT management  It generates a PL/SQL test package based on your descriptions.  It runs the test at your request and displays the results. Time for some software magic....
  • 193. Copyright 2000-2006 Steven Feuerstein - Page 193 Test, debug, fix, test, debug, fix, test, debug...  With a test script in place, I can quickly and easily move back and forth between running my program, identifying errors, debugging and fixing the code, running the program again.  I also then have my test process and regression test in place so that as I make enhancements or fix bugs, I can fall back on this foundation. – It is critical that you maintain your test case definitions and test code as your program evolves. – And update those first -- before you change the program!
  • 194. Copyright 2000-2006 Steven Feuerstein - Page 194 Change Your Testing Ways  Quest Code Tester (and even utPLSQL) can make a dramatic difference in your ability to test and your confidence in the resulting code.  Build a comprehensive "library" of unit tests as you build your application – These tests and all their test cases can be passed on to other developers – Anyone can now enhance or maintain the code with confidence. Make your changes and run the tests. If you get a green light, you're OK!
  • 195. Copyright 2000-2006 Steven Feuerstein - Page 195 Testing: Baby steps better than paralysis.  Unit testing can be an intimidating process. – You are never really done; You have to maintain your test code along with your application code.  But every incremental improvement in testing yields immediate and long-term benefits. – Don't worry about 100% test coverage. – Give Quest Code Tester a try! – Everything shown in this class will be part of the freeware version. www.ToadWorld.com
  • 196. Copyright 2000-2006 Steven Feuerstein - Page 196 > Write readable, maintainable code  PL/SQL allows you to write very readable, self-documenting and easily-maintained code. – This should be a primary objective for any program.  Let's look at... – Readability features you should use – Modular construction in PL/SQL
  • 197. Copyright 2000-2006 Steven Feuerstein - Page 197 Readability features you should use  END labels – For program units, loops, nested blocks  SUBTYPEs – Create application-specific datatypes!  Named notation – Sometimes the extra typing is worth it!  Naming conventions – Being consistent is the most important thing.  Local or nested modules – Key technique, to be covered under "Modular construction..." end_labels.sql plsql_limits.pks explimpl.pkg namednot.sql www.oracle.oreilly.com OraclePL/SQL Best Practices Examples
  • 198. Copyright 2000-2006 Steven Feuerstein - Page 198 Modular construction in PL/SQL  Packages: some quick reminders... –Logical containers for related elements –Overloading –Package-level data and caching –Initialization section  Local or nested modules –Avoid spaghetti code! –Keep your executable sections small/tiny.
  • 199. Copyright 2000-2006 Steven Feuerstein - Page 199 Packages: key PL/SQL building block  Employ object-oriented design principles – Build at higher levels of abstraction – Enforce information hiding - control what people see and do – Call packaged code from object types and triggers  Encourages top-down design and bottom-up construction – TD: Design the interfaces required by the different components of your application without addressing implementation details – BU: Existing packages contain building blocks for new code  Organize your stored code more effectively  Implements session-persistent data
  • 200. Copyright 2000-2006 Steven Feuerstein - Page 200 Overloading in packages: key usability technique  Overloading (static polymorphism): two or more programs with the same name, but different signature. – You can overload in the declaration section of any PL/SQL block, including the package body (most common).  Overloading is a critical feature when building comprehensive programmatic interfaces (APIs) or components using packages. – 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. myproc myfunc myproc Compare: DBMS_OUTPUT and p
  • 201. Copyright 2000-2006 Steven Feuerstein - Page 201 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. – Another name for overloading is "static polymorphism."  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.
  • 202. Copyright 2000-2006 Steven Feuerstein - Page 202 How Overloading Works, continued  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.
  • 203. Copyright 2000-2006 Steven Feuerstein - Page 203 Quiz! Nuances of Overloading  Is this a valid overloading? Will it compile? How can I use it? CREATE OR REPLACE PACKAGE sales IS PROCEDURE calc_total (zone_in IN VARCHAR2); PROCEDURE calc_total (reg_in IN VARCHAR2); END sales; BEGIN sales.calc_total ('NORTHWEST'); sales.calc_total ('ZONE2'); END; ambig_overloading.sql ?
  • 204. Copyright 2000-2006 Steven Feuerstein - Page 204 Package Data: Useful and Sticky  The scope of a package is your session, and any data defined at the "package level" also has session scope. – If defined in the package specification, any program can directly read/write the data. – Ideal for program-specific caching.  General best practice: hide your package data in the body so that you can control access to it.  Use the SERIALLY_REUSABLE pragma to move data to SGA and have memory released after each usage. thisuser.pkg thisuser.tst serial.sql
  • 205. Copyright 2000-2006 Steven Feuerstein - Page 205 Package Initialization Structure  The initialization section: – Is defined after and outside of any programs in the package. – Is not required. – Can have its own exception handling section.  Useful for: – Perform complex setting of default or initial values. – Set up package data which does not change for the duration of a session. – Confirm that package is properly instantiated. PACKAGE BODY pkg IS PROCEDURE proc IS BEGIN END; FUNCTION func RETURN BEGIN END; BEGIN ...initialize... END pkg; BEGIN after/outside of any program defined in the pkg. init.pkg init.tst datemgr.pkg
  • 206. Copyright 2000-2006 Steven Feuerstein - Page 206 Packages vs. Stand-alone programs  General recommendation: use packages instead of stand-alone programs. – Better way to organize code. – Can hide implementation and reduce need to recompile programs using the package.  Other considerations.... – Entire package loaded when any single program is called. – Central packages can become a "bottleneck" when changes are needed. recompile.sql
  • 207. Copyright 2000-2006 Steven Feuerstein - Page 207 Limit executable sections to <= 50 lines  It is virtually impossible to understand and therefore debug or maintain code that has long, meandering executable sections.  How do you follow this guideline? – Don't skimp on the packages. – Top-down design / step-wise refinement – Use lots of local or nested modules. ?!?!
  • 208. Copyright 2000-2006 Steven Feuerstein - Page 208 PROCEDURE assign_workload (department_in IN NUMBER) IS CURSOR emps_in_dept_cur IS SELECT * FROM emp WHERE deptno = department_in; PROCEDURE assign_next_open_case (emp_id_in IN NUMBER, case_out OUT NUMBER) IS BEGIN … END; FUNCTION next_appointment (case_id_in IN NUMBER) IS BEGIN … END; PROCEDURE schedule_case (case_in IN NUMBER, date_in IN DATE) IS BEGIN … END; BEGIN /* main */ FOR emp_rec IN emps_in_dept_cur LOOP IF analysis.caseload (emp_rec.emp_id) < analysis.avg_cases (department_in) THEN assign_next_open_case (emp_rec.emp_id, case#); schedule_case (case#, next_appointment (case#)); END IF; END LOOP END assign_workload;  Move blocks of complex code into the declaration section  Replace them with descriptive names  The code is now easier to read and maintain  You can more easily identify areas for improvement Check out my series on the OverloadCheck utility on OTN Let's read some code! locmod.sp 10g_indices_of.sql
  • 209. Copyright 2000-2006 Steven Feuerstein - Page 209 Challenges of local modules  Requires discipline. – Always be on the lookout for opportunities to refactor.  Need to read from the bottom, up. – Takes some getting used to.  Your IDE needs to reveal the internal structure of the program.  Sometimes can feel like a "wild goose chase". – Where is the darned thing actually implemented?
  • 210. Copyright 2000-2006 Steven Feuerstein - Page 210 Thoughts on Deterministic Programs  Some clear benefits to deterministic code: – They are easier to test, since they have no side- effects. – Oracle will optimize/avoid execution - sometimes!  My suggestions: – Segregate as much of your code into deterministic and non-deterministic program units. – Carefully declare all deterministic programs. show_deterministc.sql deterministc.sql
  • 211. Copyright 2000-2006 Steven Feuerstein - Page 211 Acknowledgements and Resources  Very few of my ideas are truly original. I have learned from every one of these books and authors – and you can, too!
  • 212. Copyright 2000-2006 Steven Feuerstein - Page 212 A guide to my mentors/resources  A Timeless Way of Building – a beautiful and deeply spiritual book on architecture that changed the way many developers approach writing software.  On Intelligence – a truly astonishing book that lays out very concisely a new paradigm for understanding how our brains work.  Peopleware – a classic text on the human element behind writing software.  Refactoring – formalized techniques for improving the internals of one's code without affect its behavior.  Code Complete – another classic programming book covering many aspects of code construction.  The Cult of Information – thought-provoking analysis of some of the down- sides of our information age.  Patterns of Software – a book that wrestles with the realities and problems with code reuse and design patterns.  Extreme Programming Explained – excellent introduction to XP.  Code and Other Laws of Cyberspace – a groundbreaking book that recasts the role of software developers as law-writers, and questions the direction that software is today taking us.
  • 213. Copyright 2000-2006 Steven Feuerstein - Page 213 (Mostly) Free PL/SQL Resources  Oracle Technology Network PL/SQL page  OTN Best Practice PL/SQL  Oracle documentation  OraclePLSQLProgramming.com  Quest Pipelines  Quest Code Tester for Oracle  PL/Vision http://www.oracle.com/technology/tech/pl_sql/index.html http://www.oracle.com/technology/pub/columns/plsql/index.html http://tahiti.oracle.com/ http://oracleplsqlprogramming.com/ http://quest-pipelines.com/ http://quest-pipelines.com/pipelines/dba/PLVision/plvision.htm http://www.ToadWorld.com http://unittest.inside.quest.com/index.jspa
  • 214. Copyright 2000-2006 Steven Feuerstein - Page 214 So Much to Learn...  Don't panic -- but don't stick your head in the sand, either. – You won't thrive as an Oracle7, Oracle8 or Oracle8i developer!  You can do so much more from within PL/SQL than you ever could before. – Familiarity with new features will greatly ease the challenges you face.
  • 215. Copyright 2000-2006 Steven Feuerstein - Page 215 And I end with my...  1. Drink lots of water.  2. Write tiny chunks of code.  3. Stop writing so much SQL.  4. Stop guessing and start testing. Thank you for attending this Best of PL/SQL seminar! Top Four Tips for Excellent PL/SQL

Editor's Notes

  • #11: This is my version, use either this or previous one.