Você está na página 1de 311

Oracle PL/SQL Programming

Sponsored by AMIS

The Best of Oracle PL/SQL


Must Know Features,
Best Practices and
New Features in Oracle Database 11g

Steven Feuerstein
steven@stevenfeuerstein.com
www.StevenFeuerstein.com

Oracle PL/SQL Programming

Best of Oracle PL/SQL Agenda


Runtime Memory Management and PL/SQL
The Oracle Database 10g compiler
Optimization, warnings, conditional compilation

Collections: foundation for newest and best


features
Bulk Processing
BULK COLLECT and FORALL

Table Functions
Dynamic SQL in PL/SQL
Copyright 2011 Feuerstein and Associates

Page 2

Oracle PL/SQL Programming

Best of Oracle PL/SQL Agenda - continued


"Must Know" error management features
DBMS_UTILITY functions
DBMS_ERRLOG and LOG ERRORS

Oracle Database 11g features


Function Result Cache
Trigger enhancements
Dynamic SQL enhancements

Say Goodbye to Hard-coding


Hide implementations
Extreme Modularization

Copyright 2011 Feuerstein and Associates

Page 3

Oracle PL/SQL Programming

How to benefit most from this training


Watch, listen, ask questions, focus on concepts and principles.
Download and use any of my training materials:

PL/SQL Obsession

http://www.ToadWorld.com/SF

Download and use any of my scripts (examples,


performance scripts, reusable code) from the same
location: the demo.zip file.
filename_from_demo_zip.sql

You have my permission to use all these materials to do


internal trainings and build your own applications.
But remember: they are not production ready.
You must test them and modify them to fit your needs.
Copyright 2011 Feuerstein and Associates

Page 4

Oracle PL/SQL Programming

Websites for PL/SQL Developers


www.plsqlchallenge.com
Daily PL/SQL quiz with weekly and
monthly prizes

www.plsqlchannel.com
27+ hours of detailed video training
on Oracle PL/SQL

www.stevenfeuerstein.com
Monthly PL/SQL newsletter

www.toadworld.com/SF
Quest Software-sponsored portal
for PL/SQL developers

Copyright 2011 Feuerstein and Associates

Page 5

Oracle PL/SQL Programming

Analyze memory usage of PL/SQL code


It is certainly possible to write PL/SQL code
that consumes so much memory, it kills a
user's session.
It's quite easy to do, in fact.

As you work with more advanced features, like


collections and FORALL, you will need to pay
attention to memory, and make adjustments.
First, let's review how Oracle manages
memory at run-time.
Copyright 2011 Feuerstein and Associates

memory_error.sql

Page 6

Oracle PL/SQL Programming

PL/SQL in Shared Memory


System Global Area (SGA) of RDBMS Instance
Shared Pool
Reserved Pool

Library cache
Shared SQL

Select *
from emp

Pre-parsed

Large Pool

Session 1

calc_totals

show_emps

Update emp
Set sal=...

upd_salaries

emp_rec emp%rowtype;
tot_tab tottabtype;

emp_rec emp%rowtype;
tot_tab tottabtype;

Session 1 memory
(PGA/UGA)

Session 2 memory
(PGA/UGA)

Copyright 2011 Feuerstein and Associates

Session 2

Page 7

Oracle PL/SQL Programming

How PL/SQL uses the SGA, PGA and UGA


PACKAGE Pkg is
/* 11g feature! */
Nonstatic_Constant CONSTANT PLS_INTEGER := My_Sequence.Nextval;
Static_Constant
CONSTANT PLS_INTEGER := 42;
END Pkg;

The SGA contains information that can be shared


across sessions connected to the instance.
In PL/SQL, this is limited to package static constants.

The User Global Area contains session-specific data


that persists across server call boundaries
Package-level data

The Process Global Area contains session-specific


data that is released when the current server call
terminates: "local" data.
Copyright 2011 Feuerstein and Associates

Page 8

Oracle PL/SQL Programming

Calculating PGA and UGA Consumption


SELECT n.name, s.VALUE
FROM sys.v_$sesstat s, sys.v_$statname n
WHERE
s.statistic# = n.statistic# AND s.sid = my_session.sid
AND n.name IN ('session uga memory', 'session pga memory')

Oracle keeps track of and shows the PGA and


UGA consumption for a session in the
v_$sesstat dynamic view.
With the correct privileges, PL/SQL developers
can analysis their code's memory usage.
BEGIN
plsql_memory.start_analysis;
run_my_application;
plsql_memory.show_memory_usage;
END;
Copyright 2011 Feuerstein and Associates

show_pga_uga.sql
grantv$.sql
plsql_memory.pkg
plsql_memory_demo.sql

Page 9

Oracle PL/SQL Programming

Tips for managing memory


Use LIMIT clause with BULK COLLECT.
Use varrays with BULK COLLECT to
declaratively guard against "memory creep."
Use NOCOPY hint when passing IN OUT
collections.
Be very careful about defining variables at
the package level.
Memory will not be released when the block
terminates.

Use pipelined table functions.


Copyright 2011 Feuerstein and Associates

bulklimit.sql
varray_collection_limit.sql
nocopy*.tst
tabfunc_pipelined.sql
Page 10

Oracle PL/SQL Programming

Conclusions
Oracle takes responsibility for managing memory
used for data (user data and "metadata"
program code, table definitions, etc.) shared by
multiple connections.
Based on parameter set by DBAs.

It is up to developers and DBAs to determine how


much PGA memory can be used per connection.
Then developers must make the necessary
changes in their code to conform to that limit.

Copyright 2011 Feuerstein and Associates

Page 11

Oracle PL/SQL Programming

Fully Leverage the Oracle10g PL/SQL Compiler


Oracle demonstrated its long-term
commitment to PL/SQL with the relesae of
Oracle Database 10g
Many new features and a complete re-write of
the compiler.

Automatic, transparent optimization of code


Compile-time warnings framework to help
you improve the quality of your code.
Conditional compilation: you decide what
code should be compiled/ignored!
Copyright 2011 Feuerstein and Associates

Page 12

Oracle PL/SQL Programming

The Optimizing Compiler


The PL/SQL compiler now has the ability to
automatically optimize your code.
The compiler rearranges your code.
Compile time increases, runtime performance improves.

You choose the level of optimization :


0 Pre-10g compilation without optimization
1 Smaller scale change, less impact on compile times
2 Most aggressive, maximum possible code transformations,
biggest impact on compile time. [default]
3 (Oracle11g) In-lining of local subprograms, in addition to all the
optimization performed at level 2

Stick with the default, unless you have a clear need


for an exception.
Copyright 2011 Feuerstein and Associates

Page 13

Oracle PL/SQL Programming

The PL/SQL Optimizer: High Level View


The optimizer takes advantage of "freedoms" to reorder the execution of statements.
In essence, changing the route that the runtime engine
takes to get from point A to point B in your code.

Some examples:
Unless otherwise specified, the operands of an expression
operator may be evaluated in any order.
Operands of a commutative operator may be commuted.
The actual arguments of a call or a SQL statement may be
evaluated in any order (including default actual
arguments).

Optimization does not change the logical behavior of


your code.
Optimization should not, for example, cause any of your
regression tests to suddenly fail!
Copyright 2011 Feuerstein and Associates

Page 14

Oracle PL/SQL Programming

Some Examples
T := A + B;
... T ...
...
... T ...

... A + B ...
...
... A + B ...

for i in 1 .. 10 loop
A := B + C;
...
end loop;
A := B + C;
for i in 1 .. 10 loop
...
end loop;
FOR rec in (SELECT ...)
LOOP
... do stuff
END LOOP;

Copyright 2011 Feuerstein and Associates

T is a generated variable. We never see


it. And one operation is saved.

Automatic relocation of a loop invariant.


Avoid repetitive computations.

SELECT ...
BULK COLLECT INTO ...
FROM ...

Execute cursor FOR loop


at BULK COLLECT
levels of performance.

10g_optimize_cfl.sql

Page 15

Oracle PL/SQL Programming

Things to Keep in Mind


my_function () * NULL

The PL/SQL runtime engine will always execute


your subprograms, even if the optimizer detects
that the results of that subprogram call are "not
needed."
Exception: DETERMINISTIC functions in 11g

You cannot rely on a specific order of evaluation


of arguments in a subprogram call or even when
package initialization takes place.
The compiler will even avoid initialization of a package
if it not needed (using a TYPE for example).
Copyright 2011 Feuerstein and Associates

Page 16

Oracle PL/SQL Programming

Changing the optimizer level


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

and then:
ALTER PROCEDURE bigproc COMPILE REUSE SETTINGS;

Copyright 2011 Feuerstein and Associates

Page 17

11g

Oracle PL/SQL Programming

Oracle11g In-lining optimization


A new level, 3, tells Oracle to automatically search
out opportunities to "inline" code for nested
subprograms.
This means that a pointer to the subprogram is replaced
with the implementation of the subprogram.

Oracle's own tests have shown 10-20%


performance improvement.
Depends on how many local modules you create and
how often they are used.

Note: compile code size increases.


11g_inline*.sql

ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 3;


Copyright 2011 Feuerstein and Associates

Page 18

11g

Oracle PL/SQL Programming

Selective Inlining with PRAGMA


PRAGMA INLINE (subprogram, 'YES')

You can also keep the optimization level at 2 and


request inlining explicitly for specific subprogram
invocations with a new INLINE pragma.
Inlining applies to the following statements:
Assignment, CALL, conditional, CASE, CONTINUE-WHEN,
EXECUTE IMMEDIATE, EXIT-WHEN, LOOP, RETURN

You can also request inlining for all executions of the


subprogram by placing the PRAGMA before the
declaration of the subprogram.
Inlining, like NOCOPY, is a request.
Under some circumstances, inlining will not take place.
Copyright 2011 Feuerstein and Associates

Page 19

11g

Oracle PL/SQL Programming

Inlining Could Slow Down Code


Oracle warns that inlining occurs early in the
optimization process and may "preclude later,
more powerful optimizations."
If you find that inlining is slowing down a
program unit, profile execution to identify
subprograms for which to turn off inlining.
Oracle recommends the new-to-11g hierarchical
profiler, DBMS_HPROF.

Selectively disable inlining with pragma:


PRAGMA INLINE (subprogram, 'NO')

Copyright 2011 Feuerstein and Associates

Page 20

Oracle PL/SQL Programming

Learn more about the PL/SQL optimizer


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

PL/SQL Just Got Faster


Explains the workings of the PL/SQL compiler and runtime system and
shows how major improvements on this scale are indeed possible.

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.

PL/SQL Performance Measurement Harness


Describes a performance experiment whose conclusion is the large factors
quoted above. Oracle provides a downloadable kit to enable you to
repeat the experiment yourself.
Copyright 2011 Feuerstein and Associates

Page 21

Oracle PL/SQL Programming

Warnings help you build better code


Your code compiles without errors. Great, you
can run that program!
But does it use the PL/SQL language optimally?
In Oracle 10g, Oracle added a compile-time
warnings framework.
Automatically informs you of ways to improve the
quality or performance of your code.

All warnings shown in Error Messages manual,


with the PLW prefix.
http://tahiti.oracle.com

Copyright 2011 Feuerstein and Associates

Page 22

Oracle PL/SQL Programming

Enable and Disable Warnings


To use compiler warnings, you must turn them
on for session or for a particular program unit.
By default, warnings are disabled.

Can specify individual warnings or categories.


ALTER SESSION [ENABLE | DISABLE |ERROR]:
[ALL|SEVERE|INFORMATIONAL|PERFORMANCE|warning_number]
REM To enable all warnings in your session:
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 5005 as a "hard" compile error:
ALTER PROCEDURE my_procedure SET plsql_warnings =
'enable:06002', 'enable:performance', 'ERROR:05005';

Copyright 2011 Feuerstein and Associates

Page 23

Oracle PL/SQL Programming

Checking for Warnings


The USER_ERRORS data dictionary view shows
both "hard" errors and compilation warnings.
Use the SHOW ERRORS command in SQL*Plus.
IDEs will usually display warnings within the
edit window.
Or run your own query against USER_ERRORS.

Copyright 2011 Feuerstein and Associates

Page 24

Oracle PL/SQL Programming

Example: check for unreachable code


There may be lines of code that could never, ever
execute.
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

Copyright 2011 Feuerstein and Associates

plw6002.sql

Page 25

11g

Oracle PL/SQL Programming

New Warnings in Oracle Database 11g


PLW-6017: something's going to raise an error!
Such as VARCHAR2(1) := 'abc'....FINALLY!

PLW-6009: OTHERS exception handler does not reraise an exception.


More feedback on impact of optimization
PLW-6007: Notification that entire subprograms were
removed

PLW-7205: warning on mixed use of integer types


Namely, SIMPLE_INTEGER mixed with PLS_INTEGER and
BINARY_INTEGER

PLW-7206: unnecessary assignments


Lots of PRAGMA INLINE-related warnings
plw*.sql files
Copyright 2011 Feuerstein and Associates

Page 26

11g

Oracle PL/SQL Programming

Finally, Oracle warns me of too-large value


CREATE OR REPLACE PROCEDURE plw6017
IS
c
VARCHAR2 (1) := 'abc';
BEGIN

One big frustration I have had with compiletime warnings is that it did not flag code like
you see above. What could be more basic?
This is finally addressed sort of in
Oracle11g with the PLW-06017 warning.
PLW-06017: an operation will raise an exception

plw6017.sql
Copyright 2011 Feuerstein and Associates

Page 27

Oracle PL/SQL Programming

Treating a warning as "hard" compile error


You might identify a warning that reflects such
bad coding practices, that you want to ensure
it never makes its way into production code.
Just set the warning as an error and stop the use
of that program "in its tracks."

"Function does not return value" is a prime


example.
ALTER SESSION SET PLSQL_WARNINGS='ERROR:5005'

plw5005.sql
Copyright 2011 Feuerstein and Associates

Page 28

Oracle PL/SQL Programming

Watch out for "false negatives" and


"nuisances" warnings
The check for unreachable code is not very
useful/reliable prior to Oracle11g.
Shows as "unreachable" code that is removed by
the optimizer.

You might be overwhelmed by warnings about


which you don't really care.
Example: missing AUTHID clause

Copyright 2011 Feuerstein and Associates

Page 29

Oracle PL/SQL Programming

Conclusions - Compile-time Warnings


Review the available warnings. Identify those
which of greatest importance to you.
And with each new release of Oracle check for
additions.

Consider setting up scripts to enable different


sets of warnings to match different
development scenarios and to ignore those
"nuisance" warnings.

Copyright 2011 Feuerstein and Associates

Page 30

Oracle PL/SQL Programming

Conditional Compilation
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.

First released in Oracle Database 10g Release 2


Also implemented in later patch sets of 10gR1, plus 9iR2
(contact Oracle Support for details)
Copyright 2011 Feuerstein and Associates

Page 31

Oracle PL/SQL Programming

A finely-nuanced feature of PL/SQL


Conditional compilation affects how your code
is compiled and therefore executed.
It is not something to employ casually.
This training will serve as an introduction.

Before using conditional compilation, check out


OTN's detailed whitepaper on the topic.
100 pages covering all common use cases
See URL below or search for "conditional
compilation white paper".
http://bit.ly/eXxJ9Q

Copyright 2011 Feuerstein and Associates

Page 32

Oracle PL/SQL Programming

Three types of compiler directives


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.

Selection directives: $IF

Use the $IF directive to evaluate expressions and


determine which code should be included or avoided.
Can reference inquiry directives and package static
constants.

Error directives: $ERROR

Use the $ERROR directive to report compilation errors


based on conditions evaluated when the preprocessor
prepares your code for compilation.

Copyright 2011 Feuerstein and Associates

Page 33

Oracle PL/SQL Programming

Example: toggle inclusion of tracing


Set up conditional compilation of debugging and
tracing with special "CC" flags that are placed into
the compiler settings for a program.
Only integer and Boolean values are allowed.
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_compile_settings.sql

Copyright 2011 Feuerstein and Associates

Page 34

Oracle PL/SQL Programming

Access to post-processed code


You can display or retrieve post-processed code with the
DBMS_PREPROCESSOR package.
Oracle is careful to preserve both horizontal and vertical
whitespace so runtime stack and error information
correlates to your actual source code.
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;
/

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 2011 Feuerstein and Associates

cc_postprocessed.sql

Page 35

Oracle PL/SQL Programming

Error directive example


If my program has been compiled with
optimization level 1 (less aggressive) or 0
(disabled), then raise an error.
You can in this way add "meta-requirements" to
your code definitions.
SQL>
2
3
4
5
6
7
8
9
10

CREATE OR REPLACE PROCEDURE long_compilation


IS
BEGIN
$IF $$plsql_optimize_level < 2
$THEN
$error 'Program must be compiled with full optimization' $end
$END
NULL;
END long_compilation;
/
cc_opt_level_check.sql

Copyright 2011 Feuerstein and Associates

Page 36

Oracle PL/SQL Programming

Using DBMS_DB_VERSION
This package, present in any Oracle Database
version supporting conditional compilation, contains
a set of 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_10_1
$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

Copyright 2011 Feuerstein and Associates

cc_bf_or_number.sql
cc_version_check.sql

Page 37

Oracle PL/SQL Programming

Conclusions Conditional Compilation


Conditional compilation is a very powerful and
useful feature.
It is not terribly difficult to learn, but it is hard
for developers to be confident of working with
and maintaining code that contains "$" syntax
elements.
Most important: keep it in mind when you
encounter a clear and compelling use case,
such as writing code that must run under
multiple versions of Oracle.
Copyright 2011 Feuerstein and Associates

Page 38

Oracle PL/SQL Programming

Compiler Improvements - Summary


Optimizer
Go with the default and enjoy the performance!

Compile-time warnings
Try them out, see how much value you can extract
from it.

Conditional compilation
Lots of potential, mainly for use into the future
Smart tool support needed to make it feasible and
maintainable (one's code becomes very hard to
read)
Copyright 2011 Feuerstein and Associates

Page 39

Oracle PL/SQL Programming

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 multilevel collections can be a challenge.
Copyright 2011 Feuerstein and Associates

Page 40

Oracle PL/SQL Programming

Agenda - Collections

Introduction and overview


Defining and using collection types
Using collection methods
Working with associative arrays
Working with nested tables
Working with varrays
Using collections inside SQL
Benefits of Non-Sequential Indexing
Using String Indexes with Associative Arrays
Working with Nested Collections
Using MULTISET Operators with Nested Tables
Best Practices for Collections

