0% found this document useful (0 votes)
283 views147 pages

Making The Most of The Best of PLSQL-Minneapolis PDF

Uploaded by

RUPAM CHOWDHURY
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
283 views147 pages

Making The Most of The Best of PLSQL-Minneapolis PDF

Uploaded by

RUPAM CHOWDHURY
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 147

Oracle PL/SQL Programming

Oracle PL/SQL Programming


Things to change:
• Overloading: for use in SQL, int vs bool
• dbms_trace in tracing
• API is a contract/backward compatibility

Making the Most


of the Best
of Oracle PL/SQL

Steven Feuerstein steven@stevenfeuerstein.com


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 2013 Feuerstein and Associates Page 2


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 2013 Feuerstein and Associates Page 3


Oracle PL/SQL Programming

Making the Most of the Best of Oracle


PL/SQL - What's the Best?
• Packages - the fundamental building block for PL/SQL apps
• Compiler optimization
• CASE statement and expression
• Autonomous Transactions
• Collections and set operations
• BULK COLLECT and FORALL
• Function Result Cache
• Table Functions
• NOCOPY hint
• Subtypes
• Nested subprograms

Copyright 2013 Feuerstein and Associates Page 4


Oracle PL/SQL Programming

How do we make THE MOST of all that?


• Declare First, Program Second
– Lift heavy only when necessary
• Craft Excellent APIs
– Best form of communication
• Never Repeat Anything
– For low maintenance code
• Program Socially
– Never code alone. Note: some of the "best"
items will be explored in
the "most" section.

Copyright 2013 Feuerstein and Associates Page 5


Oracle PL/SQL Programming

Here's the Plan


• First, cover many of the "best" features, so
that everyone knows what they are and what
they do for you.
– More an overview than in-depth training
• Next, tackle each "most", bringing in specific
PL/SQL features as is relevant.
• Finally, a quiz! And prizes!
• And now… on to our "best" features.

Copyright 2013 Feuerstein and Associates Page 6


Oracle PL/SQL Programming

Compiler Optimization
• As of Oracle Database 10g, the compiler will
automatically optimize your code.
– Default setting of 2 is best, but 11g also offers a
new level 3 "inlining" optimization.
• The optimizer takes advantage of "freedoms"
to re-order 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.

Copyright 2013 Feuerstein and Associates Page 7


Oracle PL/SQL Programming

Some Optimizer Examples


... A + B ... T := A + B;
... T ... T is a generated variable. We never see
... it. And one operation is saved.
... A + B ... ...
... T ...

for i in 1 .. 10 loop
A := B + C;
...
end loop; Automatic relocation of a loop invariant.
Avoid repetitive computations.
A := B + C;
for i in 1 .. 10 loop
...
end loop;

FOR rec in (SELECT ...)


SELECT ...
LOOP
BULK COLLECT INTO ... Execute cursor FOR loop
... do stuff
FROM ... at BULK COLLECT
END LOOP;
levels of performance.

10g_optimize_cfl.sql
Copyright 2013 Feuerstein and Associates Page 8
Oracle PL/SQL Programming

Optimizer Bottom Line


• Use the default setting.
– Check ALL_PLSQL_OBJECT_SETTINGS for
violations.
• Apply the inlining pragma selectively if
needed.
• Forget all about it and enjoy the benefits!
• OTN offers several whitepapers on the
optimizer for those who want more.
all_plsql_object_settings.sql

Copyright 2013 Feuerstein and Associates Page 9


Oracle PL/SQL Programming

CASE
• Added to PL/SQL in 9i (earlier in SQL), you can
use CASE statements and expressions to
replace IF statements.
• CASE expressions are especially good at
replacing multiple IF statements with a single
expression.
– When building a complex string for example.
• Most lengthy ELSIFs should be replaced with a
CASE statement.
case*.sql

Copyright 2013 Feuerstein and Associates Page 10


Oracle PL/SQL Programming

Autonomous Transactions
• Default transaction behavior in PL/SQL is at the session
level
– A commit saves all outstanding changes in your session.
– A rollback erases all outstanding changes in your session.
• Define a PL/SQL block as an "autonomous transaction" to
control the scope of commit/rollback.
– Any changes made within that block will be saved or reversed
without affecting the outer or main transaction.
• Two rules for autonomous transactions:
– Must include the autonomous transaction pragma.
– Must commit or rollback before exiting the block if any DML
statements were executed.
• Most common application: error logging

Copyright 2013 Feuerstein and Associates Page 11


Oracle PL/SQL Programming

Logging with Autonomous Transactions


CREATE OR REPLACE PACKAGE BODY log
IS
PROCEDURE putline (
code_in IN INTEGER, text_in IN Avoid inter-
VARCHAR2 dependencies with
) the main
IS transaction.
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO logtab
VALUES (code_in, text_in,
SYSDATE, USER, SYSDATE, USER,
rec.machine, rec.program Save on
); successful exit
COMMIT;
Don't forget to
EXCEPTION
rollback on error!
WHEN OTHERS THEN ROLLBACK;
END;
logger.sp
END; log81.pkg
retry.pkg
retry.tst
log81*.tst

Copyright 2013 Feuerstein and Associates Page 12


Oracle PL/SQL Programming

Collections
• Collections are PL/SQL's implementation of
arrays.
• All collections in PL/SQL are single
dimensional lists or sets.
• They provide the foundation for many
performance optimization features.
– Bulk processing, table functions and more.
• They can consume lots of PGA memory.

