Oracle PL/SQL Programming
A Speak-Tech Training
High Performance PL/SQL
Plus New Features in Oracle Database 11g
Steven Feuerstein
[email protected] www.StevenFeuerstein.com
Oracle PL/SQL Programming
High Performance PL/SQL - Agenda
Analyzing performance and memory
Runtime Memory Management and PL/SQL Profilers and timers
The optimizing PL/SQL compiler Data Caching Techniques
DETERMINISTIC , PGA caching, Function Result Cache
Other Collection Performance Techniques
String indexing, MULTISET operators
Bulk Processing with BULK COLLECT and FORALL Table Functions including Pipelined TFs NOCOPY Optimizing Datatypes
Page 2
Copyright 2011 Feuerstein and Associates
Oracle PL/SQL Programming
High Performance PL/SQL Agenda
Suppressing Errors with LOG ERRORS Optimizing SQL in PL/SQL
The RETURNING Clause Most efficient way to fetch a single row Tips for dynamic SQL Updating large tables in parallel with DBMS_PARALLEL_EXECUTE
Other Oracle Database 11g features
Dynamic SQL enhancements Other non-performance related features
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
Other Key Sites for PL/SQL Developers
oracle-developer.net
Adrian Billington's fantastic site
Oracle Performance Survival Guide
Book and scripts by Guy Harrision guyharrison.squarespace.com/opsgsamples
Oracle-BASE.com
Tim Hall's incredibly useful set of resources and scripts
Ask Tom - Tom Kyte's famed forum
Copyright 2011 Feuerstein and Associates Page 6
Oracle PL/SQL Programming
What I won't be talking about
Tuning SQL statements
I am not an expert in explain plans, hints, the CBO, etc.
Configuring the database
The DBA must make sure that, among other things, all of the many caches in the System Global Area are big enough to avoid swapping due to application of the Least Recently Used (LRU) algorithm.
Instead, I will focus on changes you can make in the way you write PL/SQL that will impact performance.
Copyright 2011 Feuerstein and Associates Page 7
Oracle PL/SQL Programming
When to optimize your code
We always want our programs to run faster. But remember the 80-20 rule:
Most of your code will never be part of a bottleneck, so don't obsess about optimizing every line of code.
Make sure that you are familiar with the most critical optimization features.
Apply these proactively.
Then prioritize maintainability of code. Afterwards, apply more specialized techniques.
Copyright 2011 Feuerstein and Associates Page 8
Oracle PL/SQL Programming
To Optimize, You Need To...
Profile execution of code
Identify performance bottlenecks
Calculate elapsed time of execution
Critical for granular analysis of performance and comparison of performance between different implementations of same program
Manage memory
Most optimizations involve a tradeoff: less CPU, more memory.
Copyright 2011 Feuerstein and Associates Page 9
Oracle PL/SQL Programming
Profiling execution of PL/SQL code
First of all, is it SQL or is it PL/SQL?
Most of the time, SQL is the problem. Use the v$SQL view to answer the question.
Profile the execution of your PL/SQL program units to identify bottlenecks.
Which lines consume the most CPU? Which subprograms take the most time?
Two profilers:
DBMS_PROFILER: line by line performance DBMS_HPROF: hierarchical profiler, rollup to program units
Copyright 2011 Feuerstein and Associates
cachedPLSQL.sql
Page 10
Oracle PL/SQL Programming
DBMS_PROFILER
BEGIN DBMS_OUTPUT.PUT_LINE ( DBMS_PROFILER.START_PROFILER ( 'my_application ' || TO_CHAR (SYSDATE, 'YYYYMDD HH24:MI:SS') )); run_your_application; DBMS_PROFILER.STOP_PROFILER; END;
Requires EXECUTE privilege on DBMS_PROFILER. Must create tables: $RDBMS_ADMIN/proftab.sql Run queries (or use your IDE) to view results. This profiler also provides raw data for code coverage analysis.
profrep.sql dbms_profiler_example.sql
Page 11
Copyright 2011 Feuerstein and Associates
11g
Oracle PL/SQL Programming
DBMS_HPROF
DECLARE l_runid BEGIN NUMBER;
DBMS_HPROF.start_profiling ('HPROF_DIR', 'run1');
run_application;
DBMS_HPROF.stop_profiling (); l_runid := DBMS_HPROF.analyze (location
, , END; $ plshprof -output hprof run1.trc filename run_comment
=> 'HPROF_DIR'
=> 'run1.trc' => 'First run');
Requires EXECUTE privilege on DBMS_PROFILER. Must create tables: $RDBMS_ADMIN/dbmshptab.sql Run queries against tables or call plshprof to generate HTML reports
Copyright 2011 Feuerstein and Associates
dbms_hprof_example.sql
Page 12
Oracle PL/SQL Programming
Calculating Elapsed Time of Programs
Many options for analyzing Oracle performance: TKPROF, SET TIMING ON, etc.
But they usually don't offer the granularity I need for my PL/SQL performance analysis.
Oracle offers DBMS_UTILITY.GET_TIME and GET_CPU_TIME (10g) to compute elapsed time down to the hundredth of a second.
Can also use SYSTIMESTAMP
DECLARE l_start_time PLS_INTEGER; BEGIN l_start_time := DBMS_UTILITY.get_time; -- Do stuff here... DBMS_OUTPUT.put_line ( DBMS_UTILITY.get_time l_start_time); END;
Copyright 2011 Feuerstein and Associates
sf_timer.* get_time.sql plvtmr.* plvtmr_ts.pkg tmr.ot thisuser*.*
Page 13
Oracle PL/SQL Programming
Whats the Problem?
What will happen when I run this code?
DECLARE l_strings DBMS_SQL.varchar2a; BEGIN FOR indx IN 1 .. 2 ** 31 - 1 LOOP l_strings (indx) := RPAD ('abc', 32767, 'def'); END LOOP; END; /
Copyright 2011 Feuerstein and Associates
memory_error.sql
Page 14
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 Page 15
Oracle PL/SQL Programming
PL/SQL Runtime Memory Architecture
System Global Area (SGA) of RDBMS Instance
Shared Pool
Shared SQL Reserved Pool Pre-parsed
Select * from emp
Library cache
Update emp Set sal=...
Large Pool
calc_totals
show_emps
upd_salaries
Session 1
emp_rec emp%rowtype; tot_tab pkg.tottabtype;
emp_rec emp%rowtype; tot_tab pkg.tottabtype;
Session 2
Session 1 memory UGA User Global Area PGA Process Global Area
Copyright 2011 Feuerstein and Associates
Session 2 memory UGA User Global Area PGA Process Global Area
Page 16
Oracle PL/SQL Programming
How PL/SQL uses the SGA, PGA and UGA
The SGA contains information that can be shared across schemas connected to the instance.
From the PL/SQL perspective, this is limited to package static constants.
PACKAGE Pkg is Nonstatic_Constant CONSTANT PLS_INTEGER := My_Sequence.Nextval; Static_Constant CONSTANT PLS_INTEGER := 42; END Pkg;
The Process Global Area contains session-specific data that is released when the current server call terminates.
Local data
The User Global Area contains session-specific data that persists across server call boundaries
Package-level data
Copyright 2011 Feuerstein and Associates
top_pga.sql
Page 17
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 18
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 19
Oracle PL/SQL Programming
Conclusions - Memory Management
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 20
Oracle PL/SQL Programming
Whats the Problem?
What can I change in this program to improve performance?
CREATE OR REPLACE PROCEDURE plch_loop ( value1 IN NUMBER, value2 IN NUMBER, value3 IN NUMBER) IS l_result NUMBER := 0; BEGIN FOR indx IN 1 .. 10000000 LOOP l_result := l_result + (value1 + value2) / value3; END LOOP; DBMS_OUTPUT.put_line (l_result); END; /
Copyright 2011 Feuerstein and Associates
loop_invariants*.sql
Page 21
Oracle PL/SQL Programming
Fully Leverage the PL/SQL Compiler
Oracle demonstrated its long-term commitment to PL/SQL with the release 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 22
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 23
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 24
Oracle PL/SQL Programming
Some Examples
... A + B ... ... ... A + B ...
T := A + B; ... T ... ... ... T ...
T is a generated variable. We never see it. And one operation is saved.
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;
Automatic relocation of a loop invariant. Avoid repetitive computations.
SELECT ... BULK COLLECT INTO ... FROM ...
Execute cursor FOR loop at BULK COLLECT levels of performance.
Copyright 2011 Feuerstein and Associates
10g_optimize_cfl.sql
Page 25
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 26
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 27
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.
ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 3;
Copyright 2011 Feuerstein and Associates Page 28
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
11g_inline*.sql
Page 29
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 30
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 31
Oracle PL/SQL Programming
Data Caching Options in PL/SQL
A time-honored technique for improving performance. Store data that doesnt change for some period of time in a location that can be accessed more quickly than the source. The SGA is an enormous, complex cache for the entire database instance. But there are other caches (in SGA and PGA) we can leverage in our PL/SQL code.
Copyright 2011 Feuerstein and Associates Page 32
Oracle PL/SQL Programming
Data Caching Options
Functions declared as DETERMINISTIC PGA caching
Used most effectively with collections Accessing PGA memory generally more efficient than SGA, especially if executing SQL.
Oracle Database 11g Function Result Cache
The best caching technique and the most important new feature in 11g for PL/SQL developers.
Copyright 2011 Feuerstein and Associates Page 33
Oracle PL/SQL Programming
DETERMINSTIC Functions
A function is deterministic if the value it returns is determined completely by its inputs (IN arguments).
In other words, no side effects.
Add the DETERMINISTIC keyword to your function to enable optimizations:
Function-based indexes Cache results in scope of a query
Dont lie! Oracle will not reject your use of the keyword, even if it isnt true.
Copyright 2011 Feuerstein and Associates
deterministic.sql deterministic_in_plsql.sql
Page 34
Oracle PL/SQL Programming
PGA-Based Caching
When you declare variables at the package level, their state persists in your session.
A PGA-based cache, specific to each session.
And if you declare a collection at the package level, you can cache multiple rows of data. Not a reliable technique for Web-based (usually stateless) applications Let's start with a trivial example: USER
thisuser*.*
Copyright 2011 Feuerstein and Associates
Page 35
Oracle PL/SQL Programming
PGA Caching with Collections
Collections are PL/SQL's "version" of arrays. You can define and manipulate collections of scalars (numbers, strings, etc.) or much more complex data structures (records, nested collections). Which means you can cache multiple rows of complex data. In this course, I will offer a quick introduction to collections.
If they are new for you, time to study!
Copyright 2011 Feuerstein and Associates
associative_array_example.sql nested_table_example.sql
Page 36
Oracle PL/SQL Programming
Avoiding Unnecessary SGA Lookups
First access
Database / SGA
Not in cache; Request data from database Pass Data to Cache
Data retrieved from cache
Data returned to application
Function
Application
PGA
Application Requests Data
Subsequent accesses
Database / SGA
Data found in cache. Database is not needed.
Data retrieved from cache
Data returned to application
Function
Application
PGA
Application Requests Data Copyright 2011 Feuerstein and Associates
emplu.pkg / emplu.tst
Page 37
Oracle PL/SQL Programming
PGA Caching: Things to keep in mind
Must use package-level data so that it persists.
Memory is consumed by the PGA and so is multiplied for all users of the application. Not a reliable technique for stateless application (Internet)
Very difficult to share cache across sessions in the same instance.
One possibility involves DBMS_PIPE.
Very difficult to update the cache once the data source is changed.
Especially by/from, other sessions. Possible to use DBMS_ALERT.
Useful under specific scenarios....
Small, static dataset Single or small number of batch processes
Copyright 2011 Feuerstein and Associates
syscache.pkg
Page 38
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 39
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
11g_frc_demo.sql
Page 40
11g
Oracle PL/SQL Programming
Minimal Impact on Code with Result Cache
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 .... END onerow; END emplu11g;
Add RESULT_CACHE keyword to header of function in both specification and body. RELIES_ON clause is deprecated in 11.2. Oracle will automatically determine all tables on which the function relies. RELIES_ON is then ignored.
Copyright 2011 Feuerstein and Associates Page 41
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 PGAbased 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!
Copyright 2011 Feuerstein and Associates
11g_emplu*.*
Page 42
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.
Copyright 2011 Feuerstein and Associates
11g_frc_demo.sql
Page 43
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.
Copyright 2011 Feuerstein and Associates
11g_frc_vpd.sql 11g_frc_vpd2.sql
Page 44
11g
Oracle PL/SQL Programming
Managing 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
If the cache is too small, then the LRU algorithm negates the point of the cache.
DBMS_RESULT_CACHE management package v$RESULT_CACHE_* performance views
Copyright 2011 Feuerstein and Associates
show_frc_dependencies.sp
Page 45
11g
Oracle PL/SQL Programming
Fine Grained Dependencies in 11.2
Oracle keeps track of table dependencies on a per-result level.
Each result cached could have a different set of dependencies.
A change to a table could invalidate just a subset of the results in the cache.
It's not all or nothing - when your function's different logic paths could "hit" different tables.
Copyright 2011 Feuerstein and Associates
11g_frc_dependencies.sql 11g_frc_dependencies2.sql
Page 46
Oracle PL/SQL Programming
Conclusions - Caching
Oracle offers several different ways you can build upon its own caching. DETERMINISTIC for functions in SQL PGA caching is very fast, but cannot be used in most situations The function result cache is the simplest, most widely applicable, and biggest-impact technique.
Get ready for it now by hiding queries inside functions.
Copyright 2011 Feuerstein and Associates
11g_emplu.pkg
Page 47
Oracle PL/SQL Programming
Whats the Problem?
Here's a function that tells me is a particular string has been used.
CREATE OR REPLACE PACKAGE BODY string_tracker IS FUNCTION string_in_use (value_in IN variable_name_t) RETURN BOOLEAN IS c_count CONSTANT PLS_INTEGER := g_names_used.COUNT; l_index PLS_INTEGER := g_names_used.FIRST; l_found BOOLEAN DEFAULT FALSE; BEGIN WHILE (NOT l_found AND l_index <= c_count) LOOP l_found := value_in = g_names_used (l_index); l_index := l_index + 1; END LOOP; RETURN l_found; END string_in_use; END string_tracker;
Copyright 2011 Feuerstein and Associates
string_tracker0.pkg
Page 48
Oracle PL/SQL Programming
Other Performance-Related Collection Techniques
Using String Indexes with Associative Arrays Using MULTISET Operators with Nested Tables
Copyright 2011 Feuerstein and Associates
Page 49
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 50
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.
assoc_array*.sql assoc_array_perf.tst
Copyright 2011 Feuerstein and Associates
Page 51
Oracle PL/SQL Programming
Tracking Usage 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 52
Oracle PL/SQL Programming
Conclusions - String Indexed Arrays
Collections are, in many ways, like tables. We don't like full table scans (for the most part) and we don't like full collection scans. So if you ever find yourself writing a loop to iterate through all elements of the collection to find a match, consider changing your index type.
Or you could also consider defining a second collection to serve as an index into the first!
Copyright 2011 Feuerstein and Associates
string_index.sql genaa.sql
Page 53
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 54
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).
Will perform much faster than writing your own algorithm to do the same thing.
Copyright 2011 Feuerstein and Associates Page 55
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 56 10g_compare_old.sql Copyright 2011 Feuerstein and Associates
Page 56
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 57 Copyright 2011 Feuerstein and Associates
authors.pkg 10g_set.sql
Page 57
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 58
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 59
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.
authors.pkg 10g_union.sql
Copyright 2011 Feuerstein and Associates
Page 60
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 61
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 62
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 63
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.
Copyright 2011 Feuerstein and Associates
global_temp_tab_vs_coll.sql
Page 64
Oracle PL/SQL Programming
Whats the Problem?
We have, on average, 10,000 employees per department.
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
Page 65
Oracle PL/SQL Programming
Row-by-row = Slow-by-slow?
Many PL/SQL blocks execute the same SQL statement repeatedly, with different bind values. Retrieves data one row at a time. Performs same DML operation for each row retrieved. The SQL engine does a lot to optimize performance, but you this row-by-row processing is inherently slow.
But, but...aren't SQL and PL/SQL supposed to be very tightly integrated? Let's take a look "under the covers.
Copyright 2011 Feuerstein and Associates Page 66
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;
SQL Engine
Procedural statement executor
SQL statement executor
Performance penalty for many context switches
Copyright 2011 Feuerstein and Associates Page 67
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 68
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...
SQL Engine
Procedural statement executor
SQL statement executor
Update... Update... Update... Update... Update... Update...
Fewer context switches, same SQL behavior
Copyright 2011 Feuerstein and Associates
Page 69
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 70
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 71
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 72
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 73
Oracle PL/SQL Programming
BULK COLLECT for Implicit Cursor
Declare a nested table of records to hold the queried data. Fetch all rows into collection sequentially, starting with 1.
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
Iterate through the collection contents with a loop.
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 74
Copyright 2011 Feuerstein and Associates
Oracle PL/SQL Programming
What's the problem with this code?
DECLARE TYPE employees_aat IS TABLE OF employees%ROWTYPE; l_employees employees_aat; BEGIN SELECT * BULK COLLECT INTO l_employees FROM employees; FOR indx IN 1 .. l_employees.COUNT LOOP process_employee (l_employees(indx)); END LOOP; END;
As data volume grows, PGA memory requirement grows. Eventually....KABOOM!
Copyright 2011 Feuerstein and Associates Page 75
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 76
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 EXIT WHEN emps.COUNT = 0; process_emps (emps); END LOOP; CLOSE emps_in_dept_cur; END bulk_with_limit;
bulklimit.sql
Copyright 2011 Feuerstein and Associates Page 77
COLLECT operation.
Definitely the preferred approach in production applications with large or varying datasets.
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 78
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 79
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 80
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 81
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 82
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 83
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 84
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 85
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 86
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 87
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 88
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 89
Oracle PL/SQL Programming
Example: FORALL with SAVE EXCEPTIONS
Add SAVE EXCEPTIONS to enable FORALL to suppress errors at the statement level.
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 90
If any exception is encountered, Oracle raises 24381 when done.
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 91
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 Challenges include:
With multiple DML statements in loop, how do you "communicate" from one to the other? Avoid excessive PGA consumption
Copyright 2011 Feuerstein and Associates Page 92
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 93
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 94
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
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;
Phase 3: Push Data
Copyright 2011 Feuerstein and Associates
cfl_to_bulk_0.sql cfl_to_bulk_5.sql
Page 95
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 96
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 97
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 98
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 99
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 100
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 101
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.
CREATE TABLE stocktable ( ticker VARCHAR2(20), trade_date DATE, open_price NUMBER, close_price NUMBER ) / CREATE TABLE tickertable ( ticker VARCHAR2(20), pricedate DATE, pricetype VARCHAR2(1), price NUMBER) /
tabfunc_streaming.sql
Copyright 2011 Feuerstein and Associates
Page 102
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; / tabfunc_streaming.sql
Copyright 2011 Feuerstein and Associates
Page 103
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 104
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 105
Oracle PL/SQL Programming
Piping rows out from a pipelined function
Add PIPELINED keyword to header 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
Pipe a row of data back to calling block or query
RETURN...nothing at all!
Copyright 2011 Feuerstein and Associates
Page 106
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 107
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 108
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 terminates with an unhandled exception.
nocopy*.* string_nocopy.*
Copyright 2011 Feuerstein and Associates Page 109
Oracle PL/SQL Programming
Optimizing Datatypes
All datatypes are not created equal. When performing intensive integer computations, consider PLS_INTEGER instead of INTEGER. When performing computations with floating point values, consider the BINARY_DOUBLE or BINARY_FLOAT types. Remember, for most situations, the impact will not be noticeable.
Copyright 2011 Feuerstein and Associates
integer_compare.sql binary_types.sql
Page 110
Oracle PL/SQL Programming
Suppressing Errors with LOG ERRORS
Why cover this in a performance training? Because the raising and trapping of an exception slows down a program dramatically.
If it doesn't simply stop it completely.
When changing a large number of rows, you might want to continue past errors, to get as much work done as possible.
LOG ERRORS allows you to do this even when a DML statement fails.
Copyright 2011 Feuerstein and Associates Page 111
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....
Usually acceptable, but what if you want to:
Avoid losing all prior changes? Avoid the performance penalty of exception management in PL/SQL?
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.
Copyright 2011 Feuerstein and Associates
errors_and_dml.sql
Page 112
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 113
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 114
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 115
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 116
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.
Copyright 2011 Feuerstein and Associates
dbms_errlog.sql
Page 117
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.
Copyright 2011 Feuerstein and Associates
dbms_errlog_helper.sql dbms_errlog_helper_demo.sql
Page 118
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 119
Oracle PL/SQL Programming
Optimizing SQL in PL/SQL (more!)
The RETURNING Clause Implicit-vs-explicit queries Optimizing Dynamic SQL Update large tables in parallel with DBMS_PARALLEL_EXECUTE
Copyright 2011 Feuerstein and Associates
Page 120
Oracle PL/SQL Programming
RETURNING Clause
Use the RETURNING INTO clause to retrieve information about rows modified by the DML statement.
Avoid a separate SELECT.
When more than one row is modified, use RETURNING BULK COLLECT INTO. You cannot return an entire row into a record.
Copyright 2011 Feuerstein and Associates
returning.tst method_2_returning.sql
Page 121
Oracle PL/SQL Programming
Implicit or Explicit - which is faster?
BEGIN SELECT INTO FROM WHERE last_name l_name employees employee_id = 128; DECLARE CURSOR name_cur IS SELECT last_name FROM employees WHERE employee_id = 128; BEGIN OPEN name_cur; FETCH name_cur INTO l_name;
Confusion lingers from ancient "guru" advice. Implicit cursors (aka, SELECT INTO) will almost always be the most efficient way to fetch a single row of data.
Even though it must perform a second fetch to check for TOO_MANY_ROWS, Oracle has optimized it to run faster than explicit cursors.
Copyright 2011 Feuerstein and Associates
single_row_fetch.sql
Page 122
Oracle PL/SQL Programming
Optimizing Dynamic SQL
Use native dynamic SQL instead of DBMS_SQL.
Less code, easier to maintain, more efficient.
Bind variables, rather than concatenate, whenever possible.
The code is simpler and easier to maintain Oracle will avoid repeated parsing.
Copyright 2011 Feuerstein and Associates
tabcount_dbms_sql .sf tabcount_nds.sf useconcat*.* usebinding.sp
Page 123
11g
Oracle PL/SQL Programming
DBMS_PARALLEL_EXECUTE (11.2)
Incrementally update data in a large table in parallel:
1. Group sets of rows in the table into smaller chunks. 2. Apply the desired UPDATE statement to the chunks in parallel, committing each time you have finished processing a chunk.
Improves performance, reduces rollback space consumption, and reduces the number of row locks held.
Copyright 2011 Feuerstein and Associates Page 124
11g
Oracle PL/SQL Programming
DBMS_PARALLEL_EXECUTE - continued
Define chunks of data to be processed in parallel. Specify those chunks by ROWID, a SQL query, a numeric column. Especially helpful in data warehousing environments. See May/June 2010 Oracle Magazine issue for more thorough introduction.
Copyright 2011 Feuerstein and Associates
11g_parallel_execute.sql
Page 125
11g
Oracle PL/SQL Programming
Other Oracle Database 11g New PL/SQL Features
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 126
11g
Oracle PL/SQL Programming
Native Compilation
The SIMPLE_INTEGER and real 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 127
Oracle PL/SQL Programming
Other Oracle11g New Features
Lets now take a look at new features in Oracle Database 11g that are not (as) directly related to performance.
Trigger enhancements Native access to NEXTVAL and CURRVAL PL/Scope Dynamic SQL enhancements And more...
Copyright 2011 Feuerstein and Associates
Page 128
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 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 129
Copyright 2011 Feuerstein and Associates
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 130
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 131
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 132
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 133
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 134
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 135
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
plscope_demo_setup.sql plscope_all_idents.sql plscope_var_declares.sql plscope_gvar_declares.sql plscope_var_changes.sql
Copyright 2011 Feuerstein and Associates
Page 136
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 137
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 138
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 139
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 140
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 141
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 142
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 143
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 144
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 145
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 146
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 147
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 148
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 149
Oracle PL/SQL Programming
High Performance PL/SQL - Conclusions
Tune your SQL first! Proactively apply all these features:
BULK COLLECT FORALL NOCOPY Function Result Cache RETURNING
Once you've identified bottlenecks, explore "secondary" optimization techniques.
Copyright 2011 Feuerstein and Associates Page 150
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 151