Copyright 2011 Feuerstein and Associates

Page 41

Oracle PL/SQL Programming

What is a collection?
A collection is an "ordered
group of elements, all of the
same type." (PL/SQL User
Guide)
In short, a list of "stuff"

Collections are similar to singledimensional arrays in other


programming languages.

Apple

22

Pear

100

Orange

10023

Apricot

With lots of subtle differences, as


well.

Copyright 2011 Feuerstein and Associates

Page 42

Oracle PL/SQL Programming

Why use collections?


Generally, to manipulate lists of information.
Of course, you can use relational tables, too.
Collection manipulation is generally much faster
than using SQL to modify the contents of tables.

Collections enable other key features of PL/SQL


BULK COLLECT and FORALL use them to boost
multi-row SQL performance
Serve up complex datasets of information to nonPL/SQL host environments using table functions.
Copyright 2011 Feuerstein and Associates

Page 43

Oracle PL/SQL Programming

What makes collections so fast?


You can read and write data in a collection
much, much more quickly than in a relational
table. Why is that?
There are two general types of memory for
Oracle data:
System or instance-level memory (SGA)
Process or session-specific memory (PGA)

global_temp_tab_vs_coll.sql

Copyright 2011 Feuerstein and Associates

Page 44

Oracle PL/SQL Programming

Memory Management and Collections


Memory for collections (and almost all PL/SQL
data structures) is allocated from the PGA
(Process Global Area).
Accessing PGA memory is quicker than
accessing SGA memory.
Sometimes much, much faster.

Collections represent a very clear tradeoff: use


more memory (per session) to improve
performance.
But you definitely need to keep an eye on your
PGA memory consumption.
plsql_memory*.*
Copyright 2011 Feuerstein and Associates

Page 45

Oracle PL/SQL Programming

Different Types of Collections


Three types of collections
Associative array
Nested table
Varray (varying arrays)

Associative array is a PL/SQL-only datatype.


Nested tables and varrays can be used within
PL/SQL blocks and also from within the SQL
layer.

Copyright 2011 Feuerstein and Associates

Page 46

Oracle PL/SQL Programming

Glossary of Terms
Element
A collection is made up of one or more elements, all of the same
type. Also referred to as "row."
Can be of almost any valid PL/SQL type.

Index value
The "location" in the collection in which an element is found. Also
referred to as "row number."
Usually an integer, can also be a string (associative arrays only)

Dense
Every index value between lowest and highest has a defined
element.

Sparse
One or more elements between lowest and highest index values
may be undefined (gaps).
Copyright 2011 Feuerstein and Associates

Page 47

Oracle PL/SQL Programming

Defining collection types


Before you can manipulate a collection variable,
you need a collection type on which to declare
the variable.
Oracle pre-defines several collection types in
various supplied packages.
DBMS_SQL
Dynamic SQL-specific types
Generic types (list of strings, numbers, etc.).

DBMS_OUTPUT
List of strings
$RDBMS_HOME\RDBMS\Admin\dbmssql.sql
Copyright 2011 Feuerstein and Associates

Page 48

Oracle PL/SQL Programming

Defining collection types


Declare all collection type with a TYPE statement.
Associative arrays use IS TABLE OF and the INDEX BY
clause.
Nested tables use IS TABLE OF, without any indexing
clause.
Varrays use IS VARRAY OF syntax.
Association array type
TYPE coll_name IS TABLE OF element_type INDEX BY index_type;

Nested table type


TYPE coll_name IS TABLE OF element_type;

Varray type
TYPE coll_name IS VARRAY (limit) OF element_type;

Copyright 2011 Feuerstein and Associates

Page 49

Oracle PL/SQL Programming

Scope for Collection Types


You can define collection types in:
Local block can be used only in that block
Package - available for use by any session with
execute authority on that package
Schema in the SQL layer, possible only for nested
tables and varrays

Avoid "reinventing" collection types in many


places in your code.
They are excellent candidates for shared code
elements.
Copyright 2011 Feuerstein and Associates

Page 50

Oracle PL/SQL Programming

Local collection types


DECLARE
TYPE strings_t IS TABLE OF VARCHAR2(100);

PROCEDURE my_procedure
IS
TYPE strings_t IS TABLE OF VARCHAR2(100);

TYPE statement found in declaration section


of block (anonymous, nested, subprogram)
It is defined and then destroyed each time the
block is executed.
You should avoid local types; it will likely lead
to redundancies in your code.
Copyright 2011 Feuerstein and Associates

Page 51

Oracle PL/SQL Programming

Package-level Types
PACKAGE my_types
IS
TYPE strings_t IS TABLE OF VARCHAR2(100);

When defined in the package specification, it


becomes a "globally" available type.
Any session with EXECUTE authority on the package
can use the type to declare collections.

In the package body, can only be used by


subprograms of that package.
Excellent repository for application-specific types
colltypes.pks
Copyright 2011 Feuerstein and Associates

Page 52

Oracle PL/SQL Programming

Schema-level Types
CREATE OR REPLACE TYPE strings_t IS TABLE OF VARCHAR2(100)
GRANT EXECUTE ON strings_t TO PUBLIC

Defined in the schema, independent of any


PL/SQL program unit.
Any session with EXECUTE authority on type can
use it to declare collection variables.
Collections of this type can also be directly referenced
inside SQL statements.

Can only be used with nested tables and varrays.


Associative arrays are PL/SQL-specific, cannot be
defined at the schema level (SQL layer).
Copyright 2011 Feuerstein and Associates

Page 53

Oracle PL/SQL Programming

Declaring collection variables


Once you have defined your collection type,
you can define a variable based on that.
The same as for any type of data in PL/SQL
CREATE TYPE hire_dates_t IS TABLE OF DATE;
CREATE PACKAGE my_types IS
TYPE strings_t IS TABLE OF VARCHAR2(100);
END my_types;

DECLARE
l_names my_types.string_t;
l_dates hire_dates_t;
l_dates HR.hire_dates_t;
l_strings DBMS_SQL.varchar2_table;
Copyright 2011 Feuerstein and Associates

Page 54

Oracle PL/SQL Programming

Collection Methods
The term method is used to describe
procedures and functions that defined in a class
or object type.
You invoke a method by attaching it, using dot
notation, to the name of the type/class or to an
instance of the class.

Collection methods are procedures and


functions that are attached to a collection
variable.
First introduction of object-oriented syntax in
PL/SQL way back in Oracle 7.3.4!
Copyright 2011 Feuerstein and Associates

method_vs_proc.sql

Page 55

Oracle PL/SQL Programming

Methods that retrieve information


COUNT: number of elements currently defined
in collection.
EXISTS: TRUE if the specified index values is
defined.
FIRST/LAST: lowest/highest index values of
defined rows.
NEXT/PRIOR: defined index value after/before
the specified index value .
LIMIT: max. number of elements allowed in a
VARRAY.

Copyright 2011 Feuerstein and Associates

Page 56

Oracle PL/SQL Programming

The COUNT Method


Returns 0 if the collection is empty.
Otherwise returns the number of defined
index values.
You cannot ask for a count of elements
between a range of index values. Too bad...
BEGIN
IF my_collection.COUNT > 0
THEN
/* We have some data in the collection */
...
END IF;

Copyright 2011 Feuerstein and Associates

Page 57

Oracle PL/SQL Programming

Checking for element existence


If you try to read an element at an undefined
index value, Oracle raises the
NO_DATA_FOUND exception.
A poor decision on Oracle's part.

Use the EXISTS method to check to see if the


index value is defined.
BEGIN
IF my_collection.EXISTS (l_index)
THEN
DBMS_OUTPUT.PUT_LINE (my_collection (l_index));
END IF;

Copyright 2011 Feuerstein and Associates

collection_exists.sql
plsqlloops.sp

Page 58

Oracle PL/SQL Programming

Navigating Through Collections


One of the most common actions on collections
is looping through the contents.
You can use WHILE, simple and FOR loops to
perform this navigation.
The characteristics of your collection will
determine which sort of loop to use.

Copyright 2011 Feuerstein and Associates

Page 59

Oracle PL/SQL Programming

Choose the Right Loop


FOR loop
Only use this loop when you want to iterate
through every element between the low and high
index values.
Do not use with sparse collections.

WHILE and simple loops


Best fit for sparse collections and when you want
to conditionally exit from your loop.

Copyright 2011 Feuerstein and Associates

Page 60

Oracle PL/SQL Programming

Navigation methods and FOR loops


With FOR loops, you will use:
COUNT when the first index value in the
collection is 1.
FIRST and LAST when FIRST may not be one.
BEGIN
FOR indx IN 1 .. my_collection.COUNT
LOOP
do_something_with (my_collection (indx));
END LOOP;
END;

BEGIN
FOR indx IN my_collection.FIRST .. my_collection.LAST
LOOP
do_something_with (my_collection (indx));
END LOOP;
END;

plsqlloops.sp
Copyright 2011 Feuerstein and Associates

Page 61

Oracle PL/SQL Programming

Navigation methods and WHILE/simple


loops
With WHILE and simple loops, you will use
FIRST and NEXT to move from first to last
LAST and PRIOR to move from last to first
rowind PLS_INTEGER := my_collection.FIRST;
BEGIN
LOOP
EXIT WHEN rowind IS NULL;
rowind := my_collection.NEXT (rowind);
END LOOP;
END;
rowind PLS_INTEGER := my_collection.LAST;
BEGIN
LOOP
EXIT WHEN rowind IS NULL;
rowind := my_collection.PRIOR (rowind);
END LOOP;
END;

plsqlloops.sp
Copyright 2011 Feuerstein and Associates

Page 62

Oracle PL/SQL Programming

The LIMIT Method


Only the varray has a pre-defined upper limit
on the number of elements that can defined
in it.
Well, the other collections types theoretically have
an upper limit, but you'll never reach it.

Use the LIMIT method to determine what that


limit is.
DECLARE
TYPE max_of_five_t IS VARRAY (5) OF NUMBER;
l_list
max_of_five_t := max_of_five_t();
BEGIN
DBMS_OUTPUT.put_line (l_list.LIMIT);
END;

Copyright 2011 Feuerstein and Associates

varray_limit.sql

Page 63

Oracle PL/SQL Programming

Methods that change a collection


DELETE deletes one or more rows from an
associative array or nested table.
EXTEND adds rows to the end of a nested
table or varray.
TRIM removes rows from a varray or nested
table.

Copyright 2011 Feuerstein and Associates

Page 64

Oracle PL/SQL Programming

The DELETE Method


You can delete one or more rows from an associative
array or nested table using DELETE.
Try to DELETE from a varray and you will see "PLS00306: wrong number or types of arguments in call
to 'DELETE'"
Low and high index values do not have to exist.
BEGIN
-- Delete all rows
myCollection.DELETE;
-- Delete one (the last) row
myCollection.DELETE (myCollection.LAST);
-- Delete a range of rows
myCollection.DELETE (1400, 17255);
END;
Copyright 2011 Feuerstein and Associates

delete.sql

Page 65

Oracle PL/SQL Programming

Make room for new elements w/ EXTEND


Use EXTEND only with varrays and nested
tables.
Tell Oracle to add N number of new elements
to the end of the collection.
"Bulk" extends faster than individual extends.
If you know you will need 10,000 elements, do the
extend in a single step.

Optional: specify the value of all new


elements from an existing element.
Default value is NULL.
Copyright 2011 Feuerstein and Associates

extend.sql

Page 66

Oracle PL/SQL Programming

Trimming elements from end of collection


Use TRIM only with varrays and nested tables.
Not to be confused with the TRIM function!

You can trim one or multiple elements.


Default is 1.
"ORA-06533: Subscript beyond count" error if you
to trim more than is in the collection.

TRIM is the only way to remove elements from


a varray.
DELETE is not supported.
trim.sql

Copyright 2011 Feuerstein and Associates

Page 67

Oracle PL/SQL Programming

Conclusions Collection Methods


Methods make it much easier to work with
collections.
You can get information about the collections
and also change their contents.
When you use the navigation methods, make
sure you choose the appropriate type of loop
to iterate through the elements.

Copyright 2011 Feuerstein and Associates

Page 68

Oracle PL/SQL Programming

Associative Arrays
DECLARE
TYPE list_of_names_t IS TABLE OF employees.last_name%TYPE
INDEX BY PLS_INTEGER;

A variable declared from an


associative array type.
From the PL/SQL User Guide:
"An unbounded set of key-value pairs.
"Each key is unique, and serves as the
subscript of the element that holds
the corresponding value.
"You can access elements without
knowing their positions in the array,
and without traversing the array."
Copyright 2011 Feuerstein and Associates

Apple

22

Pear

100

Orange

10023

Apricot

Page 69

Oracle PL/SQL Programming

Associative Array Background


It was the first type of collection available in
PL/SQL.
First introduced in Oracle7 as a "PL/SQL table"
(hence, the TABLE OF syntax).
Renamed in Oracle8 to "index-by table" when
nested tables and varrays were added.
In 9.2, renamed to "associative array" with the
advent of string indexing.
Can only be used in PL/SQL blocks.
Copyright 2011 Feuerstein and Associates

Page 70

Oracle PL/SQL Programming

Characteristics of Associative Arrays


DECLARE
TYPE list_of_names_t IS TABLE OF employees.last_name%TYPE
INDEX BY PLS_INTEGER;

TABLE OF datatypes can be almost any valid PL/SQL


type (details to follow).
INDEX BY type can be integer or string.
This means you can essentially index by anything!
But index values can never be NULL.

Associative arrays can be sparse.


Can populate elements in non-consecutive index values.
Easily used to emulate primary keys and unique indexes.

Copyright 2011 Feuerstein and Associates

Page 71

Oracle PL/SQL Programming

Simple associative array example


DECLARE
TYPE list_of_names_t IS TABLE OF VARCHAR2 (20)
INDEX BY PLS_INTEGER;
happyfamily
list_of_names_t;
l_index_value PLS_INTEGER := 88;
BEGIN
happyfamily (1) := 'Eli';
happyfamily (-15070) := 'Steven';
happyfamily (3) := 'Chris';
happyfamily (l_index_value) := 'Veva';
l_index_value := happyfamily.FIRST;
WHILE (l_index_value IS NOT NULL)
LOOP

DBMS_OUTPUT.put_line (
'Value at index '
|| l_index_value
|| ' = '
|| happyfamily (l_index_value)
);
l_index_value := happyfamily.NEXT (l_index_value);
END LOOP;
END;
assoc_array_example.sql
Copyright 2011 Feuerstein and Associates

Page 72

Oracle PL/SQL Programming

Associative array of records example


It is very easy to "emulate" a relational table
inside one's PL/SQL code.
Or use any other kind of record type.
DECLARE
TYPE employees_aat IS TABLE OF employees%ROWTYPE
INDEX BY PLS_INTEGER;
l_employees employees_aat;
BEGIN
FOR employee_rec IN (SELECT * FROM employees)
LOOP
l_employees (l_employees.COUNT + 1) := employee_rec;
END LOOP;
END;

collection_of_records.sql
Copyright 2011 Feuerstein and Associates

Page 73

Oracle PL/SQL Programming

Valid TABLE OF datatypes


You can create an associative array of almost
any PL/SQL or SQL datatype.
All scalar types, including Boolean
Collection of object types
Collection of other collections

There are some restrictions:


Cannot have a TABLE OF cursor variables or
exceptions.
aa_table_of_invalid_types.sql

Copyright 2011 Feuerstein and Associates

Page 74

Oracle PL/SQL Programming

Valid Index Values


No practical limit to number of elements you can define
in an associative array.

Integer index values range from


-2,147,483,647 to 2,147,483,647
This is the BINARY_INTEGER range.
That's almost 4.3 billion elements!

String index values can have any value; you are


only restricted by maximum number of elements
allowed in a collection.
Which you will never reach.

Copyright 2011 Feuerstein and Associates

aa_limits.sql

Page 75

Oracle PL/SQL Programming

More details on valid INDEX BY types


The INDEX BY clause defines the indexing for
the collection.
You can define the index datatype of your associative
array type to be:
BINARY_INTEGER and 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.
indexby_options.sql
Copyright 2011 Feuerstein and Associates

Page 76

Oracle PL/SQL Programming

Nested Tables and Varrays


Added in Oracle8 as part of the object model.
Types can be defined in PL/SQL or a schemalevel type (for use in SQL).
You must initialize before using the collection.
You must extend to make room for new
elements.

Copyright 2011 Feuerstein and Associates

Page 77

Oracle PL/SQL Programming

Nested Tables
CREATE OR REPLACE TYPE list_of_names_t IS TABLE OF NUMBER;

A nested table is a type of collection,


which, according to Oracle documentation,
"models an unordered set of elements."
It is a "multiset": like a relational table, there is
no inherent order to its elements, and
duplicates are allowed/significant.

From a practical standpoint, you can


access nested table elements through an
integer index.
MULTISET operators allow set-level
operations on nested tables.
Copyright 2011 Feuerstein and Associates

nested_table_example.sql

Unordered set
of elements

Apple

Pear

Orange

Apricot

Pear

Integer index
also available
Page 78

Oracle PL/SQL Programming

Varrays
CREATE OR REPLACE TYPE list_of_names_t IS VARRAY (5) OF NUMBER;