Copyright 2013 Feuerstein and Associates Page 13


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 2013 Feuerstein and Associates Page 14


Oracle PL/SQL Programming

Different Types of Collections


• Three types of collections, with different
characteristics and use cases.
• Associative array
– Use in PL/SQL only. Most flexible in that scope.
• Nested table
– Use in PL/SQL and SQL. Lots of set-related operations.
• Varray (varying arrays)
– Use in PL/SQL and SQL, but unlikely to ever use them.
• Collections come "with" methods.

Copyright 2013 Feuerstein and Associates Page 15


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 2013 Feuerstein and Associates method_vs_proc.sql Page 16


Oracle PL/SQL Programming

Collections Methods
• 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.
• DELETE
– remove elements from a collection
• EXTEND
– add elements to a nested table or varray

Copyright 2013 Feuerstein and Associates Page 17


Oracle PL/SQL Programming

About 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.
assoc_array_example.sql
emplu.pkg, emplu.tst
string_tracker*.*
Copyright 2013 Feuerstein and Associates Page 18
Oracle PL/SQL Programming

About Nested Tables


CREATE OR REPLACE TYPE list_of_names_t IS TABLE OF NUMBER;

• A nested table is a type of collection, Unordered set


which, according to Oracle documentation, of elements

"models an unordered set of elements."


– It is a "multiset": like a relational table, there is 1 Apple
no inherent order to its elements, and
duplicates are allowed/significant. 2 Pear
3 Orange
• From a practical standpoint, you can
4 Apricot
access nested table elements through an
5 Pear
integer index.
• MULTISET operators allow set-level
operations on nested tables. Integer index
also available

nested_table_example.sql
Copyright 2013 Feuerstein and Associates Page 19
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 2013 Feuerstein and Associates Page 20


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).
10g_compare*.sql 10g_union.sql
10g_set.sql 10g_intersect.sql
Copyright 2013 Feuerstein and Associates 10g_member_of.sql 10g_minus.sql Page 21
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 2013 Feuerstein and Associates Page 22


Oracle PL/SQL Programming

Bulk Processing of SQL in PL/SQL


(BULK COLLECT and FORALL)
• 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 2013 Feuerstein and Associates Page 23


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


The result? Simple and
UPDATE employee SET salary = rec.salary elegant but inefficient...
WHERE employee_id = rec.employee_id;
END LOOP;
Why is this?
END upd_for_dept;

Copyright 2013 Feuerstein and Associates Page 24


Oracle PL/SQL Programming

Repetitive statement processing from PL/SQL


Oracle server

PL/SQL Runtime Engine SQL Engine


PL/SQL block
Procedural
statement
FOR rec IN emp_cur LOOP executor
UPDATE employee SQL
SET salary = ... statement
WHERE employee_id = executor
rec.employee_id;
END LOOP;

Performance penalty
for many “context
switches”
Copyright 2013 Feuerstein and Associates Page 25
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 2013 Feuerstein and Associates Page 26


Oracle PL/SQL Programming

Bulk processing with FORALL


Oracle server

PL/SQL Runtime Engine SQL Engine


PL/SQL block
Procedural
FORALL indx IN statement
list_of_emps.FIRST..
list_of_emps.LAST
executor
SQL
UPDATE employee
SET salary = ...
statement
WHERE employee_id = executor
list_of_emps(indx);

Update... Update...
Update... Update...
Update... Update...
Update... Update...
Update... Update...
Update... Fewer context switches, Update...
same SQL behavior
Copyright 2013 Feuerstein and Associates Page 27
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 statement-
level triggers only fire once per FORALL INSERT
statements.
– Not for each INSERT statement passed to the SQL engine
from the FORALL statement.

statement_trigger_and_forall.sql
Copyright 2013 Feuerstein and Associates Page 28
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 collections with a


single context switch to the SQL engine.
– Collection filled sequentially from 1.
• Use the LIMIT clause to constrain PGA memory
consumption.
– 100 is good default, but experiment with higher values.
• When populating nested tables and varrays,
Oracle automatically initializes and extends.
bulkcoll.sql
bulkcollect.tst
Copyright 2013 Feuerstein and Associates bulklimit.sql, cfl_to_bulk0.sql Page 29
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)
forall_timing.sql
Copyright 2013 Feuerstein and Associates forall_examples.sql Page 30
Oracle PL/SQL Programming

More on FORALL
• Use the SQL%BULK_ROWCOUNT cursor
attribute to determine how many rows are
modified by each statement.
– SQL%ROWCOUNT returns total for FORALL.
• Use SAVE EXCEPTIONS and
SQL%BULK_EXCEPTIONS to execute all
statements, saving errors for later.
– You will need to handle ORA-24381.
• When collections may be sparse, use INDICES
OF or VALUES OF.
bulk_rowcount.sql
bulkexc.sql
10g_indices_of*.sql
Copyright 2013 Feuerstein and Associates 10g_values_of.sql Page 31
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 2013 Feuerstein and Associates Page 32


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 2013 Feuerstein and Associates Page 33
Oracle PL/SQL Programming

A phased approach with bulk processing


• Change from integrated, row-by-row approach to
a phased approach.
Phase 1: Bulk collect from table(s) to collection
Relational
Table

Phase 2: Modify contents of collection


according to requirements

Relational
Table
Phase 3: FORALL from collection to table
Copyright 2013 Feuerstein and Associates Page 34
Oracle PL/SQL Programming