A varray (short for "variable size array)


is a type of collection that has an upper
bound on its number of elements.
This upper limit is set when the type is
defined, but can also be adjusted at
runtime.
Always dense, can only trim from end
of varray.
Other than that, quite similar to a
nested table.
Copyright 2011 Feuerstein and Associates

varray_example.sql

Apple

Pear

Orange

Apricot

Pear

And no more
elements can fit in
this varray.

Page 79

Oracle PL/SQL Programming

Initializing Nested Tables and Varrays


Before you can use a nested table, it must be
initialized.

Initialize them explicitly with a constructor function, same


name as type, provided by Oracle
No need to initialize if populated by BULK COLLECT query.

Provide a list of values or initialize it as empty.


DECLARE
TYPE numbers_t IS TABLE OF NUMBER;
salaries numbers_t := numbers_t ();

Initialize empty
in declaration

DECLARE
TYPE numbers_t IS VARRAY (5) OF NUMBER;
salaries numbers_t := numbers_t (100, 200, 300);
BEGIN

Initialize in
declaration with
values

DECLARE
TYPE numbers_t IS TABLE OF NUMBER;
salaries numbers_t;
BEGIN
salaries := numbers_t (100, 200, 300);
Copyright 2011 Feuerstein and Associates

Initialize in
execution
section
Page 80

Oracle PL/SQL Programming

Valid TABLE OF and VARRAY datatypes


For PL/SQL-defined types, you can create
nested table of almost any PL/SQL or SQL
datatype.
All scalar types, including Boolean; collection of
object types; collection of other collections

There are some restrictions:


Cannot have a TABLE OF or VARRAY OF cursor
variables or exceptions.

Schema-level types can only use SQL


datatypes.
Copyright 2011 Feuerstein and Associates

nt_table_of_invalid_types.sql
va_table_of_invalid_types.sql

Page 81

Oracle PL/SQL Programming

Collection as Column Type


If the type is defined at schema level, it can be used as
a column type.
Must also provide a STORE AS clause.
The order of elements in the column is not preserved.
Can specify storage characteristics of collection.
See separate lesson for details on using collections in
SQL.
CREATE TABLE family
(
surname
VARCHAR2 (1000)
, parent_names
parent_names_t
, children_names
child_names_t
)
NESTED TABLE children_names
STORE AS parent_names_tbl [storage_clause]
NESTED TABLE parent_names
STORE AS children_names_tbl [storage_clause]
Copyright 2011 Feuerstein and Associates

nested_table_example.sql
varray_example.sql

Page 82

Oracle PL/SQL Programming

Changing Upper Limit on Varray


You specify an upper limit at the time the
varray type is define.
You can also change this limit at runtime with
an ALTER TYPE command.
ALTER TYPE my_varray_t MODIFY LIMIT 100 INVALIDATE
/
BEGIN
EXECUTE IMMEDIATE
'ALTER TYPE my_varray_t MODIFY LIMIT 100 CASCADE';
END;
/

varray_change_limit.sql
Copyright 2011 Feuerstein and Associates

Page 83

Oracle PL/SQL Programming

Using Collections Inside SQL


When you define your collection type at the schema
level, collections declared with that type can be
referenced in the SQL layer.
As a column in a relational table
By selecting from that collection in a SELECT statement.

This feature only applies to nested tables and


varrays.
Associative arrays are PL/SQL-only structures.

Oracle offers ways to "translate" between a


collection format and a relational table format.
TABLE: collection -> relational table
MULTISET: relational table -> collection
Copyright 2011 Feuerstein and Associates

Page 84

Oracle PL/SQL Programming

Using the TABLE Operator


Use TABLE to work with data in a collection
as if it were data in a database table.
Oracle refers to this as "un-nesting".
Especially useful when you would like to apply
SQL operations to a PL/SQL collection (ie, one
not stored in a database table).

You do not need to explicitly CAST the


collection.
Oracle will figure out the type automatically.
collections_in_sql.sql

Copyright 2011 Feuerstein and Associates

Page 85

Oracle PL/SQL Programming

Changing collection contents with TABLE


You can use TABLE to query the contents of a
collection inside SQL.
You can also change the contents of a nested
table column value with TABLE.
But varrays have to be changed "en masse" the
while varray is replace; cannot modify individual
elements.
UPDATE TABLE (SELECT children_names
FROM family
WHERE surname = 'Feuerstein')
SET COLUMN_VALUE = 'Eli Silva'
WHERE COLUMN_VALUE = 'Eli'
/
Copyright 2011 Feuerstein and Associates

nested_table_change.sql

Page 86

Oracle PL/SQL Programming

Using the MULTISET Operator


MULTISET is the inverse of TABLE, converting a set of
table, view, query) into a VARRAY or nested table.
Use MULTISET to emulate or transform relational joins into
collections, with potential client-server performance impact.
DECLARE
CURSOR bird_curs IS
SELECT b.genus, b.species,
CAST ( MULTISET (SELECT bh.country FROM bird_habitats bh
WHERE bh.genus = b.genus
AND bh.species = b.species)
AS country_tab_t)
FROM birds b;
bird_row bird_curs%ROWTYPE;
BEGIN
OPEN bird_curs;
FETCH bird_curs into bird_row;
END;

Copyright 2011 Feuerstein and Associates

collections_in_sql_multiset.sql

Page 87

Oracle PL/SQL Programming

Non-Sequential Indexing
Sometimes you simply want to add items to the
end of a list.
This makes sense if the order in which items were
added is significant.

But how do you find a specific element in the list?


With sequential indexing, you have to scan through
the contents to find a match.

And what if you want to find elements in a


collection using more than one "index"?
Collections have just one index. No way around that.
Copyright 2011 Feuerstein and Associates

string_tracker0.*

Page 88

Oracle PL/SQL Programming

Taking advantage of non-sequential indexing


Associative arrays can be sparse.
Certainly, any string-indexed collection is not
sequentially filled.

Valid index values for an associative array


cover a very wide range of integers.
Very often primary keys of tables are sequencegenerated integers that fall within this range.

Combine these two features and you have a


powerful and relatively simple mechanism for
emulating relational table keys.
collection_of_records.sql
Copyright 2011 Feuerstein and Associates

Page 89

Oracle PL/SQL Programming

Emulating Primary Key in Collection


Many tables rely on sequence-generated integer
values for their primary keys.
It is possible that this sequence value could exceed
2**31-1, but it is rarely the case.

Primary keys generally are not "densely"


allocated.
Sequences are allocated in groups, rows are deleted.

These scenarios mesh perfectly with the features


of an integer-indexed associative array.
emulate_primary_key1.sql
emulate_primary_key2.sql
Copyright 2011 Feuerstein and Associates

Page 90

Oracle PL/SQL Programming

"Multiple Indexes" on a Collection


Most relational tables have multiple indexes
defined in order to optimize query
performance (for various WHERE clauses).
What if I need to do the same thing in a
collection?
You can only have a single index on an
associative array (INDEX BY...).
But you could create other collections that serve
as indexes into the "original" collection.
emulate_indexes.sql
genaa.sql
Copyright 2011 Feuerstein and Associates

Page 91

Oracle PL/SQL Programming

Expanded indexing capabilities for


associative arrays
Prior to Oracle9i Release 2, you could only index by
BINARY_INTEGER.
You can now define the index on your associative
array to be:
Any sub-type derived from BINARY_INTEGER
VARCHAR2(n), where n is between 1 and 32767
%TYPE against a database column that is consistent with
the above rules
A SUBTYPE against any of the above.

This means that you can now index on string values!


(and concatenated indexes and...)
Copyright 2011 Feuerstein and Associates

Page 92

Oracle PL/SQL Programming

Examples of New TYPE Variants


All of the following are valid TYPE declarations in
Oracle9i Release 2 and higher
You cannot use %TYPE against an INTEGER column, because
INTEGER is not a subtype of BINARY_INTEGER.
DECLARE
TYPE
TYPE
TYPE
TYPE
TYPE
TYPE
TYPE

array_t1
array_t2
array_t3
array_t4
array_t5
array_t6
array_t7

INDEX BY BINARY_INTEGER;
INDEX BY PLS_INTEGER;
INDEX BY POSITIVE;
INDEX BY NATURAL;
INDEX BY VARCHAR2(64);
INDEX BY VARCHAR2(32767);
INDEX BY
employee.last_name%TYPE;
TYPE array_t8 IS TABLE OF NUMBER INDEX BY
types_pkg.subtype_t;

Copyright 2011 Feuerstein and Associates

IS
IS
IS
IS
IS
IS
IS

TABLE
TABLE
TABLE
TABLE
TABLE
TABLE
TABLE

OF
OF
OF
OF
OF
OF
OF

NUMBER
NUMBER
NUMBER
NUMBER
NUMBER
NUMBER
NUMBER

Page 93

Oracle PL/SQL Programming

Working with string-indexed collections


The syntax for using string indexing is the
same.
And all the same methods are available.

But the type of data returned by FIRST, LAST,


NEXT and PRIOR methods is VARCHAR2.
The longer the string values, the more time it takes
Oracle to "hash" or convert that string to the integer
that is actually used as the index value.
Relatively small strings, say under 100 characters, do not
incur too large a penalty.

Copyright 2011 Feuerstein and Associates

assoc_array*.sql
assoc_array_perf.tst

Page 94

Oracle PL/SQL Programming

Practical application for string indexing


I need to keep track of names used in my program.
Specifically, in Quest Code Tester, we generate test code
and declare variables. So I need to make sure that I do not
declare the same variable more than once.

There are lots of ways to do this, but string-indexed


collections make it really easy!
FOR indx IN 1 .. l_variables.COUNT
LOOP
If varname_already_used
THEN
-- DO NOTHING
ELSE

add_variable_declaration;
mark_varname_as_used;
END IF;
END LOOP;
Copyright 2011 Feuerstein and Associates

string_tracker0.*

Page 95

Oracle PL/SQL Programming

String Tracker without string indexing


Two subprograms are needed....
string_in_use: returns TRUE if the string was
previously used.
mark_as_used: mark the specified string as being
used.

Most "obvious" implementation: add each


string to a list of used strings.
Then search through the list for a match.
string_tracker0.*
Copyright 2011 Feuerstein and Associates

Page 96

Oracle PL/SQL Programming

String Tracker with string indexing


Rather than add each string to a list of used
strings, why not use the string as the index?
CREATE OR REPLACE PACKAGE BODY string_tracker
IS
TYPE used_aat IS TABLE OF BOOLEAN INDEX BY VARCHAR2(32767);
g_names_used used_aat;
FUNCTION string_in_use ( value_in IN VARCHAR2 )
RETURN BOOLEAN
IS BEGIN
RETURN g_names_used.EXISTS ( value_in );
END string_in_use;
PROCEDURE mark_as_used (value_in IN VARCHAR2) IS
BEGIN
g_names_used ( value_in ) := TRUE;
END mark_as_used;
END string_tracker;

Copyright 2011 Feuerstein and Associates

string_tracker1.*

Page 97

Oracle PL/SQL Programming

Converting from Integer to String Indexing


Suppose I am emulating my primary key in a
collection index for a batch job.
Much better performance!
But my primary key values are approaching
2**31-1 (the maximum allowed in an collection).

Must I abandon the collection technique?


No! You can convert to a string index.
Now the only limitation is the number of elements
defined in the collection.
You are much more likely to run out of memory
before you get anywhere near 2**31-1 elements.
Copyright 2011 Feuerstein and Associates

emulate_primary_key.sql
int_to_string_indexing.sql

Page 98

Oracle PL/SQL Programming

Multilevel (a.k.a., Nested) Collections


A multilevel collection type is a type whose
element is, directly or indirectly, another
collection.
Usages for multilevel collections:
Model normalized data structures in PL/SQL
collections
Emulate multidimensional arrays.

The syntax for working with multilevel


collections can be hard to parse (in your
head).
multilevel_collections.sql
Copyright 2011 Feuerstein and Associates

Page 99

Oracle PL/SQL Programming

String Tracker Version 2


I introduced the string_tracker package in
"Working with String-Indexed Collections."
Keeps track of the names of variables already
generated in test code.
That worked fine for a single list. What if I need to
keep track of multiple lists, and lists within lists?

Let's extend the first version to support


multiple lists by using a string-indexed, multilevel collection. A list of lists....
The hard way: segmenting one big collection
List 1: 1-10000

List 2: 10001-20000

Copyright 2011 Feuerstein and Associates

List 2: 20001-30000

string_tracker1.*
string_tracker2.*
string_tracker3*.*
Page 100

Oracle PL/SQL Programming

Emulate multidimensional arrays


Collections are always single dimensioned.
But a collection of collections is "kinda like" a
two-dimensional array.
You can extrapolate from there.
CREATE OR REPLACE PACKAGE multdim
IS
TYPE dim1_t IS TABLE OF VARCHAR2 (32767)
INDEX BY PLS_INTEGER;
TYPE dim2_t IS TABLE OF dim1_t
INDEX BY PLS_INTEGER;
TYPE dim3_t IS TABLE OF dim2_t
INDEX BY PLS_INTEGER;

multdim*.*
Copyright 2011 Feuerstein and Associates

Page 101

Oracle PL/SQL Programming

Complex example: four-levels of nesting


The most complicated structure I ever built
was a four-level nested collection structure.
I used it to build a utility to analyze packages
for potentially ambiguous overloading.
The next several slides explore the
implementation.
It is too complex to fully explain in this lesson.
Visit stevenfeuerstein.com/read/read-steven to
check out an 8-part article series I wrote to
explain it.
Copyright 2011 Feuerstein and Associates

Page 102

Oracle PL/SQL Programming

Multilevel collections as a kind of


normalized database design
I have found that coming up with the right
model of multilevel collections is very similar
to normalizing data in relational tables.
Avoid redundancy
Accurately reflect relationships
Simply resulting application code

Let's take a look at an example of such a


process.
Copyright 2011 Feuerstein and Associates

Page 103

Oracle PL/SQL Programming

The problem of ambiguous overloading


Oddly and sadly, it is possible to compile
overloadings which are not usable.
You see an obvious example below, but there are many
more subtle circumstances, usually involving defaulted
parameters.

So I will build a utility to identify such ambiguous


overloadings. But how can I do this?
PACKAGE salespkg
IS
PROCEDURE calc_total (
dept_in IN VARCHAR2);
PROCEDURE calc_total (
dept_in IN CHAR);
END salespkg;

BEGIN
salespkg.calc_total ('ABC');
END;

ambig_overloading.sql
Copyright 2011 Feuerstein and Associates

Page 104

Oracle PL/SQL Programming

ALL_ARGUMENTS to the rescue!


Parsing is too complicated for me, but the
ALL_ARGUMENTS data dictionary view contains
information about all the arguments of all the
procedures and functions to which I have access.
That sounds pretty good!

As usual, Oracle offers us a whole lot of pleasure,


mixed with a little bit of pain.
The organization of data in ALL_ARGUMENTS is a bit
bizarre, plus it is incomplete, necessitating the use also of
DBMS_DESCRIBE.DESCRIBE_COLUMNS.

all_arguments.tst
all_arguments.sql
allargs.*
Copyright 2011 Feuerstein and Associates

Page 105

Oracle PL/SQL Programming

First Inclination: Same Old, Same Old


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

Copyright 2011 Feuerstein and Associates

Page 106

Oracle PL/SQL Programming

Then what? Write lots of code to interpret


the contents...
Which programs are overloaded? Where does
one overloading end and another start?
l_last_program
all_arguments.object_name%TYPE;
l_is_new_program
BOOLEAN
:= FALSE;
l_last_overload
PLS_INTEGER := -1;
IF
l_arguments (indx).overload
BEGIN
!= l_last_overload
FOR indx IN l_arguments.FIRST ..
OR l_last_overload = -1
l_arguments.LAST
THEN
LOOP
IF l_is_new_program
IF
l_arguments (indx).object_name !=
THEN
l_last_program
do_first_overloading_stuff;
OR l_last_program IS NULL
ELSE
THEN
do_new_overloading_stuff;
l_last_program :=
END IF;
l_arguments (indx).object_name;
END IF;
l_is_new_program := TRUE;
END LOOP;
do_new_program_stuff;
END;
END IF;
...

Copyright 2011 Feuerstein and Associates

Page 107

Oracle PL/SQL Programming

Discovery: there is a hierarchy within


the ALL_ARGUMENTS
data!
Program name
RUN_TEST
SHOW_RESULTS
RESET_FLAGS

Overloading
Overloading 1

Breakout

Overloading 2

Each program has zero or


more overloadings, each
overloading has N
arguments, and each
argument can have
multiple "breakouts" (my
term - applies to nonscalar parameters, such as
records or object types).
Copyright 2011 Feuerstein and Associates

Argument

Breakout 1

Argument 1
Argument 2
Argument 3
Argument 4
Argument 5

Breakout 1
Breakout 2
Breakout 3

Page 108

Oracle PL/SQL Programming

What if I reflect this hierarchy in


a multilevel collection?
Have to build from the bottom up:
1. Set of rows from
ALL_ARGUMENTS

TYPE breakouts_t IS TABLE OF all_arguments%ROWTYPE


INDEX BY PLS_INTEGER;

2. All the "breakout" info


for a single argument

TYPE arguments_t IS TABLE OF breakouts_t


INDEX BY PLS_INTEGER;

3. All the argument info


for a single overloading

TYPE overloadings_t IS TABLE OF arguments_t


INDEX BY PLS_INTEGER;

4. All the overloadings for


a distinct program name

TYPE programs_t IS TABLE OF overloadings_t


INDEX BY all_arguments.object_name%type;

String-based index
Copyright 2011 Feuerstein and Associates

Page 109

Oracle PL/SQL Programming

Then I can populate it very easily


Assigning a single record to the "lowest" level also
defines each of the upper levels.
Notice the automatic "SELECT DISTINCT" on program
name that results!
FOR rec IN (SELECT * FROM all_arguments)
LOOP
l_arguments (NVL (l_arguments.LAST, 0) + 1)
:= rec;
l_programs
(rec.object_name)
(NVL (rec.overload, 0))
(rec.position)
(rec.data_level) := rec;
END LOOP;

Copyright 2011 Feuerstein and Associates

show_all_arguments.sp
show_all_arguments.tst
cc_smartargs.pkb/load_arguments

I can still do the


typical sequential
load.

But I will now also


add the multi-level
load in single
assignment

Page 110

Oracle PL/SQL Programming

And then I can "query" the contents


with a minimum of code
Is the TOP_SALES
program overloaded?

Is the 2nd
overloading of
TOP_SALES a
function?

What is the datatype


of the RETURN
clause of the 2nd
overloading of
TOP_SALES?

l_programs ('TOP_SALES').COUNT > 1

l_programs ('TOP_SALES') (2).EXISTS (0)

l_programs ('TOP_SALES') (2)(0)(0).datatype

And, of course, I know the beginning and end points of


each program, overloading, and argument. I just use the
FIRST and LAST methods on those collections!
Copyright 2011 Feuerstein and Associates

Page 111

Oracle PL/SQL Programming

Conclusions Nested Collections


The nested collection is a powerful, but
potentially very complicated, feature of PL/SQL.
Used correctly, it can hide complexity in the
underlying data structure, and greatly simplify
your algorithms.
If you find yourself saying "It shouldn't be this
hard," take a look at how you are using your
collections (or SQL).
Perhaps multilevel collections can come to the rescue!

Copyright 2011 Feuerstein and Associates