Translating phases into code


• The cfl_to_bulk_5.sql file contains the
converted program, following the phased
approach.
BEGIN
Phase 1: OPEN employees_cur;
Get Data
LOOP
Phase 3: fetch_next_set_of_rows (
Push Data bulk_limit_in, employee_ids, salaries, hire_dates);
EXIT WHEN employee_ids.COUNT = 0;
Phase 2: insert_history;
Massage Data adj_comp_for_arrays (employee_ids, salaries);
update_employee;
END LOOP;
Phase 3:
END upd_for_dept;
Push Data

cfl_to_bulk_0.sql
Copyright 2013 Feuerstein and Associates cfl_to_bulk_5.sql Page 35
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 2013 Feuerstein and Associates Page 36


11g Oracle PL/SQL Programming

The Oracle 11g Function Result Cache


• Many of our queries fetch the same data many times,
even if it hasn't changed.
– Paying the price of executing SQL, no matter how
optimized.
• Use the function result cache to tell Oracle to return
unchanged data, without hitting the SQL layer.
• 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.

11g_frc_demo.sql
Copyright 2013 Feuerstein and Associates Page 37
11g Oracle PL/SQL Programming

Performance Impact of Result Cache


• The result cache is stored in the SGA.
• So we should expect it be slower than a PGA-
based cache.
• But accessing result cache data does not
require going through the SQL engine.
• So it should be much faster than executing a
query.
– Even if the statement is parsed and the data
blocks are already in the SGA.
• Let's find out!

11g_emplu*.*
Copyright 2013 Feuerstein and Associates Page 38
11g Oracle PL/SQL Programming

Result Cache – Things to Keep in Mind


• If you have uncommitted changes in your session,
dependent caches are ignored.
– The cache will not override your own changed data.
• You can't cache everything.
– Oracle has to be able to do an "=" comparison.
• 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.
• Work with your DBA to identify the "sweet spots" for
applying this feature.
11g_frc_demo.sql
11g_frc_vpd.sql
Copyright 2013 Feuerstein and Associates 11g_frc_vpd2.sql Page 39
Oracle PL/SQL Programming

Table Functions SELECT COLUMN_VALUE


FROM TABLE (my_function (. . .))

• 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.
• 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.
• Improve performance for….
– Parallel queries
– User perceptions of elapsed time

Copyright 2013 Feuerstein and Associates Page 40


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.

tabfunc_scalar.sql
Copyright 2013 Feuerstein and Associates
tabfunc_streaming.sql Page 41
Oracle PL/SQL Programming

Pipelined functions 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 2013 Feuerstein and Associates Page 42


Oracle PL/SQL Programming

Applications for pipelined functions


• Execution functions in parallel.
– 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!

tabfunc_pipelined.sql

Copyright 2013 Feuerstein and Associates Page 43


Oracle PL/SQL Programming

Optimizing Table Functions in SQL


• A function called in SQL is a "black box" to the
optimizer - unless you provide statistics on the
costs and selectivities of that function.
– Use ASSOCIATE STATISTICS or hints or both.
SQL> ASSOCIATE STATISTICS WITH FUNCTIONS high_cpu_io DEFAULT COST (6747773, 21, 0);

SQL> SELECT /*+ OPT_ESTIMATE(table, e, scale_rows=2.62) */


2 *
3 FROM departments d
4 , TABLE(employees_piped) e
5 WHERE d.department_id = e.department_id;

SQL> SELECT /*+ DYNAMIC_SAMPLING(e, 2) */ * 2


2 FROM TABLE(employees_piped) e;

Resources http://www.oracle-developer.net/display.php?id=426
http://www.oracle-developer.net/display.php?id=427
http://www.dbprof.com/index.php?option=com_jdownloads&Itemid=57&view=viewcategory&catid=3

Copyright 2013 Feuerstein and Associates Page 44


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.
• Use pipelined table functions for performance
improvement and reduced PGA consumption.

Copyright 2013 Feuerstein and Associates Page 45


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 2013 Feuerstein and Associates Page 46


Oracle PL/SQL Programming

That's a LOT of BEST…


Now it's Time for the MOST
• Whew.
• That's a lot of functionality.
• And even if you know all about all of it, you
still have to figure out how to use it so that
your application is easy to:
– Understand
– Maintain (fix and enhance)

Copyright 2013 Feuerstein and Associates Page 47


Oracle PL/SQL Programming

The Big Four


• Declare First, Program Second
– Lift heavy (write code) only when necessary
• Craft Excellent APIs
– It's the best form of communication
• Never Repeat Anything
– For low maintenance code
• Program Socially
– Never code alone.

Copyright 2013 Feuerstein and Associates Page 48


Oracle PL/SQL Programming

Declare First, Program Second


DDL • Maximize the declarative aspects
of your technology before you
start writing algorithms.
DML • Get the data model right.
– Use constraints and triggers

PL/SQL • Then maximize the SQL language


and all its latest features.
• Only then should you start writing
Java/.Net programs/algorithms.

Copyright 2013 Feuerstein and Associates Page 49


Oracle PL/SQL Programming

Maximize the SQL Language


• Courtesy of Lucas Jellema of AMIS Consulting, Netherlands
• Analytical Functions
– Especially LAG and LEAD; these allow you to look to previous and following rows to
calculate differences. But also RANK, PIVOT, etc.
• 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 select d.deptno
• Scalar subquery , (select count(*)
– Adds a subquery to a query like a function call. from emp e where
e.deptno = d.deptno)
• And soon…Oracle12c new SQL features! number_staff from dept