Page 112

Oracle PL/SQL Programming

Manipulating Nested Tables as Multisets


Nested tables are, from a theoretical standpoint,
"multisets."
There is no inherent order to the elements.
Duplicates are allowed and are significant.
Relational tables are multisets as well.

If a set has no order, then it has no index, so it


must be manipulated as a set.
In Oracle Database 10g, Oracle added MULTISET
set operators to manipulate the contents of
nested tables (only).
Use in both PL/SQL blocks and SQL statements.
Copyright 2011 Feuerstein and Associates

Page 113

Oracle PL/SQL Programming

Set-Oriented Features for Nested Tables


Determine if...

two nested tables are equal/unequal


a nested table has duplicates, and remove duplicates
one nested table contains another
an element is a member of a nested table

Perform set operations.


Join contents of two nested tables: MULTISET UNION.
Return common elements of two nested tables with
MULTISET INTERSECT.
Take away the elements of one nested table from
another with MULTISET EXCEPT (oddly, not MINUS).
Copyright 2011 Feuerstein and Associates

Page 114

Oracle PL/SQL Programming

Check for equality and inequality


You can use = and <> to compare the contents of two
nested tables.
But watch out! NULLs have the usual disruptive impact.

This is an enormous advantage over writing a program


to compare the contents of two collections.
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;
END;
10g_compare.sql
10g_compare_nulls.sql
Copyright 2000-2008 Steven Feuerstein - Page 115
10g_compare_old.sql
Copyright 2011 Feuerstein and Associates

Page 115

Oracle PL/SQL Programming

Nested table duplicates detection and removal


Use the SET operator to work with distinct values, and
determine if you have a set of distinct values.
DECLARE
keep_it_simple strings_nt := strings_nt ();
BEGIN
keep_it_simple := SET (favorites_pkg.my_favorites);
favorites_pkg.show_favorites ('FULL SET', favorites_pkg.my_favorites);
p.l (favorites_pkg.my_favorites IS A SET, 'My favorites distinct?');
p.l (favorites_pkg.my_favorites IS NOT A SET,
'My favorites NOT distinct?');
favorites_pkg.show_favorites (
'DISTINCT SET', keep_it_simple);
p.l (keep_it_simple IS A SET, 'Keep_it_simple distinct?');
p.l (keep_it_simple IS NOT A SET, 'Keep_it_simple NOT distinct?');
END;
Copyright 2000-2008 Steven Feuerstein - Page 116
Copyright 2011 Feuerstein and Associates

authors.pkg
10g_set.sql

Page 116

Oracle PL/SQL Programming

Determine if value is in nested table


Use the MEMBER OF syntax to determine if a
value is in the nested table.
Much simpler than scanning the contents of a
collection.
Performs an equality check for the entire element;
you cannot compare individual fields of records,
and so on.

The implementation in SQL itself is quite slow.


Performance in PL/SQL is fast.
Copyright 2011 Feuerstein and Associates

in_clause.*
10g_member_of.sql Page 117

Oracle PL/SQL Programming

Does one nested table contains another?


The SUBMULTISET OF operator determines if
all the elements of one nested table are in
another.
DECLARE
TYPE nested_typ IS TABLE OF NUMBER;
nt1
nested_typ := nested_typ (1, 2);
nt2
nested_typ := nested_typ (3, 2, 1);
BEGIN
IF nt1 SUBMULTISET OF nt3
THEN
...
END IF;
END;
/

Copyright 2011 Feuerstein and Associates

authors.pkg
10g_submultiset.sql

Page 118

Oracle PL/SQL Programming

UNION two nested tables together


Use UNION to join together the contents of
two nested tables.
Duplicates are preserved unless you include
the DISTINCT modifier.
This is the opposite of SQL UNION and UNION ALL.

The resulting collection is either empty or


sequentially filled from index value 1.
You do not need to initialize or extend first.

Copyright 2011 Feuerstein and Associates

authors.pkg
10g_union.sql

Page 119

Oracle PL/SQL Programming

Intersect two nested tables together


Use INTERSECT to find the common elements
of two nested tables.
Duplicates are preserved unless you include
the DISTINCT modifier.
And the ALL modifier is the default.

The resulting collection is either empty or


sequentially filled from index value 1.
You do not need to initialize or extend first.

Copyright 2011 Feuerstein and Associates

10g_intersect.sql

Page 120

Oracle PL/SQL Programming

Take away the elements of one nested table


from another
Use EXCEPT (not MINUS!) to take all elements
in one nested table out of another.
Duplicates are preserved unless you include
the DISTINCT modifier.
And the ALL modifier is the default.

The resulting collection is either empty or


sequentially filled from index value 1.
You do not need to initialize or extend first.
Copyright 2011 Feuerstein and Associates

10g_except.sql

Page 121

Oracle PL/SQL Programming

Conclusions MULTISET operators


When you need to manipulate the contents of
a collection as a set, use a nested table.
The MULTISET operators offer a powerful,
simple way to avoid writing lots of code.
The SET, SUBMULTISET and MEMBER
operators also can come in very handy.
Watch out for results when your nested table
may contain NULL elements.

Copyright 2011 Feuerstein and Associates

Page 122

Oracle PL/SQL Programming

Collection Best Practices


Watch the PGA memory.
Hide the implementation details.
Use subtypes to self-document element and
index by types.
Choosing the best type of collection

Copyright 2011 Feuerstein and Associates

Page 123

Oracle PL/SQL Programming

Watch the PGA memory


Memory for collections is allocated from the
PGA (Process Global Area).
There is a PGA for each session connected to the
instance.

Large collections constructed by programs run


by many simultaneously-connected users can
cause memory errors.
Use the plsq_memory package to analyze the
amount of memory used.
Or at least make sure your DBA knows you are
making extensive use of collections.
plsql_memory*.*
Copyright 2011 Feuerstein and Associates

Page 124

Oracle PL/SQL Programming

Hide the implementation details.


Collections, especially multi-level collections,
are very complex structures.
Hide the way you to set and get elements in a
collection behind an API.
Procedure to set, function to get.
When you have change the implement, you
change in one place (single point of definition).

The "Say Goodbye to Hard-Coding" section


offers more examples on the idea and practice
of information hiding.
multdim.sql
cc_smartargs.sql
Copyright 2011 Feuerstein and Associates

Page 125

Oracle PL/SQL Programming

Use subtypes to self-document datatypes


You should avoid using base datatypes in both
the TABLE OF and INDEX BY clauses.
Especially when working with string-indexed
associative arrays.

Use SUBTYPEs to provide application-specific


names that self-document both contents and
intention.
Constants can also come in handy.

string_tracker3.*
Copyright 2011 Feuerstein and Associates

Page 126

Oracle PL/SQL Programming

Choosing the best type of collection


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 or string indexing

Use nested tables when you need to...


Access the collection inside SQL (table functions, columns in
tables, or utilize SQL operations)
Want or need to perform high level set operations
(MULTISET)

Use varrays when you need to...


If you need to specify a maximum size to your collection
Optimize performance of storing collection as column
Copyright 2011 Feuerstein and Associates

Page 127

Oracle PL/SQL Programming

Collections vs. Global Temporary Tables


Global temporary tables cut down on the
overhead of working with persistent tables.
And you can use the full power of SQL, which is
their main advantage over collections.

GTTs still require interaction with the SGA.


So collections will still be faster, but they will
use more memory.
GTTs consume SGA memory.
global_temp_tab_vs_coll.sql
Copyright 2011 Feuerstein and Associates

Page 128

Oracle PL/SQL Programming

Dealing with Mutating Table errors


Database triggers can be associated with both the DML
statement as a whole and individual rows affected by that
statement.
UPDATE emp SET sal = 1000
Statement level
Row level

UPDATE row 1
UPDATE row N

Row level triggers cannot query from or change the


contents of the table to which it is attached; it is
"mutating".
But statement level triggers do not have this
restriction.
So what are you supposed to do when a row-level
operation needs to "touch" that table?
Copyright 2011 Feuerstein and Associates

mutating.sql

Note: you can use


autonomous
transactions to relax
restrictions associated
with queries.
Page 129

Oracle PL/SQL Programming

A Collection-Based Solution
Since you cannot perform the processing desired in the
row-level trigger, you need to defer the action until you
get to the statement level.
If you are going to defer the work, you have to remember
what you needed to do.
An associative array is an ideal repository for this reminder list.

1st row trigger fires

Nth row trigger fires

mutating_trigger.pkg
ranking.pkg
Copyright 2011 Feuerstein and Associates

Writes to list

Writes to list

Work List
(collection)

Process data
in the list.

Statement Trigger
Page 130

Oracle PL/SQL Programming

Collections: Don't start coding without them.


It is impossible to write efficient, high quality PL/SQL
code, taking full advantage of new features, unless
you use collections.
From array processing to table functions, collections are
required.

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!).

Hands-On Collection seminar


www.ToadWorld.com/SF
Copyright 2011 Feuerstein and Associates

Page 131

Oracle PL/SQL Programming

Bulk Processing of SQL in PL/SQL


The central purpose of PL/SQL is to provide a
portable, fast, easy way to write and execute
SQL against an Oracle database.
Unfortunately, this means that most
developers take SQL for granted when writing
SQL...and just assume Oracle has fully
(automagically) optimized how SQL will run
from within PL/SQL.

Copyright 2011 Feuerstein and Associates

Page 132

Oracle PL/SQL Programming

The Problem with SQL in PL/SQL


Many PL/SQL blocks execute the same SQL statement
repeatedly with different bind values.
Retrieve data one row at a time.
Performs same DML operation for each row retrieved.
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
adjust_compensation (rec, newsal_in);
UPDATE employee SET salary = rec.salary
WHERE employee_id = rec.employee_id;
END LOOP;
END upd_for_dept;
Copyright 2011 Feuerstein and Associates

The result? Simple and


elegant but inefficient...
Why is this?

Page 133

Oracle PL/SQL Programming

Repetitive statement processing from PL/SQL


Oracle server
PL/SQL Runtime Engine
PL/SQL block
FOR rec IN emp_cur LOOP
UPDATE employee
SET salary = ...
WHERE employee_id =
rec.employee_id;
END LOOP;

Procedural
statement
executor

SQL Engine

SQL
statement
executor

Performance penalty
for many context
switches
Copyright 2011 Feuerstein and Associates

Page 134

Oracle PL/SQL Programming

Bulk Processing in PL/SQL


The goal is straightforward: reduce the number of
context switches and you improver performance.
To do this, Oracle "bundles up" the requests for data
(or to change data) and then passes them with a
single context switch.
FORALL speeds up DML.
Use with inserts, updates, deletes and merges.
Move data from collections to tables.

BULK COLLECT speeds up queries.


Can be used with all kinds of queries: implicit, explicit,
static and dynamic.
Move data from tables into collections.

Copyright 2011 Feuerstein and Associates

Page 135

Oracle PL/SQL Programming

Bulk processing with FORALL


Oracle server
PL/SQL Runtime Engine
PL/SQL block
FORALL indx IN
list_of_emps.FIRST..
list_of_emps.LAST
UPDATE employee
SET salary = ...
WHERE employee_id =
list_of_emps(indx);
Update...
Update...
Update...
Update...
Update...
Update...

Copyright 2011 Feuerstein and Associates

Procedural
statement
executor

Fewer context switches,


same SQL behavior

SQL Engine

SQL
statement
executor
Update...
Update...
Update...
Update...
Update...
Update...

Page 136

Oracle PL/SQL Programming

Impact of Bulk Processing in SQL layer


The bulk processing features of PL/SQL change the
way the PL/SQL engine communicates with the SQL
layer.
For both FORALL and BULK COLLECT, the processing
in the SQL engine is almost completely unchanged.
Same transaction and rollback segment management
Same number of individual SQL statements will be
executed.

Only one difference: BEFORE and AFTER statementlevel triggers only fire once per FORALL INSERT
statements.
Not for each INSERT statement passed to the SQL engine
from the FORALL statement.
Copyright 2011 Feuerstein and Associates

statement_trigger_and_forall.sql

Page 137

Oracle PL/SQL Programming

BULK COLLECT Agenda

Introduction to BULK COLLECT


Unlimited BULK COLLECTs
Using the LIMIT clause
When to convert to BULK COLLECT

Copyright 2011 Feuerstein and Associates

Page 138

Oracle PL/SQL Programming

BULK COLLECT for multi-row querying


SELECT * BULK COLLECT INTO collection(s) FROM table;
FETCH cur BULK COLLECT INTO collection(s);
EXECUTE IMMEDIATE query BULK COLLECT INTO collection(s);

Retrieve multiple rows into a collection with a


single fetch (context switch to the SQL engine).
Deposit the multiple rows of data into one or
more collections.

Copyright 2011 Feuerstein and Associates

Page 139

Oracle PL/SQL Programming

"Good to Know" about BULK COLLECT


NO_DATA_FOUND is not raised when no rows

are fetched; instead, the collection is empty.


The "INTO" collections are filled sequentially
from index value 1.
There are no "gaps" between 1 and the index
value returned by the COUNT method.

Only integer-indexed collections may be used.


No need to initialize or extend nested tables
and varrays. Done automatically by Oracle.
Copyright 2011 Feuerstein and Associates

Page 140

Oracle PL/SQL Programming

An "unlimited" BULK COLLECT


Declare a nested
table of records to
hold the queried
data.
Fetch all rows into
collection
sequentially,
starting with 1.

Iterate through
the collection
contents with a
loop.

Copyright 2011 Feuerstein and Associates

DECLARE
TYPE employees_aat IS TABLE OF
employees%ROWTYPE;
l_employees employees_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
FROM employees;

bulkcoll.sql
bulkcollect.tst

FOR indx IN 1 .. l_employees.COUNT


LOOP
process_employee (l_employees(indx));
END LOOP;
END;

But what if I need to fetch and process


millions of rows?
This approach could consume unacceptable
amounts of PGA memory.
Page 141

Oracle PL/SQL Programming

Limiting retrieval with BULK COLLECT


If you are certain that your table with never
have more than N rows, use a VARRAY (N) to
hold the fetched data.
If that limit is exceeded, Oracle will raise an error.
This is not, however, a very common scenario.

If you do not know in advance how many rows


you might retrieve, you should:
1. Declare an explicit cursor.
2. Fetch BULK COLLECT with the LIMIT clause.
Copyright 2011 Feuerstein and Associates

Page 142

Oracle PL/SQL Programming

Limit 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
Use the LIMIT clause with the
FETCH emps_in_dept_cur
INTO to manage the amount of
BULK COLLECT INTO emps LIMIT 1000;
memory used with the BULK
COLLECT operation.

EXIT WHEN emps.COUNT = 0;

Definitely the preferred approach


in production applications with
large or varying datasets.

process_emps (emps);
END LOOP;
CLOSE emps_in_dept_cur;
END bulk_with_limit;
bulklimit.sql
Copyright 2011 Feuerstein and Associates

Page 143

Oracle PL/SQL Programming

Details on that LIMIT clause


The limit value can be a literal or a variable.
I suggest using passing the limit as a parameter to
give you maximum flexibility.

A limit of 100 seems like a good default value.


Setting it to 500 or 1000 doesn't seem to make
much difference in performance.

With very large volumes of data and small


numbers of batch processes, however, a larger
LIMIT could help.
Copyright 2011 Feuerstein and Associates

Page 144

Oracle PL/SQL Programming

Terminating loops containing BULK COLLECT


LOOP
FETCH my_cursor BULK COLLECT INTO l_collection LIMIT 100;
EXIT WHEN my_cursor%NOTFOUND;
BAD IDEA

You will need to break the habit of checking


%NOTFOUND right after the fetch.
You might skip processing some of your data.

Instead, do one of the following:


At the end of the loop, check %NOTFOUND.
Right after fetch, exit when collection.COUNT = 0.
At end of loop, exit when collection.COUNT < limit.
bulklimit_stop.sql
Copyright 2011 Feuerstein and Associates

Page 145

Oracle PL/SQL Programming

When to convert to BULK COLLECT


Prior to Oracle10g, you should convert all
multiple row fetch code to BULK COLLECTs.
On 10.1 and higher, the optimizer will
automatically optimize cursor FOR loops to run at
performance levels similar to BULK COLLECT.
So leave your cursor for loops in place if they...
contain no DML operations.
seem to be running fast enough.

Explicit BULK COLLECTs will usually run a little


faster than cursor for loops optimized to BC.
10g_optimize_cfl.sql
Copyright 2011 Feuerstein and Associates

Page 146

Oracle PL/SQL Programming

BULK COLLECT Conclusions


BULK COLLECT improves performance of
queries that retrieve more than one row.
Use the LIMIT clause to avoid excessive PGA
memory consumption.
Leave it to the optimizer to speed up "read
only" cursor FOR loops.

Copyright 2011 Feuerstein and Associates

Page 147

Oracle PL/SQL Programming

FORALL Agenda

Introduction to FORALL
Using the SQL%BULK_ROWCOUNT
Referencing fields of collections of records
Using FORALL with sparsely-filled collections
Handling errors raised during execution of
FORALL

Copyright 2011 Feuerstein and Associates

Page 148

Oracle PL/SQL Programming

Use FORALL for repeated DML operations


PROCEDURE upd_for_dept (...) IS
BEGIN
FORALL indx IN low_value .. high_value
UPDATE employee
SET salary = newsal_in
WHERE employee_id = list_of_emps (indx);
END;
Bind array

Convert loops that contain inserts, updates,


deletes or merges to FORALL statements.
Header looks identical to a numeric FOR loop.
Implicitly declared integer iterator
At least one "bind array" that uses this iterator as its
index value.
You can also use a different header "style" with INDICES
OF and VALUES OF (covered later)
Copyright 2011 Feuerstein and Associates

forall_timing.sql
forall_examples.sql

Page 149

Oracle PL/SQL Programming

More on FORALL
Use any type of collection with FORALL.
Only one DML statement is allowed per
FORALL.
Each FORALL is its own "extended" DML
statement.
The collection must be indexed by integer.
The bind array must be sequentially filled.
Unless you use the INDICES OF or
VALUES OF clause.
Indexes cannot be expressions.
forall_restrictions.sql
Copyright 2011 Feuerstein and Associates

Page 150

Oracle PL/SQL Programming

How many rows were modified?


SQL%ROWCOUNT returns total number of
rows modified by entire FORALL.
Not to be relied on when used with LOG
ERRORS.

Use the SQL%BULK_ROWCOUNT cursor


attribute to determine how many rows are
modified by each statement.
A "pseudo-collection" of integers; no methods
are defined for this element.
bulk_rowcount.sql
Copyright 2011 Feuerstein and Associates

Page 151

11g

Oracle PL/SQL Programming

FORALL and collections of records


Prior to 11g, you cannot reference a field of a
record in FORALL.
You must instead break data into separate
collections, or...
You can also perform record-level inserts and
updates.
In 11g, this restriction is lifted (but it is an
undocumented feature).
11g_field_of_record.sql
Copyright 2011 Feuerstein and Associates

Page 152

Oracle PL/SQL Programming

Using FORALL with Sparse Collections


Prior to Oracle10g R2, the binding arrays in a
FORALL statement must be sequentially filled.
Now, however, you can bind sparse collections
by using INDICES OF and VALUES OF in the
FORALL header.
PROCEDURE upd_for_dept (...) IS
BEGIN
FORALL indx IN INDICES OF list_of_emps
UPDATE employee
SET salary = newsal_in
WHERE employee_id = list_of_emps (indx);
10g_indices_of*.sql
10g_values_of*.sql

Copyright 2011 Feuerstein and Associates

Page 153

Oracle PL/SQL Programming

FORALL and DML Errors


FORALLs typically execute a large number of DML
statements.
When an exception occurs in one of those DML
statement, the default behavior is:
That statement is rolled back and the FORALL stops.
All (previous) successful statements are not rolled back.

What if you want the FORALL processing to


continue, even if an error occurs in one of the
statements?
Just add the SAVE EXCEPTIONS clause!
Copyright 2011 Feuerstein and Associates

Page 154

Oracle PL/SQL Programming

SAVE EXCEPTIONS and FORALL


PROCEDURE upd_for_dept (newsal_in IN NUMBER,
list_of_emps_in IN DBMS_SQL.NUMBER_TABLE) IS
BEGIN
FORALL indx IN list_of_emps_in.FIRST .. list_of_emps_in.LAST
SAVE EXCEPTIONS
UPDATE employees
SET salary = newsal_in
WHERE employee_id = list_of_emps_in (indx);
END;

The SAVE EXCEPTIONS clause tells Oracle to save exception


information and continue processing all of the DML
statements.
When the FORALL statement completes, if at least one
exception occurred, Oracle then raises ORA-24381.
You then check the contents of SQL%BULK_EXCEPTIONS.
Copyright 2011 Feuerstein and Associates

Page 155

Oracle PL/SQL Programming

Example: FORALL with SAVE EXCEPTIONS


Add SAVE EXCEPTIONS to enable FORALL to
suppress errors at the statement level.
If any exception is
encountered,
Oracle raises 24381 when done.

CREATE OR REPLACE PROCEDURE load_books (books_in IN


book_obj_list_t)
IS
bulk_errors EXCEPTION;
PRAGMA EXCEPTION_INIT ( bulk_errors, -24381 );
BEGIN
Allows processing of all
FORALL indx IN books_in.FIRST..books_in.LAST
statements, even after
SAVE EXCEPTIONS
an error occurs.
INSERT INTO book values (books_in(indx));
EXCEPTION
WHEN bulk_errors THEN
Iterate through
FOR indx in 1..SQL%BULK_EXCEPTIONS.COUNT
"pseudo-collection"
LOOP
log_error (SQL%BULK_EXCEPTIONS(indx).ERROR_INDEX
of errors.
, SQL%BULK_EXCEPTIONS(indx).ERROR_CODE);
END LOOP;
END;
bulkexc.sql
Copyright 2011 Feuerstein and Associates

Page 156

Oracle PL/SQL Programming

SAVE EXCEPTIONS in Detail


For each exception raised, Oracle populates the
SQL%BULK_EXCEPTIONS pseudo-collection of records.
The record has two fields : ERROR_INDEX and ERROR_CODE.
ERROR_INDEX: the index in the bind array for which the error
occurred.
ERROR_CODE: the number (positive) for the error that was
raised

It's a pseudo-collection, because it only supports a single


method: COUNT.
So you iterate from 1 to SQL%BULK_EXCEPTIONS.COUNT
to get information about each error.
Unfortunately, it does not store the error message.
Copyright 2011 Feuerstein and Associates

Page 157

Oracle PL/SQL Programming

Converting to Bulk Processing


Let's take a look at the process by which you
go from "old-fashioned" code to a bulk
processing-based solution.
From integrated row-by-row to phased
processing
With multiple DML statements in loop, how
do you "communicate" from one to the other?

Copyright 2011 Feuerstein and Associates

Page 158

Oracle PL/SQL Programming

The "Old Fashioned" Approach


Cursor FOR loop with two DML statements, trap
exception, and keep on going.
CREATE OR REPLACE PROCEDURE upd_for_dept (
dept_in
IN
employees.department_id%TYPE
, newsal_in
IN
employees.salary%TYPE)
IS
CURSOR emp_cur ...;
BEGIN
FOR rec IN emp_cur
LOOP
BEGIN
INSERT INTO employee_history ...
adjust_compensation (rec.employee_id, rec.salary);
UPDATE employees SET salary = rec.salary ...
EXCEPTION
WHEN OTHERS THEN log_error;
END;
END LOOP;
END upd_for_dept;
cfl_to_bulk_0.sql
Copyright 2011 Feuerstein and Associates

Page 159

Oracle PL/SQL Programming

A phased approach with bulk processing


Change from integrated, row-by-row approach to
a phased approach.
Relational
Table

Phase 1: Bulk collect from table(s) to collection

Phase 2: Modify contents of collection


according to requirements

Relational
Table
Phase 3: FORALL from collection to table
Copyright 2011 Feuerstein and Associates

Page 160

Oracle PL/SQL Programming

Translating phases into code


The cfl_to_bulk_5.sql file contains the
converted program, following the phased
approach.
Phase 1:
Get Data
Phase 3:
Push Data
Phase 2:
Massage Data

Phase 3:
Push Data

BEGIN
OPEN employees_cur;
LOOP
fetch_next_set_of_rows (
bulk_limit_in, employee_ids, salaries, hire_dates);
EXIT WHEN employee_ids.COUNT = 0;
insert_history;
adj_comp_for_arrays (employee_ids, salaries);
update_employee;
END LOOP;
END upd_for_dept;

Copyright 2011 Feuerstein and Associates

cfl_to_bulk_0.sql
cfl_to_bulk_5.sql

Page 161

Oracle PL/SQL Programming

Conclusions Bulk Processing


FORALL is the most important performance tuning
feature in PL/SQL.
Almost always the fastest way to execute repeated SQL
operations in PL/SQL.

You trade off increased complexity of code for


dramatically faster execution.
But remember that Oracle will automatically optimize
cursor FOR loops to BULK COLLECT efficiency.
No need to convert unless the loop contains DML or you
want to maximally optimize your code.

Watch out for the impact on PGA memory!

Copyright 2011 Feuerstein and Associates

Page 162

Oracle PL/SQL Programming

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:
"just" rows and columns!
After all, not everything can be done in SQL.

Table functions can also help improve


performance in several ways.

Copyright 2011 Feuerstein and Associates

Page 163

Oracle PL/SQL Programming

When should you use a table function?


To pass datasets back to a non-PL/SQL host
environment, such as Java and .Net.
They just deal with rows and columns of data.
To do this, you will need to take advantage of
cursor variables.

Improve query performance with pipelined


table functions.
For parallel query environments (data warehouse)
And to reduce user perceptions of elapsed time
Copyright 2011 Feuerstein and Associates

Page 164

Oracle PL/SQL Programming

Short tangent: Cursor variables and the


OPEN FOR
A cursor variable is a variable that points to a
cursor's result set (rows and columns of data).
The type of a cursor variables is a REF CURSOR.
Strong REF CURSOR: select lists must match
Weak REF CURSOR: use with any select.

Cursor variables can be passed as an argument


to a program.
Or passed back to Java, .Net.
ref_cursors.sql
Copyright 2011 Feuerstein and Associates

Page 165

Oracle PL/SQL Programming

Building a table function


A table function must return a nested table or
varray based on a schema-defined type.
Types defined in a PL/SQL package can only be
used with pipelined table functions.

The function header and the way it is called


must be SQL-compatible: all parameters use
SQL types; no named notation allowed until
11g.
In some cases (streaming and pipelined
functions), the IN parameter must be a cursor
variable -- a query result set.

Copyright 2011 Feuerstein and Associates

Page 166

Oracle PL/SQL Programming

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
SELECT column_value
IS
FROM TABLE (
retval
names_nt := names_nt ();
lotsa_names ('Steven'
BEGIN
, 100)) names;
retval.EXTEND (count_in);
FOR indx IN 1 .. count_in
LOOP
retval (indx) :=
base_name_in || ' ' || indx;
END LOOP;
RETURN retval;
END lotsa_names;
Copyright 2011 Feuerstein and Associates

COLUMN_VALUE
-----------Steven 1
...
Steven 100

tabfunc_scalar.sql
Page 167

Oracle PL/SQL Programming

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.

tabfunc_streaming.sql

Copyright 2011 Feuerstein and Associates

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)
/

Page 168

Oracle PL/SQL Programming

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;
/

Copyright 2011 Feuerstein and Associates

tabfunc_streaming.sql

Page 169

Oracle PL/SQL Programming

Use pipelined functions to enhance


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

Pipelined functions allow you to return data


iteratively, asynchronous to termination of the
function.
As data is produced within the function, it is passed
back to the calling process/query.

Pipelined functions can only be called within a


SQL statement.
They make no sense within non-multi-threaded
PL/SQL blocks.
Copyright 2011 Feuerstein and Associates

Page 170

Oracle PL/SQL Programming

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 begin viewing and browsing, even before
the function has finished retrieving all of the data.

And pipelined functions use less PGA memory than


non-pipelined functions!

Copyright 2011 Feuerstein and Associates

Page 171

Oracle PL/SQL Programming

Piping rows out from a pipelined function


Add PIPELINED
keyword to header

Pipe a row of data


back to calling block
or query

RETURN...nothing at
all!

Copyright 2011 Feuerstein and Associates

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

Page 172

Oracle PL/SQL Programming

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 2011 Feuerstein and Associates

Page 173

Oracle PL/SQL Programming

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 2011 Feuerstein and Associates

Page 174

Oracle PL/SQL Programming

The NOCOPY hint


By default, Oracle passes all IN OUT and OUT
arguments by value, not reference.
This means that OUT and IN OUT arguments
always involve some copying of data.

With NOCOPY, you turn off the copy process.


But it comes with a risk: Oracle will not
automatically "rollback" or reverse changes
made to your variables if the NOCOPY-ed
program raises an exception.
nocopy*.*
string_nocopy.*
Copyright 2011 Feuerstein and Associates

Page 175

Oracle PL/SQL Programming

Dynamic SQL in PL/SQL Programs


Dynamic SQL actually refers, in the world of
PL/SQL, to two things:
SQL statements, such as a DELETE or DROP 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

Copyright 2011 Feuerstein and Associates

'BEGIN ' ||
l_proc_name || ' (' ||
l_parameters || '); END;'

Page 176

Oracle PL/SQL Programming

Use Dynamic SQL To...


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 2011 Feuerstein and Associates

Page 177

Oracle PL/SQL Programming

Two Mechanisms 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.
EXECUTE IMMEDIATE
OPEN cv FOR 'SELECT ... '
Copyright 2011 Feuerstein and Associates

Page 178

Oracle PL/SQL Programming

Four Dynamic SQL Methods


Method 1: DDL or non-query DML without bind
variables
EXECUTE IMMEDIATE string

Method 2: Non-query DML with fixed number of bind


variables
EXECUTE IMMEDIATE string USING

Method 3: Query with fixed number of expressions in


the select list and fixed number of bind variables
EXECUTE IMMEDIATE string USING ... INTO

Method 4: Query with dynamic number of


expressions in select list or DML with dynamic
number of bind variables.
DBMS_SQL is best.
Copyright 2011 Feuerstein and Associates

And then there's


dynamic PL/SQL....

Page 179

Oracle PL/SQL Programming

Method 1: DDL within PL/SQL


The simplest kind of dynamic SQL.
All you can do is pass a string for execution, no
values are bound in, no values are passed out.

Always performs an implicit commit when


executed.
Should be used with great care, since a DDL
change can cause a ripple effect of invalidating
program units.
Common problem: Insufficient privileges.
Directly granted privileges are needed!

Copyright 2011 Feuerstein and Associates

dropwhatever.sp
create_index.sp
settrig.sp
ddl_insuff_privs.sql

Page 180

Oracle PL/SQL Programming

Method 2: DML with fixed # of bind variables


Add the USING clause to EXEC IMMEDIATE to
supply bind values for placeholders.
Placeholders are strings starting with ":".

USING elements can include a mode, just like


a parameter: IN, OUT or IN OUT.
OUT and IN OUT are for dynamic PL/SQL

Must provide a value for each placeholder.


With dynamic SQL, even if the same placeholder
is repeated, you must provide the repeat value.
method_2_example.sql
updnval*.*
Copyright 2011 Feuerstein and Associates

Page 181

Oracle PL/SQL Programming

Dynamic FORALL Method 2 Example


This example shows the use of bulk binding and
collecting, plus application of the RETURNING clause.
CREATE TYPE NumList IS TABLE OF NUMBER;
CREATE TYPE NameList IS TABLE OF VARCHAR2(15);
PROCEDURE update_emps (
col_in IN VARCHAR2, empnos_in IN numList) IS
enames NameList;
BEGIN
FORALL indx IN empnos_in.FIRST .. empnos_in.LAST
EXECUTE IMMEDIATE
'UPDATE emp SET ' || col_in || ' = ' || col_in
|| ' * 1.1 WHERE empno = :1
RETURNING ename INTO :2'
USING empnos_in (indx )
Notice that empnos_in is
RETURNING BULK COLLECT INTO enames;
indexed, but enames is not.
...
END;
Copyright 2011 Feuerstein and Associates

Page 182

Oracle PL/SQL Programming

Method 3: Query with fixed # in select list


Add the INTO clause to EXEC IMMEDIATE to
retrieve values from query.
May be in addition to the USING clause.
If you don't know the number at compile time,
cannot use the INTO clause.

Usually you are dealing with a dynamic table


or column name.
The INTO clause can contain a list of variables,
a record, a collection, etc.
tabcount81.sql
Copyright 2011 Feuerstein and Associates

Page 183

Oracle PL/SQL Programming

Dynamic BULK COLLECT Method 3


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;
Copyright 2011 Feuerstein and Associates

return_nested_table.sf

Page 184

Oracle PL/SQL Programming

Quiz!
What's wrong with
this code?
How would you fix
it?

Copyright 2011 Feuerstein and Associates

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;

Page 185

Oracle PL/SQL Programming

From 22,000 lines of code to 1!


PROCEDURE process_lineitem (
line_in IN INTEGER)
IS
BEGIN
IF line_in = 1
THEN
process_line1;
END IF;

PROCEDURE process_lineitem (
line_in IN INTEGER)
IS
BEGIN
EXECUTE IMMEDIATE
'BEGIN process_line'||
line_in ||'; END;';
END;

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

Identify the pattern and


resolve it either with
reusable modules or
dynamic abstractions.
dynplsql.txt

Copyright 2011 Feuerstein and Associates

Page 186

Oracle PL/SQL Programming

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.
dynplsql*.sql
Copyright 2011 Feuerstein and Associates

Page 187

Oracle PL/SQL Programming

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 2011 Feuerstein and Associates

Page 188

Oracle PL/SQL Programming

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 2011 Feuerstein and Associates

Page 189

Oracle PL/SQL Programming

Pseudo-code flow for


DBMS_SQL implementation
Build the
SELECT list

BEGIN
FOR each-column-in-table LOOP
add-column-to-select-list;
END LOOP;

Parse the
variable SQL

DBMS_SQL.PARSE (cur, select_string, DBMS_SQL.NATIVE);

Define each
column

FOR each-column-in-table LOOP


DBMS_SQL.DEFINE_COLUMN (cur, nth_col, datatype);
END LOOP;

Execute the
query

Extract each
value

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;

Also:
dyn_placeholder.*
Copyright 2011 Feuerstein and Associates

Lots of code, but relatively


straightforward
Page 190

Oracle PL/SQL Programming

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!


Or upgrade to 11g
Copyright 2011 Feuerstein and Associates

Page 191

Oracle PL/SQL Programming

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 - max 255 bytes.
DBMS_SQL.VARCHAR2A - max 32,767 bytes

New in Oracle11g: both NDS and DBMS_SQL


accept CLOBs.
exec_ddl_from_file.sql
Copyright 2011 Feuerstein and Associates

Page 192

Oracle PL/SQL Programming

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
Steven
Feuerstein - Page 193
Copyright
2011 2000-2008
Feuerstein and
Associates

Page 193

Oracle PL/SQL Programming

Best Practices for Dynamic SQL


Stored programs with dynamic SQL should be defined
as AUTHID CURRENT_USER.
Remember that dynamic DDL causes an implicit
commit.
Consider making all DDL programs autonomous
transactions.

Always EXECUTE IMMEDIATE a variable, so that you


can then display/log/view that variable's value in case
of an error.
Avoid concatenation;
bind whenever possible.
dropwhatever.sp
usebinding.sp
toomuchbinding.sp
useconcat*.*
ultrabind.*
Copyright 2011 Feuerstein and Associates

Page 194

Oracle PL/SQL Programming

NDS or DBMS_SQL: Which should you use?


Reasons to go with
NDS:
Ease of use
Works with all SQL
datatypes (including userdefined object and
collection types)
Fetch into records and
collections of records
Usually faster runtime
performance

tabcount.sf
tabcount81.sf
Copyright 2011 Feuerstein and Associates

Why You'd Use


DBMS_SQL:
Method 4 Dynamic SQL
DESCRIBE columns of cursor
SQL statements larger than
32K (advantage disappears in
11g)
Better reuse of parsed SQL
statements -- persistent cursor
handles!

Bottom line: NDS should be your first choice.


Page 195

Oracle PL/SQL Programming

Must Know Error Management Features


DBMS_UTILITY functions
FORMAT_CALL_STACK
FORMAT_ERROR_STACK
FORMAT_ERROR_BACKTRACE

DBMS_ERRLOG and LOG ERRORS