Check out the SQL quizzes at the PL/SQL Challenge!


Copyright 2013 Feuerstein and Associates Page 50
Oracle PL/SQL Programming

Leverage Declarative Statements in PL/SQL


• SQL, first and foremost - inside PL/SQL
• Cursor FOR loop
– Automatic optimization demonstrates the benefit
• FORALL statement
• More generally, don't reinvent the wheel when
built-in functions can do the heavy lifting.
– Regular expressions
– All nuances of string functions
– TRUNC and TO_CHAR

Copyright 2013 Feuerstein and Associates Page 51


Oracle PL/SQL Programming

Craft Excellent APIs


• An API is an application program
interface.
– A set of procedures and functions (and
more) that can be used as "building
blocks" for application construction.
• Clean, well-designed APIs hide details,
reduce overall code volume, improve
productivity, and the lower the
do_X
frequency and severity of bugs.
• In PL/SQL, packages are the best way do_Y
to build APIs, though you can also do
so in object types. return_Z

Copyright 2013 Feuerstein and Associates Page 52


Oracle PL/SQL Programming

Building with Packages


• Employ object-oriented design principles
– Build at higher levels of abstraction
– Enforce information hiding - control what people see and
do
– Call packaged code from object types and triggers
• Encourages top-down design and bottom-up
construction
– TD: Design the interfaces required by the different
components of your application without addressing
implementation details
– BU: packages contain building blocks for new code
• Organize your stored code more effectively
• Implements session-persistent data
sf_timer.* dbms_errlog_helper.sql
plsql_memory.* errpkg.pkg
assert.pkg loop_killer.pkg
Copyright 2013 Feuerstein and Associates Page 53
Oracle PL/SQL Programming

Package Data: Useful and (but?) Sticky


• The scope of a package is your session, and any data
defined at the "package level" also has session scope.
– If defined in the package specification, any program can
directly read/write the data.
– Ideal for program-specific caching.
• Attention must be paid:
– Package cursors must be explicitly closed.
– Collection contents must be explicitly deleted.
– Clean-up will not occur automatically on close of block.
• Note that with connection pools and stateless
applications, you should not rely on package state -
between server calls.
thisuser.*
emplu.pkg
emplu.tst
Copyright 2013 Feuerstein and Associates Page 54
Oracle PL/SQL Programming

More on Package-level Data


• Hide your package data in the body so that you can
control access to it.
– Only constants and "scratch" variables should be placed in
the specification.
– Build "get and set" subprograms
• Use the SERIALLY_REUSABLE pragma to move data to
SGA and have memory released after each usage.
– This also means that the package is re-initialized with each
server call.
• A package with at least one variable has state, and
that can greatly complicate recompilation.
– Watch out for the ORA-04068 errors!
– If you have these, check out Edition-Based Redefinition.
serial.sql
valerr.pkg
Copyright 2013 Feuerstein and Associates Page 55
Oracle PL/SQL Programming

Package Initialization
• The initialization section:
PACKAGE BODY pkg
– Is defined after and outside of any IS
programs in the package. PROCEDURE proc IS
BEGIN
– Is not required. In fact, most packages you END;
build won't have one.
– Can have its own exception handling FUNCTION func RETURN
section. BEGIN
END;
• Useful for: BEGIN
...initialize...
– Performing complex setting of default or END pkg;
initial values.
– Setting up package data which does not
change for the duration of a session.
BEGIN after/outside
– Confirming that package is properly of any program
instantiated. defined in the pkg.

init.pkg
init.tst
Copyright 2013 Feuerstein and Associates datemgr.pkg Page 56
Oracle PL/SQL Programming

Overloading in Packages:
key API/usability technique myproc
• Overloading (static polymorphism): two or more
myfunc
programs with the same name, but different
signature. myproc
– You can overload in the declaration section of any
PL/SQL block, including the package body (most
common).
• Overloading is a critical feature when building
comprehensive programmatic interfaces (APIs) or
components using packages.
– If you want others to use your code, you need to make
that code as smart and as easy to use as possible.
– Overloading transfers the "need to know" from the user
to the overloaded program.
Compare:
DBMS_OUTPUT and p packages dynamic_polymorphism.sql
Copyright 2013 Feuerstein and Associates Page 57
Oracle PL/SQL Programming

How Overloading Works


• For two or more modules to be overloaded, the
compiler must be able to distinguish between the
two calls at compile-time.
– Another name for overloading is "static
polymorphism."
• There are two different "compile times":
– 1. When you compile the package or block containing
the overloaded code.
– 2. When you compile programs that use the overloaded
code.

Copyright 2013 Feuerstein and Associates Page 58


Oracle PL/SQL Programming

How Overloading Works, continued


• Distinguishing characteristics:
– The formal parameters of overloaded modules must
differ in number, order or datatype family (CHAR vs.
VARCHAR2 is not different enough).
– The programs are of different types: procedure and
function.
• Undistinguishing characteristics:
– Functions differ only in their RETURN datatype.
– Arguments differ only in their mode (IN, OUT, IN OUT).
– Their formal parameters differ only in datatype and the
datatypes are in the same family.

Copyright 2013 Feuerstein and Associates Page 59


Oracle PL/SQL Programming

Quiz! Nuances of Overloading