Suppress errors at row level in SQL layer

Copyright 2011 Feuerstein and Associates

Page 196

Oracle PL/SQL Programming

Oracle Built-ins For Handling Exceptions


In addition to the application-specific
information you may want to log, Oracle builtins provide you with answers to the following
questions:
How did I get here?
What is the error code?
What is the error message and/or stack?
On what line was the error raised?

Copyright 2011 Feuerstein and Associates

Page 197

Oracle PL/SQL Programming

SQLCODE and SQLERRM


SQLCODE returns the error code of the most
recently-raised exception in your session.
SQLERRM returns the error message
associated with SQLCODE but it also a
generic error message lookup function.
Neither SQLCODE nor SQLERRM can be called
from within a SQL statement.
You must assign them to local variables to use their values
in SQL statements (like writing to an error log).
sqlcode.sql
sqlcode_test.sql
Copyright 2011 Feuerstein and Associates

Page 198

Oracle PL/SQL Programming

SQLERRM Details
If you don't pass an argument to SQLERRM, it returns
the error message for the SQLCODE value.
When called outside of an exception handler, always
returns "success" message no error.

You can also pass an error code to SQLERRM and it


will return the generic error message.
The maximum size of a string returned by SQLERRM
is 512 bytes.
When there is a stack of errors, Oracle may truncate the
string returned by SQLERRM.
Oracle recommends you use
DBMS_UTILITY.FORMAT_ERROR_STACK instead.
sqlerrm.sql
Copyright 2011 Feuerstein and Associates

Page 199

Oracle PL/SQL Programming

DBMS_UTILITY error functions


Answer the question "How did I get here?"
with DBMS_UTILITY.FORMAT_CALL_STACK.
Get a more complete error message with
DBMS_UTILITY.FORMAT_ERROR_STACK.
Find line number on which error was raised
with
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE.

Copyright 2011 Feuerstein and Associates

Page 200

Oracle PL/SQL Programming

DBMS_UTILITY.FORMAT_CALL_STACK
The "call stack" reveals the path taken through
your application code to get to that point.
Very useful whenever tracing or logging
errors.
The string is formatted to show line number
and program unit name.
But it does not reveal the names of subprograms
in packages.
callstack.sql
callstack.pkg
Copyright 2011 Feuerstein and Associates

Page 201

Oracle PL/SQL Programming

DBMS_UTILITY.FORMAT_ERROR_STACK
This built-in returns the error stack in the
current session.
Possibly more than one error in stack.

Returns NULL when there is no error.


Returns a string of maximum size 2000 bytes
(according to the documentation).
Oracle recommends you use this instead of
SQLERRM, to reduce the chance of truncation.
errorstack.sql
big_error_stack.sql
Copyright 2011 Feuerstein and Associates

Page 202

Oracle PL/SQL Programming

DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
The backtrace function (new to 10.2) answers the
question: "Where was my error raised?
Prior to 10.2, you could not get this information from
within PL/SQL.

Call it whenever you are logging an error.


When you re-raise your exception (RAISE;) or raise a
different exception, subsequent BACKTRACE calls will
point to that line.
So before a re-raise, call BACKTRACE and store that
information to avoid losing the original line number.
backtrace.sql
bt.pkg
Copyright 2011 Feuerstein and Associates

Page 203

Oracle PL/SQL Programming

DBMS_ERRLOG and LOG ERRORS- agenda

Impact of errors on DML execution


Introduction to LOG ERRORS feature
Creating an error log table
Adding LOG ERRORS to your DML statement
"Gotchas" in the LOG ERRORS feature
The DBMS_ERRLOG helper package

Copyright 2011 Feuerstein and Associates

Page 204

Oracle PL/SQL Programming

Impact of errors on DML execution


A single DML statement can result in changes to
multiple rows.
When an error occurs on a change to a row....

All previous changes from that statement are rolled


back.
No other rows are processed.
An error is passed out to the calling block (turns into a
PL/SQL exception).
No rollback on completed DML in that session.

Usually acceptable, but what if you want to:


Avoid losing all prior changes?
Avoid the performance penalty of exception
management in PL/SQL?

Copyright 2011 Feuerstein and Associates

errors_and_dml.sql

Page 205

Oracle PL/SQL Programming

Row-level Error Suppression in DML with


LOG ERRORS
Once the error propagates out to the PL/SQL
layer, it is too late; all changes to rows have
been rolled back.
The only way to preserve changes to rows is to
add the LOG ERRORS clause in your DML
statement.
Errors are suppressed at row level within the SQL
Layer.

But you will first need to created an error log


table with DBMS_ERRLOG.
Copyright 2011 Feuerstein and Associates

Page 206

Oracle PL/SQL Programming

Terminology for LOG ERRORS feature


DML table: the table on which DML
operations will be performed
Error logging table (aka, error table): the table
that will contain history of errors for DML
table
Reject limit: the maximum number of errors
that are acceptable for a given DML statement
"If more than 100 errors occur, something is badly
wrong, just stop."
Copyright 2011 Feuerstein and Associates

Page 207

Oracle PL/SQL Programming

Step 1. Create an error log table


Call DBMS_ERRLOG.CREATE_ERROR_LOG to
create the error logging table for your "DML
table."
Default name: ERR$_<your_table_name>

You can specify alternative table name,


tablespace, owner.
Necessary if DML table name > 25 characters!

The log table contains five standard error log


info columns and then a column for each
VARCHAR2-compatible column in the DML
table.
Copyright 2011 Feuerstein and Associates

dbms_errlog.sql

Page 208

Oracle PL/SQL Programming

Step 2: Add LOG ERRORS to your DML


UPDATE employees
SET salary = salary_in
LOG ERRORS REJECT LIMIT UNLIMITED;

UPDATE employees
SET salary = salary_in
LOG ERRORS REJECT LIMIT 100;

Specify the limit of errors after which you want


the DML statement to stop or UNLIMITED to
allow it to run its course.
Then...make sure to check the error log table
after you run your DML statement!
Oracle will not raise an exception when the DML
statement ends big difference from SAVE
EXCEPTIONS.
Copyright 2011 Feuerstein and Associates

Page 209

Oracle PL/SQL Programming

"Gotchas" in the LOG ERRORS feature


The default error logging table is missing some critical
information.
When the error occurred, who executed the statement,
where it occurred in my code

Error reporting is often obscure: "Table or view does


not exist."
Its up to you to grant the necessary privileges on the
error log table.
If the DML table is modified from another schema,
that schema must be able to write to the log table as
well.
Use the DBMS_ERRLOG helper package to get around
many of these issues.
dbms_errlog.sql
Copyright 2011 Feuerstein and Associates

Page 210

Oracle PL/SQL Programming

The DBMS_ERRLOG helper package


Creates the error log table.
Adds three columns to keep track of user,
timestamp and location in code.
Compiles a trigger to populate the added
columns.
Creates a package to make it easier to manage
the contents of the error log table.

dbms_errlog_helper.sql
dbms_errlog_helper_demo.sql
Copyright 2011 Feuerstein and Associates

Page 211

Oracle PL/SQL Programming

LOG ERRORS Conclusions


When executing multiple DML statements or
affecting multiple rows, decide on your error
policy.
Stop at first error or continue?

Then decide on the level of granularity of


continuation: statement or row?
LOG ERRORS is the only way to perform row-level
error suppression.

Make sure that you check and manage any


error logs created by your code.
Copyright 2011 Feuerstein and Associates

Page 212

Oracle PL/SQL Programming

Other Oracle Database 11g


New PL/SQL Features

Function Result Cache


In-lining optimization (covered earlier)
SIMPLE_INTEGER and native compilation
Triggers: FOLLOWS and compound trigger
Dynamic SQL interoperability and completeness
Use sequences in native PL/SQL
The CONTINUE statement
PL/Scope
Referencing supertype methods
Fine-grained dependency model

Copyright 2011 Feuerstein and Associates

Page 213

11g

Oracle PL/SQL Programming

The Oracle 11g Function Result Cache


Oracle offers a far superior caching solution
than PGA caching in 11g: the Function Result
Cache.
This cache is...
stored in the SGA
shared across sessions
purged of dirty data automatically

You can use and should use it to retrieve data


from any table that is queried more
frequently than updated.
Copyright 2011 Feuerstein and Associates

Page 214
Page 214

11g

Oracle PL/SQL Programming

How the Function Result Cache Works


Add the RESULT_CACHE clause to your function's
header.
When a call is made to function, Oracle
compares IN argument values to the cache.
If no match, the function is executed and the
inputs and return data are cached.
If a match is found, the function is not executed;
cached data is returned.
If changes to a "relies on" table are committed,
the cache is marked invalid and will be re-built.
Copyright 2011 Feuerstein and Associates

Page 215
Page 215

11g

Oracle PL/SQL Programming

Function Result Cache Example


CREATE OR REPLACE PACKAGE emplu11g
IS
FUNCTION onerow (employee_id_in IN employees.employee_id%TYPE)
RETURN employees%ROWTYPE
RESULT_CACHE;
END emplu11g;
CREATE OR REPLACE PACKAGE BODY emplu11g
IS
FUNCTION onerow (employee_id_in IN employees.employee_id%TYPE)
RETURN employees%ROWTYPE
RESULT_CACHE RELIES_ON (employees)
IS
The specification must indicate you
onerow_rec
employees%ROWTYPE;
are using a result cache.
BEGIN
SELECT * INTO onerow_rec
FROM employees
The body specifies the "relies on"
WHERE employee_id = employee_id_in;
dependencies, if any.
RETURN onerow_rec;
END onerow;
END emplu11g;
11g_emplu*.*
Copyright 2011 Feuerstein and Associates

Page 216
Page 216

11g

Oracle PL/SQL Programming

Result Cache Things to Keep in Mind - 1


If you have uncommitted changes in your
session, dependent caches are ignored.
The cache will not override your own changed
data.

Caching is not performed for complex types:


records with CLOBs, collections, etc.
The cache is not related to SQL statements in
your function.
It only keeps track of the input values and the
RETURN clause data.
11g_frc_demo.sql
Copyright 2011 Feuerstein and Associates

Page 217

11g

Oracle PL/SQL Programming

Result Cache Things to Keep in Mind - 2


You cannot use the result cache with invoker rights
program units.
Bypass execution of function body, Oracle cannot
resolve references to objects - the whole point of IR.

Functions with session-specific dependencies must


be "result-cached" with great care.
Virtual private database configurations
References to SYSDATE, reliance on NLS_DATE_FORMAT,
time zone changes
Application contexts (calls to SYS_CONTEXT)

Solution: move all dependencies into parameter


list.
11g_frc_vpd.sql
11g_frc_vpd2.sql
Copyright 2011 Feuerstein and Associates

Page 218

11g

Oracle PL/SQL Programming

Tuning the Result Cache


Oracle offers a number of ways to manage the
result cache and tune it to your specific
application needs:
RESULT_CACHE_SIZE initialization parameter
DBMS_RESULT_CACHE management package
v$RESULT_CACHE_* performance views

Copyright 2011 Feuerstein and Associates

Page 219
Page 219

11g

Oracle PL/SQL Programming

The SIMPLE_INTEGER
and real Native Compilation

Native Compilation

With PLSQL_CODE_TYPE='Native' ('INTERPRETED is the


default), Oracle will compile PL/SQL code down to machine
code on all chip sets supported by Oracle.
Use only for production; you cant debug native code.
Oracle recommends that you recompile your entire code
base (including STANDARD and built-in packages) using
native compilation!

The new, faster SIMPLE_INTEGER:


Has a NOT NULL constraint
Values wrap, they do not overflow
Faster than PLS_INTEGER
ALTER SESSION SET PLSQL_CODE_TYPE = 'NATIVE';

Copyright 2011 Feuerstein and Associates

Page 220

11g

Oracle PL/SQL Programming

The Compound Trigger


Rather than manage
multiple triggers on
the same table, you
can join all trigger
operations into a
single compound
trigger.
Avoidance of mutating
trigger errors is now
much simpler and more
straightforward.
mutating.sql
11g_compound_mutating.sql

Copyright 2011 Feuerstein and Associates

CREATE TRIGGER full_mfe_excuse_transaction


FOR UPDATE ON mfe_customers
COMPOUND TRIGGER
... declare variables here ...
BEFORE STATEMENT IS
BEGIN
...
END BEFORE STATEMENT;
BEFORE ROW IS
BEGIN
...
END BEFORE ROW;
AFTER ROW IS
BEGIN
...
END AFTER ROW;
AFTER STATEMENT IS
BEGIN
...
END AFTER STATEMENT;
END full_mfe_excuse_transaction;
Page 221

11g

Oracle PL/SQL Programming

Specifying order of trigger firing


Prior to Oracle11g, when you defined more
than one trigger on the same action (e.g.,
"before insert"), there was no guarantee of
the order in which the triggers would fire.
Now simply include a FOLLOWS clause:
CREATE OR REPLACE TRIGGER after_insert_validate
BEFORE INSERT
ON my_table
FOR EACH ROW
FOLLOWS after_insert_adjust
BEGIN
...
END;
multiple_triggers.sql
trigger_conflict.sql
Copyright 2011 Feuerstein and Associates

Page 222

11g

Oracle PL/SQL Programming

Use sequences in native PL/SQL!


You no longer have to select from dual to get
the next value of a sequence!
Much more intuitive code
Improvement in performance
CREATE OR REPLACE TRIGGER employees_bi_trg
BEFORE INSERT
ON employees
FOR EACH ROW
BEGIN
:NEW.employee_id := my_seq.NEXTVAL;
END;
/

Copyright 2011 Feuerstein and Associates

11g_native_sequence.sql

Page 223

11g

Oracle PL/SQL Programming

Using CONTINUE in a loop


You can now tell PL/SQL to terminate execution of the
current loop body and immediately go on to the next
iteration of that loop.
BEGIN
<<outer_loop >>
FOR o_index IN 1 .. my_list.COUNT
LOOP
<<inner_loop>>
FOR i_index IN your_list.FIRST .. your_list.LAST
LOOP
... lots of code
/* Skip the rest of this and the outer
loop if condition is met. */
CONTINUE outer_loop WHEN condition_is_met;
... more inner loop logic
END LOOP inner_loop;
... more outer loop logic
END LOOP outer_loop;
END;
Copyright 2011 Feuerstein and Associates

11g_continue.sql
local_modules_with_continue.sql

Page 224

11g

Oracle PL/SQL Programming

PL/Scope
A compiler-driven tool that collects information
about identifiers and stores it in data dictionary
views.
Use PL/Scope to answer questions like:
Where is a variable assigned a value in a program?
What variables are declared inside a given program?
Which programs call another program (that is, you
can get down to a subprogram in a package)?
Find the type of a variable from its declaration.
Copyright 2011 Feuerstein and Associates

Page 225

Oracle PL/SQL Programming

Getting Started with PL/Scope


ALTER SESSION SET plscope_settings='IDENTIFIERS:ALL'

PL/Scope must be enabled; it is off by default.


When your program is compiled, information
about all identifiers are written to the
ALL_IDENTIFIERS view.
You then query the contents of the view to get
information about your code.
Check the ALL_PLSQL_OBJECT_SETTINGS view
for the PL/Scope setting of a particular
program unit.
Copyright 2011 Feuerstein and Associates

Page 226

Oracle PL/SQL Programming

Key Columns in ALL_IDENTIFIERS


TYPE
The type of identifier (VARIABLE, CONSTANT, etc.)

USAGE
The way the identifier is used (DECLARATION,
ASSIGNMENT, etc.)

LINE and COL


Line and column within line in which the identifier is found

SIGNATURE
Unique value for an identifier. Especially helpful when
distinguishing between overloadings of a subprogram or
"connecting" subprogram declarations in package with
definition in package body.

USAGE_ID and USAGE_CONTEXT_ID


Reveal hierarchy of identifiers in a program unit
Copyright 2011 Feuerstein and Associates

Page 227

11g

Oracle PL/SQL Programming

Start with some simple examples


Show all the identifiers in a program unit
Show all variables declared in a subprogram
(not at package level)
Show all variables declared in the package
specifications
Show the locations where a variable could be
modified

Copyright 2011 Feuerstein and Associates

plscope_demo_setup.sql
plscope_all_idents.sql
plscope_var_declares.sql
plscope_gvar_declares.sql
plscope_var_changes.sql

Page 228

11g

Oracle PL/SQL Programming

More advanced examples


Find exceptions that are defined but never
raised
Show the hierarchy of identifiers in a program
unit
Validate naming conventions with PL/Scope

plscope_unused_exceptions.sql
plscope_hierarchy.sql
plscope_naming_conventions.sql

Copyright 2011 Feuerstein and Associates

Page 229

11g

Oracle PL/SQL Programming

PL/Scope Helper Utilities


Clearly, "data mining" in ALL_IDENTIFIERS can
get very complicated.
Suggestions for putting PL/Scope to use:
Build views to hide some of the complexity.
Build packages to provide high-level subprograms
to perform specific actions.

plscope_helper_setup.sql
plscope_helper.pkg

Copyright 2011 Feuerstein and Associates

Page 230

11g

Oracle PL/SQL Programming

PL/Scope Summary
PL/Scope gives you a level of visibility into
your code that was never before possible.
The ALL_IDENTIFIERS view is not
straightforward.
Use the helper package to get you started.
Hopefully we will see PL/Scope interfaces built
into the most popular IDEs.

Copyright 2011 Feuerstein and Associates

Page 231

11g

Oracle PL/SQL Programming

Oracle11g Enhancements for Dynamic SQL


Parse and execute very large strings.
EXECUTE IMMEDIATE a CLOB
DBMS_SQL.PARSE a CLOB

Interoperability
Convert DBMS_SQL cursor to cursor variable
Convert cursor variable to DBMS_SQL cursor

Improved security
Random generation of DBMS_SQL cursor handles
Denial of access/use of DBMS_SQL with invalid cursor
or change of effective user.

Copyright 2011 Feuerstein and Associates

Page 232

11g

Oracle PL/SQL Programming

Parse very large SQL statements


Both EXECUTE IMMEDIATE and OPEN FOR
now accept a CLOB.
A new DBMS_SQL.PARSE overloading also
accepts a CLOB.
You no longer need to use the collection
overloadings of DBMS_SQL.PARSE to work
with very large strings.

Copyright 2011 Feuerstein and Associates

exec_ddl_from_file.sql
exec_ddl_from_file_11g.sql

Page 233

Oracle PL/SQL Programming

Interoperability
DBMS_SQL.TO_REFCURSOR
Cursor handle to cursor variable
Useful when you need DBMS_SQL to bind and
execute, but easier to fetch through cursor
variable.

DBMS_SQL.TO_CURSOR_NUMBER
Cursor variable to cursor handle
Binding is static but SELECT list is dynamic

Copyright 2011 Feuerstein and Associates

Page 234

11g

Oracle PL/SQL Programming

DBMS_SQL.TO_REFCURSOR
Converts a SQL cursor number to a weak cursor
variable, which you can use in native dynamic SQL
statements.
Before passing a SQL cursor number to the
DBMS_SQL.TO_REFCURSOR function, you must OPEN,
PARSE, and EXECUTE it (otherwise an error occurs).
After you convert a SQL cursor number to a REF
CURSOR variable, DBMS_SQL operations can access it
only as the REF CURSOR variable, not as the SQL cursor
number.
Using the DBMS_SQL.IS_OPEN function to see if a
converted SQL cursor number is still open causes an error.
Copyright 2011 Feuerstein and Associates

11g_to_refcursor.sql

Page 235

11g

Oracle PL/SQL Programming

DBMS_SQL.TO_CURSOR_NUMBER
Converts a REF CURSOR variable (either strong
or weak) to a SQL cursor number, which you
can pass to DBMS_SQL subprograms.
Before passing a REF CURSOR variable to the
DBMS_SQL.TO_CURSOR_NUMBER function,
you must OPEN it.
After you convert a REF CURSOR variable to a
SQL cursor number, native dynamic SQL
operations cannot access it.
Copyright 2011 Feuerstein and Associates

11g_to_cursorid.sql

Page 236

11g

Oracle PL/SQL Programming

Improved Security
Cursor handles generated by the
DBMS_SQL.OPEN_CURSOR function are now
random and not sequential.
Pass an invalid cursor handle to many
DBMS_SQL programs and DBMS_SQL is then
disabled.
Have to reconnect.

You can specify a security level for DBMS_SQL


cursor management.
Minimize danger of SQL injection.
Copyright 2011 Feuerstein and Associates

11g_random_cursor_handle.sql
11g_access_denied_1.sql
11g_effective_user_id.sql

Page 237

11g

Oracle PL/SQL Programming

Oracle11g Dynamic SQL Conclusions


Both mechanisms for dynamic SQL have been
improved.
Of especial importance is the ability to move
between native dynamic SQL and DBMS_SQL,
to make it even easier to implement method 4
dynamic SQL requirements.
Make sure you take full advantage of the
security-related features of DBMS_SQL for any
external-facing interfaces.
If, that is, you are required to use DBMS_SQL!
Copyright 2011 Feuerstein and Associates

Page 238

11g

Oracle PL/SQL Programming

Referencing supertype methods


New to Oracle 11g, you can invoke a supertype's
method in your override of that method.
Useful when you want to "add on" to supertype method,
but you certainly don't want to have to copy/paste the
code needed.

One very typical example is when you want to


"display" an object.
Show values of attributes of each type in the hierarchy.
Each "level" has its own "to _string" function.

11g_gen_invoc.sql
Copyright 2011 Feuerstein and Associates

Page 239

Oracle PL/SQL Programming

Pre-Oracle11g Dependency Model


Dependencies tracked at object level
Which tables is a program dependent on?
Which program units is a program dependent on?

So if any change is made to a referenced object,


all dependent objects' status are set to INVALID.
Even if the change doesn't affect the dependent
object.

Use ALL_DEPENDENCIES to analyze.


REFERENCED* columns show the objects on which an
object depends.
Copyright 2011 Feuerstein and Associates

analyzedep*.*
code_referencing_tables.sql
layer_validator*.*

Page 240

Oracle PL/SQL Programming

Oracle11g Dependency Model


Now dependencies are tracked down to the
sub-object level: "fine-grained dependencies"
or FGD.
Columns within tables
Parameters within program units.

Impact of change:
You can minimize invalidation of program units.

You cannot obtain this fine-grained


dependency information through any data
dictionary views yet.
11g_fgd*.sql
Copyright 2011 Feuerstein and Associates

Page 241

Oracle PL/SQL Programming

Say Goodbye to Hard-Coding - Agenda

What is hard-coding?
Why is it a problem?
The opposite of hard-coding
Where's the hard-coding?
Specific techniques for getting rid of the many
forms of hard-coding

Copyright 2011 Feuerstein and Associates

Page 242

Oracle PL/SQL Programming

What is Hard Coding? - 1


Traditionally, has referred to practice of
placing literal values in the main body of your
code.
From Wikipedia: The term "hard-coded" was coined in
1990 by R. Galichon (then a Programmer/Analyst at
Mobil). The term was used as an analogy to hardwiring
circuits - and was meant to convey the inflexibility which
results from its usage within software design and
implementation.

More generally, hard-coding is closely tied to


the problem of repetition in our code.
Copyright 2011 Feuerstein and Associates

Page 243

Oracle PL/SQL Programming

What is Hard Coding? - 2


You "hard code" every time you write a piece
of code that assumes an aspect of your
application will not change and therefore can
be explicitly referenced throughout the code
base.
Then when the change takes place, you have
to locate all those repetitions and fix them.
Sometimes that's easy, sometimes it is very
difficult to do, but in all cases, it causes problems.
Copyright 2011 Feuerstein and Associates

Page 244

Oracle PL/SQL Programming

Why is Hard Coding Bad?


Hard-coding would be fine if nothing ever
changed in our applications.
If requirements stayed the same...
If the definitions of our tables stayed the same....
If rules and formulas stayed the same....
If configuration constants stayed the same....

Too bad!
Whenever anything changes, you have to find all
the places you explicitly coded it, and fix them.
Copyright 2011 Feuerstein and Associates

Page 245

Oracle PL/SQL Programming

The Opposite of Hard-Coding


If hard-coding is bad, then maybe we should
do the opposite of hard-coding.
Soft Coding
Rather than explicitly code values, rules and
algorithms, make them "soft" or dynamic
changeable and settable at runtime.

Easy Coding
It's hard fixing hard-codings in multiple places. It'd
be easier to fix things in one place. It really does
make things easier.

Copyright 2011 Feuerstein and Associates

Page 246

Oracle PL/SQL Programming

Hard-Coding Avoidance:
Principles and Concepts
Single point of definition (no repetition)
You should always aim for a single point of definition or
SPOD for everything in your application.

Information hiding the name is the thing


Avoid exposing the implementation details of formulas,
rules, algorithms, data access.
The more you hide, the more flexibility you have.

"Never" and "Always" in software


It's never going to stay the same.
It's always going to change.

Pay attention to that "voice in your head."


Copyright 2011 Feuerstein and Associates

Page 247

Where's the hard-coding?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Oracle PL/SQL Programming

PROCEDURE process_employee (department_id_in IN NUMBER)


IS
l_id
INTEGER; l_salary NUMBER (9,2);
l_name
VARCHAR2 (100);

hardcoding.sql

/* Full name: LAST COMMA FIRST (ReqDoc 123.A.47) */


CURSOR emps_in_dept_cur
IS
SELECT employee_id, salary, last_name || ',' || first_name lname
FROM employees
WHERE department_id = department_id_in;
BEGIN
OPEN emps_in_dept_cur;
LOOP
FETCH emps_in_dept_cur
INTO l_id, l_salary, l_name;
IF l_salary > 10000000 THEN adjust_comp_for_ceo (l_salary);
ELSE analyze_compensation (l_id, l_salary, 10000000); END IF;
EXIT WHEN emps_in_dept_cur%NOTFOUND;
END LOOP;
COMMIT;
EXCEPTION WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20907, 'Invalid department ID');
END;

Copyright 2011 Feuerstein and Associates

Page 248

Oracle PL/SQL Programming

Potential Hard-Codings in PL/SQL Code


Literal values
Especially language-specific literals

Constrained declarations
Especially VARCHAR2(n)

Fetch into a list of variables


Rules and formulas - especially the "trivial" ones
SQL statements ah, very scary!
Algorithmic details
Example: error logging mechanisms

Transaction boundaries and dates


Copyright 2011 Feuerstein and Associates

Page 249

Oracle PL/SQL Programming

Dealing with Magic Values

What are "magic values"?


Hide behind constants
Hide behind functions
Soft code in tables
Hide error codes with EXCEPTION_INIT
Use silly values for your magic values

Copyright 2011 Feuerstein and Associates

Page 250

Oracle PL/SQL Programming

Magical Values (Literals)

The most commonly recognized form of hardcoding.


The only place a literal should appear in your
code is in its SPOD.
Hide literals behind constants or functions.
Consider soft coding values in tables.
Copyright 2011 Feuerstein and Associates

Page 251

Oracle PL/SQL Programming

Hide Behind Constant


Instead of exposing the literal value, and
referencing it in multiple places, declare a
constant and reference that name.
Best to put such constants in a package
specification.
Can share across entire code base.

Constants are simple and quick, but they


expose the value in the package specification.
If the value needs to change, all programs that
depend on that package must be recompiled.
constant_vs_function.sql
Copyright 2011 Feuerstein and Associates

Page 252

Oracle PL/SQL Programming

Hide Behind Function


You can also define a function whose body
returns the value.
Best done in a package

Advantages over constants include


When the value changes, only the package body
must be recompiled.
Developers cannot "lazily" see/use value.
You can call the function in an SQL statement

But this is less efficient than a constant.


constant_vs_function.sql
Copyright 2011 Feuerstein and Associates

Page 253

Oracle PL/SQL Programming

Soft-Code Values in Table


You can make things really flexible by putting
all literals in a table, associating them with a
name, and retrieving them as needed from
the table.
Downsides are:
More complex code
More overhead, but caching can avoid this
problem.
soft_code_literals.sql
Copyright 2011 Feuerstein and Associates

Page 254

Oracle PL/SQL Programming

Use Silly Values for Constants


Sometimes you can choose the literals for
your magic values, such as with status codes.
If you use "reasonable" values for your
constants, people will be tempted to
"cheat."
Success = 0, Failure = 1, etc.

You might consider using "silly" values


instead.
Making it downright embarrassing to reference
the values explicitly.
fileIO92.pkg

Copyright 2011 Feuerstein and Associates

Page 255

Oracle PL/SQL Programming

Hide error codes with EXCEPTION_INIT


Oracle doesn't provide
a name for every error
code, but you can do
this.
Best place to put
exception declarations
is a package, so they
can be shared across
the application.
Copyright 2011 Feuerstein and Associates

WHEN OTHERS
THEN
IF SQLCODE = -24381
THEN
...
ELSIF SQLCODE = -1855
THEN
...
ELSE
RAISE;
END;

e_forall_failure EXCEPTION;
PRAGMA EXCEPTION_INIT (
e_forall_failure, -24381);
BEGIN
....
EXCEPTION
WHEN e_forall_failure
THEN
...
END;

Page 256

Oracle PL/SQL Programming

Conclusions
Magic value hard-coding is the most
commonly recognized form of hard-coding.
It is also the easiest to remove from your
code.
Use constants or functions to hide the value,
define that value in one place.
"Single point of definition" or SPOD

Copyright 2011 Feuerstein and Associates

Page 257

Oracle PL/SQL Programming

Avoiding Hard-Coded Declarations - Agenda


What's the problem?
Always fetch into records
Use %TYPE and %ROWTYPE whenever
possible
Use SUBTYPE to define application-specific
types

Copyright 2011 Feuerstein and Associates

Page 258

Oracle PL/SQL Programming

What's the problem?


Hard-coded declarations are declarations that
reference PL/SQL base types and/or constrain
values in some way.
Very often our variables hold data that is
stored elsewhere (columns of tables).
If the underlying column definition changes,
we can get VALUE_ERROR (ORA-06502) and
other errors.
Generally, our variable types can become "out
of synch" with the data.
hardcoding.sql
Copyright 2011 Feuerstein and Associates

Page 259

Oracle PL/SQL Programming

Fetch into list of variables


If your FETCH statement contains a list of
individual variables, you are hard-coding the
number of elements in the SELECT list.
When the cursor changes, you must change the
FETCH as well.

Solution: always fetch into a record, defined


with %ROWTYPE against the cursor.

fetch_into_record.sql
Copyright 2011 Feuerstein and Associates

Page 260

Oracle PL/SQL Programming

Do not expose constrained declarations


Every declaration requires a datatype.
If you are not careful, the way you specify that
datatype could be a hard-coding.
Generally, any declaration that relies on a
constrained datatype is a hard-coding.
BOOLEAN and DATE are unconstrained.

Another way to remember this is:


Consider every VARCHAR2(N) declaration to be
a bug unless it's a SPOD.
Copyright 2011 Feuerstein and Associates

Page 261

Oracle PL/SQL Programming

"SPODification" for Datatypes


Two problems with hard-coding the datatype:
Constraints can lead to errors in future.
The datatype does not explain the application
significance of the element declared.

Whenever possible, anchor the datatype of your


declaration to an already-existing type.
That way, if the existing type or SPOD ever changes,
then your code will be marked INVALID and
automatically recompiled to pick up the latest
version of the anchoring type.
Copyright 2011 Feuerstein and Associates

Page 262

Oracle PL/SQL Programming

%TYPE and %ROWTYPE


Use %TYPE for declarations based on columns
in tables.
Use %ROWTYPE for records based on tables,
views or cursors.
The lookup of the datatype from these
attributes occurs at compile-time.
There is no run-time overhead.

no_more_hardcoding.sql
Copyright 2011 Feuerstein and Associates

Page 263

Oracle PL/SQL Programming

SUBTYPEs
You can't always use %TYPE or %ROWTYPE in your
declaration.
You can, however, always define a "subtype" or
subset of an existing type with the SUBTYPE
statement. SUBTYPE benefits:
Avoid exposing and repeating constraints.
Give application-specific names to types. Critical when
working with complex structures like collections of
records, and nested collections.
Apply constraints, such as numeric ranges, to the variable
declared with the subtype.
Copyright 2011 Feuerstein and Associates

Page 264

Oracle PL/SQL Programming

SUBTYPE Details and Examples


SUBTYPE type_name IS data_type [ constraint ] [ NOT NULL ]

Define a subtype based on any pre-defined


type or other, already-defined subtype.
If the base type can be constrained, then you
can constrain the subtype.
(precision,scale) or RANGE

You can also, always specify NOT NULL.


Even if the base type could be NULL.
Copyright 2011 Feuerstein and Associates

subtype_examples.sql

Page 265

Oracle PL/SQL Programming

Applying SUBTYPEs
Two key scenarios:
Whenever you are about to write a VARCHAR2(N)
or other constrained declaration, define a subtype
instead, preferably in a package specification.
Instead of writing a comment explaining a
declaration, put the explanation into a subtype.
Instead
of this:
Write
this:

DECLARE
l_full_name VARCHAR2(100);
l_big_string VARCHAR2(32767);

DECLARE
l_full_name employees_rp.full_name_t;
l_big_string plsql_limits.maxvarchar2;

Copyright 2011 Feuerstein and Associates

fullname.pks
plsql_limits.pks
string_tracker3.*
Page 266

Oracle PL/SQL Programming

Conclusions
Declarations offer a danger of hard-coding of
both datatype and constraint on that type.
Assume that over time everything will change.
Apply the same "single point of definition"
principle to your declarations.
Use %TYPE and %ROWTYPE whenever possible.
Fall back on subtypes to define application specific
types and PL/SQL limits.

Copyright 2011 Feuerstein and Associates

Page 267

Oracle PL/SQL Programming

Stop Writing So Much SQL - agenda


What's the problem with writing lots of
SQL?
Why talk about SQL in a lesson on hardcoding?
Concepts behind and benefits of data
encapsulation
Data encapsulation recommendations

Copyright 2011 Feuerstein and Associates

Page 268

Oracle PL/SQL Programming

Writing SQL in PL/SQL


The most critical aspect of our programs.
SQL statements directly reflect our business
models.
And those models are always changing.

SQL statements cause most of the


performance problems in our applications.
Tuning SQL and the way that SQL is called in
PL/SQL overwhelms all other considerations.

Many runtime errors in applications result


from integrity and check constraints on tables.
Copyright 2011 Feuerstein and Associates

PagePage
269269

Oracle PL/SQL Programming

The fundamental problem with


SQL in PL/SQL
We take it entirely for
granted.
Why not? It's so easy to
write SQL in PL/SQL!

Order Entry Application

The Backend
Order
Table

Item
Table

Customer
We don't set rules on
Table
how, when and where
SQL should be written in
The result? Slow, buggy
PL/SQL.
code that is difficult to

optimize and maintain.


Copyright 2011 Feuerstein and Associates

Page 270

Oracle PL/SQL Programming

So set some SQL standards!


At a minimum, before starting your next
application, ask yourselves explicitly:
1. Are we taking full advantage of SQL,
particularly new features in our version?
You should do as much as possible in "pure" SQL.

2. Do we want standards or should we just do


whatever we want, whenever we want?
That way, you are making a conscious decision.

Copyright 2011 Feuerstein and Associates

Page 271

Oracle PL/SQL Programming

Fully leverage SQL in your PL/SQL code


Oracle continually adds significant new
functionality to the SQL language.
If you don't keep up with SQL capabilities, you
will write slower, more complicated PL/SQL
code than is necessary.
I am actually a good example of what you don't
want to do or how to be.

So take the time to refresh your


understanding of Oracle SQL in 10g and 11g.
Copyright 2011 Feuerstein and Associates

Page 272

Oracle PL/SQL Programming

Some exciting recently added SQL features


Courtesy of Lucas Jellema of AMIS Consulting
Analytical Functions
Especially LAG and LEAD; these allow you to look to previous and following
rows to calculate differences.

WITH clause (subquery factoring)


Allows the definition of 'views' inside a query that can be used and reused;
they allow procedural top-down logic inside a query

Flashback query
No more need for journal tables, history tables, etc.

ANSI JOIN syntax


Replaces the (+) operator and introduces FULL OUTER JOIN

SYS_CONNECT_BY_PATH and CONNECT_BY_ROOT for hierarchical queries


Scalar subquery
Adds a subquery to a query like a function call.

Copyright 2011 Feuerstein and Associates

select d.deptno
, (select count(*)
from emp e where
e.deptno = d.deptno)
number_staff from dept
Page 273

Oracle PL/SQL Programming