• Will these specifications compile? If so, can I call the
subprograms?
CREATE OR REPLACE PACKAGE sales
IS
PROCEDURE calc_total (zone_in IN VARCHAR2);

PROCEDURE calc_total (reg_in IN VARCHAR2);


END sales;

CREATE OR REPLACE PACKAGE sales


IS
PROCEDURE calc_total (zone_in IN CHAR);

PROCEDURE calc_total (zone_in IN VARCHAR2);


END sales;

ambig_overloading.sql
Copyright 2013 Feuerstein and Associates Page 60
Oracle PL/SQL Programming

Tips for Optimal Overloading


• Don't repeat code across implementations.
– Single point of definition!
• Most overloadings involve doing mostly the
"same thing", but with different combinations
of data.
• So make sure that in the package body, all
overloadings are based on the same "core."
• Don't overload "just in case".
– Avoid hypothetically useful code.
lazy_overloading.sql
dynamic_polymorphism.sql
Copyright 2013 Feuerstein and Associates Page 61
Oracle PL/SQL Programming

Tips for Writing Package Bodies


• A well-constructed API (interface) is
determined by the package specification.
• But the way we implement that API in the
package body has enormous ramifications on
maintainability.
• You must make very careful decisions about
how to modularize your code.
– Avoid spaghetti code
– Expose only the functionality that is needed "out
there"

Copyright 2013 Feuerstein and Associates Page 62


Oracle PL/SQL Programming

Modularization Choices
• You can choose from:
– Schema-level procedure or function
– Public packaged subprogram
– Private packaged subprogram
– Nested subprogram
• Avoid schema-level programs; put all your code in
packages.
– Entire package is loaded into memory
– Each package provides a "namespace" in which to
organize related code.
• Use nested subprograms to improve readability
and maintainability of your bodies.

Copyright 2013 Feuerstein and Associates Page 63


Oracle PL/SQL Programming

Extreme Modularization
(Write tiny chunks of code)
• Spaghetti code is the bane of
a programmer's existence.
• It is impossible to understand
and therefore debug or
maintain code that has long, Organize your
twisted executable sections. code so that the
executable
• Fortunately, it is really easy to
section has no
make spaghetti code a thing more than fifty
of the past. lines of code.

Copyright 2013 Feuerstein and Associates Page 64


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 2013 Feuerstein and Associates Page 65


Oracle PL/SQL Programming

Let’s 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 While there are still unhandled calls in the queue, assign them to
an "executive employees who are under-utilized (have fewer calls assigned to
summary" them then the average for their department).

Copyright 2013 Feuerstein and Associates Page 66


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 2013 Feuerstein and Associates Page 67
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 2013 Feuerstein and Associates Page 68


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.


locmod_step_by_step.sql
Copyright 2013 Feuerstein and Associates Page 69
Oracle PL/SQL Programming

About 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 2013 Feuerstein and Associates Page 70


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. Don’t 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 can’t 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 2013 Feuerstein and Associates Page 71


Oracle PL/SQL Programming

Next: Isolate and refactor common code.


CREATE OR REPLACE PACKAGE BODY call_manager
IS
FUNCTION current_caseload ( Note the increased
employee_id_in IN employees.employee_id%TYPE complexity,
, use_in_show_in IN BOOLEAN DEFAULT TRUE) needed to ensure backward
RETURN PLS_INTEGER compatibility.
IS BEGIN ... END current_caseload;

PROCEDURE show_caseload (
department_id_in IN departments.department_id%TYPE)
IS BEGIN ... END show_caseload; distribute show_
_calls caseload
PROCEDURE distribute_calls (
department_id_in IN departments.department_id%TYPE
)
IS BEGIN ... END distribute_calls;
END;
current_
caseload
• Now current_caseload is at the package
level and can be called by any program in
the package.
locmod_step_by_step.sql
Copyright 2013 Feuerstein and Associates Page 72
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! Don’t 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 2013 Feuerstein and Associates Page 73
Oracle PL/SQL Programming

Next: Implement what’s 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.
locmod_step_by_step.sql
topdown*.*
Copyright 2013 Feuerstein and Associates Page 74
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 2013 Feuerstein and Associates Page 75


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 2013 Feuerstein and Associates Page 76


Oracle PL/SQL Programming

Craft Excellent APIs - Conclusion


• The more clearly you define and control
access to underlying functionality, the easier it
will be to use and maintain your code.
• Ideally, a developer never needs to look at the
package body to use that code.
• It's a real joy to be able to
construct new, complex programs
by pulling out pre-tested units
from your "set of blocks."

Copyright 2013 Feuerstein and Associates Page 77


Oracle PL/SQL Programming

Never Repeat Anything


• The single most important guideline for writing high
quality code.
• Repetition => hard coding => exposed
implementation => maintenance nightmare.
• Instead, aim for a Single Point of Definition (SPOD)
for every aspect of your application.
• The hardest part of doing this can be recognizing the
different ways that hard-coding can creep into your
code.

hardcoding2.sql

Copyright 2013 Feuerstein and Associates Page 78


Oracle PL/SQL Programming

Never Repeat Anything - Specifically….


• Hide magic values
• Hides constrained declarations
• Use a shared error logging utility
• Use a shared execution tracer
• Build a data encapsulation layer for SQL

Copyright 2013 Feuerstein and Associates Page 79


Oracle PL/SQL Programming

Hide Magical Values (Literals)

• The most commonly recognized form of hard-


coding.
• 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 2013 Feuerstein and Associates Page 80


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 2013 Feuerstein and Associates Page 81
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 2013 Feuerstein and Associates Page 82
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 2013 Feuerstein and Associates Page 83


Oracle PL/SQL Programming

Hide error codes with EXCEPTION_INIT


WHEN OTHERS

• Oracle doesn't provide THEN


IF SQLCODE = -24381
THEN
a name for every error ...
ELSIF SQLCODE = -1855
THEN
code, but you can do ...
ELSE

this. END;
RAISE;

• Best place to put


exception declarations e_forall_failure EXCEPTION;
is a package, so they PRAGMA EXCEPTION_INIT (
e_forall_failure, -24381);
BEGIN
can be shared across ....
EXCEPTION

the application. WHEN e_forall_failure


THEN
END;
...

Copyright 2013 Feuerstein and Associates errpkg.pkg Page 84


Oracle PL/SQL Programming

Hide 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.
– VARCHAR2(n) is constrained; BOOLEAN and DATE are
unconstrained.
• 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.

Copyright 2013 Feuerstein and Associates Page 85


Oracle PL/SQL Programming

"SPODification" for Datatypes


Consider every VARCHAR2(N) declaration to be
a bug – unless it's a SPOD.

• 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.
• Use %TYPE and %ROWTYPE whenever possible
– Fetch into record, never list of variables
• Use SUBTYPEs when anchoring is not possible.
Copyright 2013 Feuerstein and Associates Page 86
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 2013 Feuerstein and Associates Page 87
Oracle PL/SQL Programming

Fetch into record, not 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 2013 Feuerstein and Associates Page 88
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 2013 Feuerstein and Associates Page 89


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.

subtype_examples.sql
Copyright 2013 Feuerstein and Associates Page 90
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.

DECLARE
Instead l_full_name VARCHAR2(100);
of this: l_big_string VARCHAR2(32767);

DECLARE fullname.pks
Write plsql_limits.pks
l_full_name employees_rp.full_name_t;
this: l_big_string plsql_limits.maxvarchar2; string_tracker3.*

Copyright 2013 Feuerstein and Associates Page 91


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 limitations.

Copyright 2013 Feuerstein and Associates Page 92


Oracle PL/SQL Programming

Error Management and Logging


• Handling problems gracefully and effectively is
critical.
• You need to know what Oracle offers to help
you diagnose issues.
• And you need to make sure error handling and
logging is not a mess of hard-codings.

Copyright 2013 Feuerstein and Associates Page 93


Oracle PL/SQL Programming

Oracle Built-ins For Handling Exceptions


• In addition to the application-specific
information you may want to log, Oracle built-
ins 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 2013 Feuerstein and Associates Page 94


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 2013 Feuerstein and Associates Page 95
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 2013 Feuerstein and Associates Page 96


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 2013 Feuerstein and Associates Page 97


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 2013 Feuerstein and Associates Page 98


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 2013 Feuerstein and Associates Page 99


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 2013 Feuerstein and Associates Page 100
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 2013 Feuerstein and Associates Page 101
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 those 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 2013 Feuerstein and Associates qem_demo.sql Page 102


Oracle PL/SQL Programming

Execution Tracing (Instrumentation)


• Tracing, also known as instrumentation, is an
important technique for diagnosing
production issues and quickly resolving them.
– Also helpful as you build your code.
• Sadly, too many developers do not sufficiently
instrument their code and too many use the
wrong instrument.
– You know what I'm talking about:
DBMS_OUTPUT.PUT_LINE!

Copyright 2013 Feuerstein and Associates Page 103


Oracle PL/SQL Programming

Doesn't Oracle do tracing for us?


• Sure, but 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
(hard-coding).
– Too many drawbacks, too little flexibility.

Copyright 2013 Feuerstein and Associates Page 104


Oracle PL/SQL Programming

Tips on Instrumenting Code


• Don't remove trace calls when you are "done"
writing your program.
– You will need it for production problem diagnoses.
• Minimize overhead when tracing is disabled.
– Put calls to the trace program inside a faster
Boolean check.
• Make it possible for users to enable tracing to
gather production data.

Copyright 2013 Feuerstein and Associates Page 105


Oracle PL/SQL Programming

Some Tracing Options


• Trace packages in demo.zip include:
– sf_trace: my latest, simple and sufficient tracer
– watch.pkg: "watch" the action, ability to direct trace
information to a variety of targets.
• logger utility by Tyler Muth
– Very popular with APEX developers; check
http://goo.gl/5jrkT for update on project.
• DBMS_APPLICATION_INFO
– Writes to V$ views, good for long-running operations

sf_trace*.*
watch.pkg
dbms_application_info_demo.sql

Copyright 2013 Feuerstein and Associates Page 106


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 2013 Feuerstein and Associates Page 107


Oracle PL/SQL Programming

The fundamental problem with


SQL in PL/SQL
Order Entry Application
• We take it entirely for
granted. The Backend
– Why not? It's so easy to
Order
write SQL in PL/SQL! Table Item
Table

• We don't set rules on Customer


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 2013 Feuerstein and Associates Page 108


Oracle PL/SQL Programming

Set Standards for Writing SQL