SQL statements as hard-codings


I suggest that every SQL statement you will ever
write is a hard-coding. Consider....
I need to write a complex query to return HR data
for a report.
SELECT . . .
FROM employees, departments, locations
WHERE . . . (a page full of complex conditions)

And Joe needs to use that same query in his


business rule procedure. And so on...
And then the three way join turns into a four way
join and we have to find all occurrences of this
query. A very tough thing to do!
Copyright 2011 Feuerstein and Associates

PagePage
274274

Oracle PL/SQL Programming

What to do about SQL hard coding


Of course, you have to (and should) write SQL
statements in your PL/SQL code.
PL/SQL is, in fact, the best place for SQL.

But we should be very careful about where,


when and how we write these statements.
Follow the principles; they are your guide.
Don't repeat anything!

The best approach: hide SQL statements


inside a data access layer.
Copyright 2011 Feuerstein and Associates

Page 275

Oracle PL/SQL Programming

SQL as a Service
Think of SQL as a service that is provided to you, not
something you write.
Or if you write it, you put it somewhere so that it can be
easily found, reused, and maintained.

This service consists of views and


programs defined in the data access
layer.
Views hide complex query construction
Packaged APIs for tables, transactions
and business entiries

Copyright 2011 Feuerstein and Associates

Application
Code

Intermediate Layer

Order
Table

Item
Table

Page 276

Oracle PL/SQL Programming

With a data access layer, I can...


Change/improve my implementation with
minimal impact on my application code.
The underlying data model is constantly changing.
We can depend on Oracle to add new features.
We learn new ways to take advantage of PL/SQL.

Vastly improve my SQL-related error handling.


Do you handle dup_val_on_index for INSERTs,
too_many_rows for SELECT INTOs, etc?

Greatly increase my productivity


I want to spend as much time as possible
implementing business requirements.
Copyright 2011 Feuerstein and Associates

11g_frc_demo.sql
11g_emplu.*

Page 277

Oracle PL/SQL Programming

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 usually an "extra stuff"


package with custom SQL
logic and related code:
<table>_XP
Copyright 2011 Feuerstein and Associates

Page 278

Oracle PL/SQL Programming

How to implement data encapsulation


It must be very consistent, well-designed and
efficient - or it will not be used.
Best solution: generate as much of the code as
possible.
This includes products like APEX and Hibernate
that generate lots of their own SQL for you.

Any custom SQL statements should be written


once and placed in a shareable container
(usually a package).
TAPI generator:
Quest CodeGen Utility at www.ToadWorld.com/SF
Copyright 2011 Feuerstein and Associates

Page 279

Oracle PL/SQL Programming

Conclusions
SQL statements are among the most critical
parts of your application.
You should have a clearly defined set of
guidelines about when, where and how to
write SQL.
Most important: Don't repeat SQL statements
(a form of hard-coding).

Copyright 2011 Feuerstein and Associates

Page 280

Oracle PL/SQL Programming

Hiding the Mechanics


When you hard-code a "magic value," you
write that value directly into your code as a
literal.
Over and over again; the value is "exposed".

When you "expose" the way you get things


done, you are hard-coding a particular
implementation.
The more you repeat it, the more difficult it is to
change that implementation in the future.
Copyright 2011 Feuerstein and Associates

Page 281

Oracle PL/SQL Programming

Everything Changes, Hide the Details


Even if the users don't change their minds, we
(developers) and Oracle technology change.
And let's face it: sometimes the way that Oracle
implements things is not ideal.

So assume that whatever you are working on


will change and hide it behind an API.
Logging errors and tracing execution
Calls to Oracle built-ins, like UTL_FILE.
Soft-code transaction boundaries
Watch out for SYSDATE
Copyright 2011 Feuerstein and Associates

Page 282

Oracle PL/SQL Programming

Logging Errors
We usually, but not always, want to write error
information out to a log table. How's this?
WHEN NO_DATA_FOUND
THEN
l_code := SQLCODE;
INSERT INTO errlog
VALUES ( l_code
, 'No company for id ' || TO_CHAR ( v_id )
, 'fixdebt', SYSDATE, USER );
WHEN OTHERS
THEN
l_code := SQLCODE; l_errm := SQLERRM;
INSERT INTO errlog
VALUES (l_code, l_errm, 'fixdebt', SYSDATE, USER );
RAISE;
END;

It's easy to "read" but only because it exposes


the logging mechanism.
Copyright 2011 Feuerstein and Associates

Page 283

Oracle PL/SQL Programming

Hide how and what you log


Don't call RAISE_APPLICATION_ERROR.
Don't explicitly insert into log table or write to file.
Don't call all the useful built-in functions in each
handler.
Do use a generic and shared error management utility.
Check out Quest Error Manager at PL/SQL Obsession for an example.
WHEN NO_DATA_FOUND
THEN
q$error_manager.register_error (
text_in => 'No company for id ' || TO_CHAR ( v_id ));
WHEN OTHERS
THEN
q$error_manager.raise_unanticipated (
name1_in => 'COMPANY_ID', value1_in => v_id);
END;
Copyright 2011 Feuerstein and Associates

Page 284

Oracle PL/SQL Programming

Execution Tracing (Instrumentation)


We often need to retrieve additional,
application-specific information from our code
while running.
Especially in production.

DBMS_OUTPUT.PUT_LINE is the "default"


tracing mechanism and should never appear
in your application code.
You are exposing the trace/display mechanism.
Too many drawbacks, too little flexibility.
Copyright 2011 Feuerstein and Associates

Page 285

Oracle PL/SQL Programming

Alternative Tracing Mechanisms


The Quest Error Manager available at
www.ToadWorld.com/SF offers built-in tracing.
The demo.zip watch.pkg offers lots of flexibility.
OTN samplecode logger utility, designed
primarily for use with APEX
The log4plsql open source utility
DBMS_APPLICATION_INFO
Writes to V$ views

Copyright 2011 Feuerstein and Associates

Page 286

Oracle PL/SQL Programming

Hide Calls to Oracle Built-ins


We all use those DBMS_ and UTL_ packages.
Very useful, but sometimes not implemented
in the best possible way.
Generally, I recommend avoiding making
direct calls to the built-ins.
Instead, create your own layer on top of the builtin.

Examples...
DBMS_OUTPUT.PUT_LINE (previously discussed)
UTL_FILE.GET_LINE
exec_ddl_from_file_bad.sql
Jfile.java, xfile.pkg
Copyright 2011 Feuerstein and Associates

Page 287

Oracle PL/SQL Programming

Hard-coded Transaction Boundaries


Every COMMIT; and ROLLBACK; in your PL/SQL
code represents a hard-coding of your
transaction boundary.
There's no going back.

Might sound silly, but deciding when to


commit can sometimes be a challenge.
Test vs Production
Incremental commit processing
Rollback segment challenges

And it can be very helpful to trace commits.


Copyright 2011 Feuerstein and Associates

Page 288

Oracle PL/SQL Programming

Committing in test and production


During test phase, we might want to easily reset
their test data back to its original form.
Sure, you could build a script to drop all the objects
and recreate them with the proper data, but that can
be time-consuming.

Instead, why not just comment out the COMMIT?


When I'm done testing, I will "un-comment" it.
A classic mistake. You finish
testing and debugging your
application -- and then you
change it to make it
"production ready".
Copyright 2011 Feuerstein and Associates

PROCEDURE my_monster_application
IS
BEGIN
Insert_a_bunch_of_rows;
Change_lots_more_data;
-- COMMIT;
END;
Page 289

Oracle PL/SQL Programming

Using the COMMIT Alternative


The following program uses the my_commit
package in place of an explicit COMMIT.
CREATE OR REPLACE PROCEDURE myapp (counter IN INTEGER)
IS
BEGIN
FOR cmtind IN 1 .. counter
LOOP
DELETE FROM emp2 WHERE ROWNUM < 2;
my_commit.perform_commit (
'DELETED ' || SQL%ROWCOUNT ||
' on iteration ' || cmtind);
END LOOP;
END;

While committing,
also pass trace
information.
Copyright 2011 Feuerstein and Associates

my_commit.*

Page 290

Oracle PL/SQL Programming

Watch out for hard-codings of SYSDATE


If you include direct references to SYSDATE in
your application, you will have reduced
flexibility when testing your code.
Hide SYSDATE behind a function returning
today's date.
Then you can override that "behind the scenes".

We do this in the PL/SQL Challenge (a daily


PL/SQL quiz) to make it easier to perform
weekday/weekend testing.
Copyright 2011 Feuerstein and Associates

Page 291

Oracle PL/SQL Programming

Hiding the Mechanics - Conclusions


Repeat after me: Everything is going to
change.
When you hide the mechanics, how you get
things done, behind a procedure or function,
you are "liberated."
Change the implementation, and you don't need
to change all the places in which it is used.

Back to that same principle:


Never Repeat Anything.
Aim for Single Point of Definition.
Copyright 2011 Feuerstein and Associates

Page 292

Oracle PL/SQL Programming

Hiding stuff a great career move!


By hiding all forms of hard-coding behind
subprograms, I am in a good position to....
Hide my mistakes
Does the query have a bug in it? OK, fix the one instance of
the query inside my function. I don't have to tell everyone
about it....

Get a promotion
I can improve my application code much more quickly than
those who hard-code SQL.....
The Result Cache is a great example of this.
Copyright 2011 Feuerstein and Associates

293

Page 293

Oracle PL/SQL Programming

Say goodbye to hard coding!


It's not all that difficult to do, once you
recognize all the different ways that hard
coding can manifest itself in your code.
Repeat nothing: become allergic to redundant
repetition.
Aim for a "single point of definition" in
everything you write.
Hide, hide, hide: values, implementations,
workarounds
Copyright
2010 Steven
Feuerstein - Page 294
Copyright
2011 Feuerstein
and Associates

Page 294

Oracle PL/SQL Programming

Your Reward
Elegant, functional code that you and others
can maintain easily
The respect of your peers
A deep sense of satisfaction with a job well
done
The opportunity to continue making a very
fine living, mostly from just thinking about
abstractions.

Copyright 2011 Feuerstein and Associates

Page 295

Oracle PL/SQL Programming

Extreme Modularization
Spaghetti code is the bane of
a programmer's existence.
It is impossible to understand
and therefore debug or
maintain code that has long,
twisted executable sections.
Fortunately, it is really easy to
make spaghetti code a thing
of the past.
Copyright 2011 Feuerstein and Associates

Organize your
code so that the
executable
section has no
more than fifty
lines of code.
Page 296

Oracle PL/SQL Programming

Fifty lines of code? That's ridiculous!


Of course you write lots more than 50 lines of
code in your applications.
The question is: how will you organize all that
code?
Turns out, it is actually quite straightforward
to organize your code so that it is transparent
in meaning, with a minimal need for
comments.
Key technique: local or nested subprograms.
Copyright 2011 Feuerstein and Associates

Page 297

Oracle PL/SQL Programming

Lets write some code!


My team is building a support application.
Customers call with problems, and we put
their call in a queue if it cannot be handled
immediately.
I must now write a program that distributes
unhandled calls out to members of the support
team.

Fifty pages of doc, complicated program!


But there is
an "executive
summary"
Copyright 2011 Feuerstein and Associates

While there are still unhandled calls in the queue, assign them to
employees who are under-utilized (have fewer calls assigned to
them then the average for their department).
Page 298

Oracle PL/SQL Programming

First: Translate the summary into code.


PROCEDURE distribute_calls (
department_id_in IN departments.department_id%TYPE)
IS
BEGIN
WHILE ( calls_are_unhandled ( ) )
LOOP
FOR emp_rec IN emps_in_dept_cur (department_id_in)
LOOP
IF current_caseload (emp_rec.employee_id)
<
avg_caseload_for_dept (department_id_in)
THEN
assign_next_open_call (emp_rec.employee_id);
END IF;
END LOOP;
END LOOP;
END distribute_calls;

A more or less direct translation. No need for comments, the


subprogram names "tell the story" but those subprograms
don't yet exist!
Copyright 2011 Feuerstein and Associates

Page 299

Oracle PL/SQL Programming

Explanation of Subprograms
Function calls_are_unhandled: takes no arguments,
returns TRUE if there is still at least one unhandled
call, FALSE otherwise.
Function current_caseload: returns the number of
calls (case load) assigned to that employee.
Function avg_caseload_for_dept: returns the average
number of calls assigned to employees in that
department.
Procedure assign_next_open_call: assigns the
employee to the call, making it handled, as opposed
to unhandled.
Copyright 2011 Feuerstein and Associates

Page 300

Oracle PL/SQL Programming

Next: Implement stubs for subprograms


PROCEDURE call_manager.distribute_calls (
department_id_in IN departments.department_id%TYPE)
IS
FUNCTION calls_are_handled RETURN BOOLEAN
IS BEGIN ... END calls_are_handled;
FUNCTION current_caseload (
employee_id_in IN employees.employee_id%TYPE) RETURN PLS_INTEGER
IS BEGIN ... END current_caseload;
FUNCTION avg_caseload_for_dept (
employee_id_in IN employees.employee_id%TYPE) RETURN PLS_INTEGER
IS BEGIN ... END current_caseload;
PROCEDURE assign_next_open_call (
employee_id_in IN employees.employee_id%TYPE)
IS BEGIN ... END assign_next_open_call;
BEGIN

These are all defined locally in the procedure.


Copyright 2011 Feuerstein and Associates

locmod_step_by_step.sql

Page 301

Oracle PL/SQL Programming

About local or nested subprograms


They can be called only from within the block in
which they are defined.
They can reference any variables defined in the parent
block.
Watch out for "global" references.

Only procedures and functions can be nested.


No packages within packages
No object types
No triggers

Use these instead of nested blocks.


You replace code with a name tell the story!
Copyright 2011 Feuerstein and Associates

Page 302

Oracle PL/SQL Programming

Next: Think about implementation of


just this level.

Think about what the programs need to do.


Think about if you or someone has already
done it. Dont reinvent the wheel!
Hey! Just last week I wrote another function that is very similar to
current_caseload. It is now "buried" inside a procedure named
show_caseload. I cant call it from distribute_calls, though. It is local,
private, hidden.
Should I copy and paste? No! I should extract the program and
expand its scope.

Copyright 2011 Feuerstein and Associates

Page 303

Oracle PL/SQL Programming

Next: Isolate and refactor common code.


CREATE OR REPLACE PACKAGE BODY call_manager
IS
FUNCTION current_caseload (
employee_id_in IN employees.employee_id%TYPE
, use_in_show_in IN BOOLEAN DEFAULT TRUE)
RETURN PLS_INTEGER
IS BEGIN ... END current_caseload;
PROCEDURE show_caseload (
department_id_in IN departments.department_id%TYPE)
IS BEGIN ... END show_caseload;

Note the increased


complexity,
needed to ensure backward
compatibility.

distribute
_calls

show_
caseload

PROCEDURE distribute_calls (
department_id_in IN departments.department_id%TYPE
)
IS BEGIN ... END distribute_calls;
END;

Now current_caseload is at the package


level and can be called by any program in
the package.
Copyright 2011 Feuerstein and Associates

locmod_step_by_step.sql

current_
caseload

Page 304

Oracle PL/SQL Programming

Next: Reuse existing code whenever possible.


Just last week, Sally emailed all of us with news of
her call_util package.
Returns average workload of employee and much more.
Just what I need! Dont have to build it myself, just call it.
BEGIN
WHILE ( calls_are_unhandled ( ) )
LOOP
FOR emp_rec IN emps_in_dept_cur (department_id_in)
LOOP
IF current_caseload (emp_rec. employee_id) <
call_util.dept_avg_caseload (department_id_in)
THEN
assign_next_open_call (emp_rec.employee_id);
END IF;
END LOOP;
END LOOP;
This program has the widest scope possible: it can be
END distribute_calls;
executed by any schema with execute authority on the

call_util package, and by any program within the owning


schema.
Copyright 2011 Feuerstein and Associates

Page 305

Oracle PL/SQL Programming

Next: Implement whats left.


Now I am left only with program-specific,
nested subprograms.
So I move down to the next level of detail and
apply the same process.
Write the executive summary first.
Keep the executable section small.
Use local modules to hide the details.

Eventually, you get down to the real code


and can deal with the actual data structures
and algorithms without being overwhelmed.
Copyright 2011 Feuerstein and Associates

locmod_step_by_step.sql
topdown*.*

Page 306

Oracle PL/SQL Programming

Challenges of Nested Subprograms


Requires discipline: always be on the lookout for
opportunities to refactor.
Need to read from the bottom, up.
Takes some getting used to.

Sometimes can feel like a "wild goose chase".


Where is the darned thing actually implemented?
Your IDE should help you understand the internal
structure of the program.

You cannot directly test nested subprogams.


But how do you decide when a module should be
local or defined at a higher level?
Copyright 2011 Feuerstein and Associates

Page 307

Oracle PL/SQL Programming

Rule: Define subprograms close to usage.


When should the program be nested? Private
to the package? Publicly accessible?
The best rule to follow is:
Define your subprograms as close as
possible to their usage(s).
The shorter the distance from usage to
definition, the easier it is to find, understand
and maintain that code.

Copyright 2011 Feuerstein and Associates

Page 308

Oracle PL/SQL Programming

Conclusions Nested Subprograms


Write tiny chunks of code.
Your programs will be transparent and readable,
to you and everyone else.
They will contain many fewer bugs.

The quality of your code will be instantly


transformed!
"All" it takes is discipline.
No special tools required (but a top-notch IDE to
will make your job much easier).
Copyright 2011 Feuerstein and Associates

Page 309

Oracle PL/SQL Programming

Make the Most of Oracle PL/SQL!


This language is not evolving very rapidly
these days (less change than in SQL).
Make sure that you are aware of key new (and
existing) features, and put them to use.
Always prioritize the maintainability of your
code.
It's going to be around for YEARS to come!

Copyright 2011 Feuerstein and Associates

Page 310

Oracle PL/SQL Programming

Websites for PL/SQL Developers


www.plsqlchallenge.com
Daily PL/SQL quiz with weekly and
monthly prizes

www.plsqlchannel.com
27+ hours of detailed video training
on Oracle PL/SQL

www.stevenfeuerstein.com
Monthly PL/SQL newsletter

www.toadworld.com/SF
Quest Software-sponsored portal
for PL/SQL developers

Copyright 2011 Feuerstein and Associates

Page 311

Você também pode gostar