• Check Bryn Llewellyn's "Doing SQL in PL/SQL"
whitepaper on OTN for many ideas.
• Use a data encapsulation layer.
– Hide SQL statements behind an interface.
• Hide all tables in schemas users cannot
access.
• Qualify every identifier in SQL statements.
• Dot-qualify references to Oracle-supplied
objects.
• And some best practices for dynamic SQL
Copyright 2013 Feuerstein and Associates Page 109
Oracle PL/SQL Programming

Data Encapsulation: hide all tables in


schemas users cannot access.
• A fundamental issue of control and security.
• Do not allow users to connect to any schema
that contains tables.
– Simply too risky.
• Define tables in other schemas.
• Grant access via privileges, mostly via
EXECUTE on packages that maintain the tables
(data encapsulation/API).

Copyright 2013 Feuerstein and Associates Page 110


Oracle PL/SQL Programming

Architecture with "inaccessible schema"


• The OE Data schemas own all tables.
• The OE Code schema owns the code and has directly
granted privileges on the tables.
• User schemas have execute authority granted on the code.
OE Code
Sam_Sales
Order_Mgt
Place
Close Old But we also need to
Cancel Orders extend these controls
to developers.
Because every SQL
X statement you write
OE Data
is a hard-coding!
Cannot access
Orders table directly.

Copyright 2013 Feuerstein and Associates Page 111


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 2013 Feuerstein and Associates PagePage


112112
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 2013 Feuerstein and Associates Page 113


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 Application


Code
programs defined in the data access
layer. Intermediate Layer
– Views hide complex query construction
– Packaged APIs – for tables, transactions
Order Item
and business entities Table Table

Copyright 2013 Feuerstein and Associates Page 114


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.
11g_frc_demo.sql
11g_emplu.*
Copyright 2013 Feuerstein and Associates Page 115
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, but also views).

Copyright 2013 Feuerstein and Associates Page 116


Oracle PL/SQL Programming

Qualify every column and identifier in the


SQL statement.
• Improves readability.
• Avoids potential bugs when variable names
match column names.
• Minimizes invalidation of dependent program
units in Oracle11g.
11g_fgd*.sql

Instead of this.... Write this....

PROCEDURE abc (...) PROCEDURE abc (...)


IS IS
BEGIN BEGIN
SELECT last_name SELECT e.last_name
INTO l_name INTO l_name
FROM employees FROM employees e
WHERE employee_id = employee_id_in; WHERE e.employee_id = abc.employee_id_in;

Copyright 2013 Feuerstein and Associates Page 117


Oracle PL/SQL Programming

Dot-qualify all references to Oracle-


supplied objects with "SYS."
• Another annoying, but incontestable
recommendation.
• If you don't prefix calls to all supplied packages with
"SYS.", you are more vulnerable to injection.
– More details available in "Best Practices for Dynamic SQL"
in the "Dynamic SQL in PL/SQL" series.
BEGIN
run_dynamic_plsql_block
(append_this_in =>
'employee_id=101; EXECUTE IMMEDIATE
''CREATE OR REPLACE PACKAGE DBMS_OUTPUT ... '''
);
END;

code_injection.sql
Copyright 2013 Feuerstein and Associates Page 118
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 2013 Feuerstein and Associates Page 119


Oracle PL/SQL Programming

Conclusions
• SQL statements are among the most critical
parts of your application.
– They change frequently, they consume lots of
resources, result in many errors.
• You should have a clearly defined set of
guidelines about when, where and how to
write SQL.
• Most important: Don't repeat SQL statements.
– Most good practices will follow more easily if you
manage to avoid SQL hard-coding.

Copyright 2013 Feuerstein and Associates Page 120


Oracle PL/SQL Programming

Never Repeat Anything - 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 2013 Feuerstein and Associates Page 121
Oracle PL/SQL Programming

Program Socially
• Never code alone.
– Writing code by yourself results in
much buggier code than if you had someone with
whom to consult; to ask questions; to help, in turn.
– It's OK to ask for help.
– Follow the 30 minute rule.
• Automated code review in PL/SQL
– Compile-time warnings
– Leverage the data dictionary views
– PL/Scope

Copyright 2013 Feuerstein and Associates Page 122


Oracle PL/SQL Programming

Don't be afraid to ask for help.


"Predators look for signs of illness or weakness when choosing their prey,
so a prey animal needs to appear healthy, or it will be a sure target. By
the time they are showing signs of disease, in many instances, the birds
have become too weak to be able to disguise it."
- From peteducation.com

• Our evolved survival instinct urges us to hide


weakness.
• On top of that, we software developers are
supposed to be really smart.
– We are the wizards of modern society.
• Unfortunately, ignorance leads directly to bugs
and sub-optimal code.

Copyright 2013 Feuerstein and Associates Page 123


Oracle PL/SQL Programming

It's OK to say "I don't know. Help me!"


• Just thinking about asking for help
will often do the trick.
• Most people like to be asked to help.
– It makes them feel valued.
– It strengthens the team as a whole.
• It may not really matter who you ask for help.
– If there are no programmers handy, ask your spouse
or parent or child to be a sounding board.
– Or write an email. By the time you finish writing it,
you will likely have found the answer to your
problem.
– The important thing is to get the issue out of your
head.
Copyright 2013 Feuerstein and Associates Page 124
Oracle PL/SQL Programming

Code Review - Automated and Otherwise


• Even if you write your code mostly by yourself,
it's extremely important for someone or
something to check that code.
– Does it make sense? Does it follow standards? Is
there a better way to do it?
• Peer code review - in a group or one-on-one is
very helpful, but often intimidating.
• Automated code review is less personal, might
catch many issues the "eyeball" might miss.
– Compile time warnings, DD views, PL/Scope

Copyright 2013 Feuerstein and Associates Page 125


Oracle PL/SQL Programming

Follow the Thirty Minute Rule


• We are usually too deeply inside
(and part of) the problem to step
back and take a fresh look.
• If you can't fix a bug in 30 minutes, ask for help.
– For "trivial" bugs, "give up" after just a few minutes!
• Senior developers and managers must take the
lead.
– Ask more junior members for help. Show that you
are fallible, that you can learn from anyone and
everyone.

Copyright 2013 Feuerstein and Associates Page 126


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://docs.oracle.com

Copyright 2013 Feuerstein and Associates Page 127


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 2013 Feuerstein and Associates Page 128


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 2013 Feuerstein and Associates Page 129


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:
plw6002.sql
LINE/COL ERROR
-------- -------------------------------------
7/7 PLW-06002: Unreachable code

Copyright 2013 Feuerstein and Associates Page 130


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 compile-


time 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 2013 Feuerstein and Associates Page 131


Oracle PL/SQL Programming

New compile-time warnings in Oracle11g


• PLW-6009: Exception handler does not re-raise an
exception.
– Doesn't recognize when a subprogram does the raise.
• PLW-7205: warning on mixed use of integer types
– Namely, SIMPLE_INTEGER mixed with PLS_INTEGER
and BINARY_INTEGER
• PLW-7206: unnecessary assignments
– My own warning: I can't get this warning to "fire"!
• Lots of PRAGMA INLINE-related warnings
• More feedback on impact of optimization
– PLW-6007: Notification that entire subprograms were
removed
plw*.sql files
Copyright 2013 Feuerstein and Associates Page 132
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 2013 Feuerstein and Associates Page 133


Oracle PL/SQL Programming

Watch out for "false negatives" and


"nuisances" warnings
• The check for unreachable code is not very
useful 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.
– Show us "missing AUTHID clause".

Copyright 2013 Feuerstein and Associates Page 134


Oracle PL/SQL Programming

Code analysis with data dictionary views


• Oracle's data dictionary provides access to
many views containing information about our
stored program units. With them we can...
– Analyze objects defined in the database
– Analyze source code for contents and patterns
– Analyze program unit structure and header
– Check compile-time settings of program units
• With a good set of scripts you can easily and
productively analyze your code.

Copyright 2013 Feuerstein and Associates Page 135


Oracle PL/SQL Programming

Analyzing source code


• ALL_SOURCE
– Write queries against source code to identify
violations of coding standards.
– Which programs contain/exclude particular strings?
• Use with other data dictionary views and utilities
that reference source code.
– DBMS_UTILITY.FORMAT_CALL_STACK
– Profiler data
• ALL_IDENTIFIERS (Oracle11g) –PL/Scope
– Analyze all references to identifiers (named
elements) – covered in separate lesson.
all_source.sql valstds.pks/pkb
package_analyzer.pks/pkb
Copyright 2013 Feuerstein and Associates Page 136
Oracle PL/SQL Programming

Analyzing program unit structure/header


• Source code is handy, but also "freeform" text.
– The more structured the data, the better.
• ALL_PROCEDURES
– Information about every subprogram you can execute
– Missing some information (the type of subprogram)
• ALL_ARGUMENTS
– Information about every argument of every subprogram you can
execute
– Rich resource of information, not that well designed.
– Can use it to figure out type of subprogram
– DBMS_DESCRIBE offers another access path to more or less the same
data
all_arguments.sql
show_all_arguments*.*
show_authid.sql show_procs_with_parm_types.sql
show_deterministic.sql is_function.sf
Copyright 2013 Feuerstein and Associates Page 137
Oracle PL/SQL Programming

Compile time settings for program units


• ALL_PLSQL_OBJECT_SETTINGS
• Stores information about compile-time
characteristics of program units.
– Optimization level
– Code type: NATIVE or INTERPRETED
– Debug settings
– Compile-time warnings
– Conditional compilation flags
– PL/Scope settings
whats_not_optimal.sql
Copyright 2013 Feuerstein and Associates
show_non_default_object_settings.sql Page 138
Oracle PL/SQL Programming

PL/Scope: powerful code analysis tool


• A compiler-driven tool that collects information
about identifiers and stores it in data dictionary
views.
– Introduced in Oracle Database 11g
• 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 2013 Feuerstein and Associates Page 139


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 2013 Feuerstein and Associates Page 140


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 2013 Feuerstein and Associates Page 141
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

plscope_demo_setup.sql
plscope_all_idents.sql
plscope_var_declares.sql
plscope_gvar_declares.sql
plscope_var_changes.sql
Copyright 2013 Feuerstein and Associates Page 142
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 2013 Feuerstein and Associates Page 143


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 2013 Feuerstein and Associates Page 144


Oracle PL/SQL Programming

Break Down Coding Isolation


• Ask for help, and offer help to others.
• Participate willingly in code review.
– Your code and your skills will both benefit.
• Automate code evaluation as much as
possible.
– It's always been possible with the DD views, but
now with compile-time warnings and PL/Scope,
the quality of feedback is much higher.

Copyright 2013 Feuerstein and Associates Page 145


Oracle PL/SQL Programming

Make the Most of the Best 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 2013 Feuerstein and Associates Page 146


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 2013 Feuerstein and Associates Page 147

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy