You are on page 1of 28

PL/SQL Programming Tips

Contents
Naming Conventions ..................................................................................................................................... 3
Why need naming convention .................................................................................................................. 3
Some facts that matter ............................................................................................................................. 3
General Guidelines .................................................................................................................................... 3
Naming Conventions ................................................................................................................................. 4
Coding Style .................................................................................................................................................. 5
General Coding Style ................................................................................................................................. 5
Some Examples of SQL Statement Formatting ......................................................................................... 5
Tools that can format SQL Statement....................................................................................................... 6
Code Commenting .................................................................................................................................... 7
Best Practices ................................................................................................................................................ 8
Literals, constants and variables ............................................................................................................... 8
Data Types................................................................................................................................................. 9
Flow Control ............................................................................................................................................ 11
Exception Handling ................................................................................................................................. 13
Dynamic SQL ........................................................................................................................................... 14
Packages, Functions and Procedures ...................................................................................................... 14
CASE/IF/DECODE/NVL/NVL2/COALESCE ................................................................................................ 14
Some Built-in Utilities ................................................................................................................................. 15
PLSQL_OPTIMIZE_LEVEL ......................................................................................................................... 15
PLSQL_WARNINGS .................................................................................................................................. 16
PLSQL_DEBUG ......................................................................................................................................... 16
Native Compilation ................................................................................................................................. 16
Conditional Compilation ......................................................................................................................... 17
DML Error Logging .................................................................................................................................. 18
Use contexts to store global data ........................................................................................................... 19
DBMS_APPLICATION_INFO ..................................................................................................................... 19
DBMS_SESSION ....................................................................................................................................... 21
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
DBMS_PROFILER ..................................................................................................................................... 21
DBMS_TRACE .......................................................................................................................................... 21
Some Traps.................................................................................................................................................. 22
Pipelined table function and cardinality ................................................................................................. 22
DBMS_RANDOM ..................................................................................................................................... 26
The literal string length limit in SQL statement ...................................................................................... 27

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Naming Conventions
Why need naming convention
Good and consistent naming manner, obviously, will make the PL/SQL code easier to read and maintain.
The result is that the code is most likely to be more robust and with higher quality. Whats more, it will
win higher reputation for the programmer.

Some facts that matter


Since PL/SQL language is closely associated with Oracle, the variable names used in the PL/SQL code are
limited to the restriction of Oracle.

- The length of the names cannot be longer than 30 characters.


- The name is case insensitive.

General Guidelines
There are some general guidelines for the naming manner for the PL/SQL code.

- Use meaningful and specific names, abbreviations and table/column alias.


- Do not use reserved words as variable names. Use the following SQL statement to see which
words are reserved by Oracle.

SELECT keyword FROM v$reserved_words;

- Avoid redundant or meaningless prefixes and suffixes. Remember there is 30 characters length
restriction. For example, there is no need to add _table as the suffix of the table name.
- Do not quote name with double quotes (). Following example illustrates that if the table name
is double quoted, it will causes some troubles when querying the table.

SQL> create table "HelloWorld" (id number);


Table created.

SQL> select * from helloworld;


select * from helloworld
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> select * from HelloWorld;


select * from HelloWorld
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> select * from "HelloWorld";


no rows selected

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Naming Conventions
The following tables will illustrate the naming conventions for pl/sql variables, cursors, collections and
oracle objects.

- Naming Conventions for Variables


Type of Element Naming Convention Example
Variable declared in a PL/SQL block l_<xxx> l_emp_no
Constant declared in a PL/SQL block c_<xxx> c_max_str_length
Variable declared at the package level g_<xxx> g_analysis_plan
Constant declared at the package level gc_<xxx> gc_max_str_length
IN parameter p_<xxx>_in or p_<xxx>_i p_emp_no_in
OUT parameter p_<xxx>_out or p_<xxx>_o p_emp_no_out
IN OUT parameter p_<xxx>_inout or p_<xxx>_io p_emp_no_inout
Exception variable e_<xxx> e_name_not_found

- Naming Conventions for Cursors


Type of Element Naming Convention Example
Explicit cursor [l|g]_<xxx>_cur l_employees_cur
g_employees_cur
Cursor variables [l|g]_<xxx>_cv l_employees_cv
g_employees_cv

- Naming Conventions for Records and Collections


Type of Element Naming Convention Example
Record types [l|g]_<xxx>_rt l_emp_info_rt
Record variables The same as general variable, in singular form l_employee_info
Associate Array type [l|g]_<xxx>_aat l_employees_aat
Nested Table type [l|g]_<xxx>_ntt l_employees_ntt
Varray type [l|g]_<xxx>_vat l_employees_vat
Collection varaibles The same as general variable, in plural form l_employees

- Naming Conventions for Database Objects


Type of Element Naming Convention Example
Types Objects t_<xxx>[_o] t_user_objects
Types Nested Table t_<xxx>_t[able] t_user_objects_table
Packages pack_<xxx> pack_fermat
Triggers tri_<xxx> tri_lo_entity
Sequences seq_<xxx> seq_lo_entity
Views v_<xxx> or mv_<xxx> v_lo_entity
Indexes i[n]_<xxx> or ui[n]_<xxx> i1_lo_entity
Synonyms s_<xxx> s_entity
Global temporary table gtt_<xxx> or <xxx>_gtt user_gtt

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Coding Style
General Coding Style
- Keywords are written in uppercase while variable names are written in lowercase.
- Use 3 white spaces as the code line indention, dont use tab keystroke, as different platforms
treat the tab keystroke differently.
- Specify table columns explicitly in SELECT and INSERT statements.
- Keep SQL statement keywords right-aligned or left-aligned.

The table below shows the general PL/SQL coding style.

PROCEDURE set_salary(p_empno_in IN emp.empno%TYPE)


IS
CURSOR l_emp_cur(p_empno emp.empno%TYPE)
IS
SELECT ename
, sal
FROM emp
WHERE empno = p_empno
ORDER BY ename;
l_emp l_emp_cur%ROWTYPE;
l_new_sal emp.sal%TYPE;
BEGIN
OPEN l_emp_cur(p_empno_in);
FETCH l_emp_cur INTO l_emp;
CLOSE l_emp_cur;
--
get_new_salary (p_empno_in => in_empno
, p_sal_out => l_new_sal);
-- Check whether salary has changed
IF l_emp.sal <> l_new_sal THEN
UPDATE emp
SET sal = l_new_sal
WHERE empno = p_empno_in;
END IF;
END set_salary;

Some Examples of SQL Statement Formatting


- Write the whole SQL statement in one line
SELECT e.job, e.deptno FROM emp e WHERE e.name = 'SCOTT' AND e.sal > 100000

- Keep the SQL statement keywords right-aligned

SELECT e.JOB,
e.deptno
FROM emp e
WHERE e.name = 'SCOTT'
AND e.sal > 100000

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
- Keep the SQL statement keywords left-aligned

SELECT e.JOB,
e.deptno
FROM emp e
WHERE e.name = 'SCOTT'
AND e.sal > 100000

Tools that can format SQL Statement


- Instant SQL Formatter (http://www.dpriver.com/pp/sqlformat.htm)

- Oracle SQL Developer

- Toad for Oracle (Format tools)

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Code Commenting
Obviously, the comment for the code is very important. It can not only make the code easier to
understand, but also can be used by some tools to generate code documentation.

- PLDOC
Pldoc is an open-source utility for generating HTML documentation of code written in Oracle
PL/SQL. Please refer to pldoc official site (http://pldoc.sourceforge.net/maven-site) for details.
Below is a code example which follows the pldoc convention.

PACKAGE pack_syspar IS
/**
* Project: Test Project (<a href="http://pldoc.sourceforge.net">PLDoc</a>)<br/>
* Description: System Parameters Management<br/>
* DB impact: YES<br/>
* Commit inside: NO<br/>
* Rollback inside: NO<br/>
* @headcom
*/

/** Gets system parameter value.


* @param p_name parameter name
* @return parameter value
* @throws no_data_found if no parameter with such name found
*/
FUNCTION get_char (p_name_in VARCHAR2) RETURN VARCHAR2;
END pack_syspar ;
/

- For those standalone (i.e. at schema level) PL/SQL code units, the comments for the units should
not put in front of the code unit, otherwise the comments will not kept in the data dictionary.
Below is an example of trigger comments.
Instead of putting the comments in front of the trigger TRI_MYTAB_BI

/*******************************************************************
Copyright YYYY by <Company Name>
All Rights Reserved.
<Short synopsis of trigger's purpose. Required.>

<Optional design notes.>


*******************************************************************/

CREATE OR REPLACE TRIGGER tri_mytab_bi


BEFORE INSERT ON mytab
FOR EACH ROW
DECLARE
-- local variables here
BEGIN
NULL;
END tri_mytab_bi;
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
The comments should be put inside the trigger definition, please note the comma behind the
trigger comments.

CREATE OR REPLACE TRIGGER tri_mytab_bi


BEFORE INSERT ON mytab
FOR EACH ROW
DECLARE
-- local variables here
BEGIN
NULL;
END tri_mytab_bi
/*******************************************************************
Copyright YYYY by <Company Name>
All Rights Reserved.
<Short synopsis of trigger's purpose. Required.>

<Optional design notes.>


*******************************************************************/ ;

Best Practices
Literals, constants and variables
- Avoid using literals directly in the code, abstract literals behind package constants.
For example, instead of using the literal LEADER in the code directly like blow

-- Bad
DECLARE
l_function player.function_name%TYPE;
BEGIN
SELECT p.function
INTO l_function
FROM player p
WHERE
--
IF l_function = LEADER
THEN

Try to extract the literal LEADER inside one package, like PACK_CONSTANTS. And then
reference this constant in the code unit

-- Good
CREATE OR REPLACE PACKAGE pack_constants
IS
gc_leader CONSTANT player.function_name%TYPE := LEADER;
END pack_constants ;
/
DECLARE

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
l_function player.function_name%TYPE;
BEGIN
SELECT p.function INTO l_function FROM player p WHERE
--
IF l_function = pack_constants.gc_leader
THEN

- Use a function to return a constant if the constant value may change at times. This way, the
code units that depend on this function will not become invalid and need recompile if the value
returned by the function is changed.
- Do not use public global variables, use getter/setter or parameters-passing.
- Never put all of a systems constants in a single package. Try to put the constants in separate
packages according to the constants usage.
- Avoid initializing variables using functions in the declaration section. Instead, assign the
variables using functions in the body section. This way, if the function raises the exception, such
exception can be caught.
For example, dont write the code like this

-- Bad
DECLARE
l_code_section VARCHAR2(30) := TEST_PCK;
l_company_name VARCHAR2(30) := util_pck.get_company_name(in_id => 47);
BEGIN

END;

Write it like this

-- Good
DECLARE
l_code_section VARCHAR2(30) := TEST_PCK;
l_company_name VARCHAR2(30);
BEGIN
<<init>>
BEGIN
l_companyName := util_pck.get_company_name(inId => 47);
EXCEPTION
WHEN VALUE_ERROR THEN ;
END;
END;

Data Types
- Anchor parameters and variables using %TYPE (or %ROWTYPE for cursor or entire table). When
the table structure or cursor definition changes, the variables dont need to update.
- Use SUBTYPE to avoid hard-coded variable length declarations. See below for an example. Since
Oracle restricts the length of the object name to be no longer than 30, so defining the string
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
(varchar2) variables with the length of 30 is a common practice. Instead of hard coding 30 in the
variable length declarations, try to define a subtype in a separate package.
Instead of declaring the variable l_code_section with the length of 30

-- Bad
l_code_section VARCHAR2(30) := TEST_PCK;

Try to create a package PACK_TYPES to define the subtypes and use these subtypes to define
the variables.

-- Good
CREATE OR REPLACE PACKAGE PACK_TYPES
AS
SUBTYPE t_oracle_name IS VARCHAR2(30);
SUBTYPE t_oracle_max_varchar IS VARCHAR2(4000);
subtype t_plsql_max_varchar IS VARCHAR2(32767);
END PACK_TYPES;
/

l_code_section PACK_TYPES.t_oracle_name := TEST_PCK;

- Avoid using CHAR data type, stick to VARCHAR2 data type. Use VARCHAR2(1) to replace CHAR.
- Never use zero-length strings to substitute NULL.

-- Bad
l_char := ;

-- Good
l_char := NULL;

- Use CLOB, NCLOB, BLOB or BFILE for unstructured content in binary or character format. Do not
use LONG or LONG RAW. LOB data types provide efficient, random, piece-wise access to the
data. Some differences between LOB data types and LONG (LONG RAW) are listed below
o A table can contain multiple LOB columns but only one LONG column.
o A table containing one or more LOB columns can be partitioned, but a table containing a
LONG column cannot be partitioned.
o The maximum size of a LOB is 8 terabytes, and the maximum size of a LONG is only 2
gigabytes.
o LOBs support random access to data, but LONGs support only sequential access.
o LOB data types (except NCLOB) can be attributes of a user-defined object type but LONG
data types cannot.
- NUMBER vs. PLS_INTEGER vs. SIMPLE_INTEGER (New in 11g, always not null).
When declaring an integer variable, PLS_INTEGER is the most efficient numeric data type
because its value requires less storage than INTEGER or NUMBER values, which are represented
internally as 22-byte Oracle numbers. Also, PLS_INTEGER operations use machine arithmetic, so
they are faster than NUMBER or INTEGER (integer is subtype of NUMBER) operations, which use
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
library arithmetic. SIMPLE_INTEGER is introduced in 11g, if the integer will always not null use
this data type. SIMPLE_INTEGER is a subtype of the PLS_INTEGER data type and can dramatically
increase the speed of integer arithmetic in natively compiled code, but only shows marginal
performance improvements in interpreted code.
- Avoid using ROWID or UROWID (Universal ROWID). It is a dangerous practice to store ROWIDs in
a table, except for some very limited scenarios of runtime duration. Any manually explicit or
system generated implicit table reorganization will reassign the rows ROWID and break the data
consistency.
- Minimize data type conversion as it takes time.

DECLARE
n NUMBER := 0;
c CHAR(2);
BEGIN
n := n + 15; -- converted implicitly; slow
n := n + 15.0; -- not converted; fast
c := 25; -- converted implicitly; slow
c := TO_CHAR(25); -- converted explicitly; still slow
c := '25'; -- not converted; fast
END;
/

- Bigger is better for VARCHAR2 variables. VARCHAR2 variables below than 4001 bytes (2000
bytes in 9i) have their memory allocated in full at declaration time. VARCHAR2 variables larger
than 4000 bytes (1999 bytes in 9i) have their memory allocated at assignment time, based on
the data assigned to them. As a result, defining large variables that contain little data wastes
memory, unless you move above the optimization threshold at which point no memory is
wasted. If in doubt, define VARCHAR2 (32767) variables.

Flow Control
- Always label the loops. The example below labels the FOR loop with the name of
process_employees.

BEGIN
<<process_employees>>
FOR r_employee IN (SELECT * FROM emp)
LOOP

END LOOP process_employees;
END;

- Always use a FOR loop to process the complete cursor results unless use the bulk operation
- Always use a WHILE loop to process a loose array. The collection t_employees in the example
below is a loose array, so use the WHILE loop.

DECLARE
l_index PLS_INTEGER;
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
BEGIN
l_index := t_employees.FIRST();

<<ProcessEmployees>>
WHILE l_index IS NOT NULL
LOOP

l_index := t_employees.NEXT(l_index);
END LOOP process_employees;
END;

- Always use FIRST..LAST when iterating through collections with a loop

BEGIN
<<process_employees>>
FOR idx IN t_employees.FIRST()..t_employees.LAST()
LOOP

END LOOP process_employees;
END;

- Always use EXIT WHEN instead of an IF statement to exit from a loop

-- Bad
BEGIN
<<process_employees>>
LOOP
...
IF ...
THEN
EXIT process_employees;
END IF;
...
END LOOP process_employees;
END;

-- Good
BEGIN
<<process_employees>>
LOOP
...
EXIT process_employees WHEN (...);
END LOOP process_employees;
END;

- Never EXIT from within any FOR loop, use LOOP or WHILE loop instead
- Avoid hard-coded upper or lower bound values with FOR loops
- Never issue a RETURN statement inside a loop

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Exception Handling
- Never handle unnamed exceptions using the error number, instead using EXCEPTION_INIT to
name the exception. The example below illustrates this idea.

-- Bad
-- ORA-00001: unique constraint violated
BEGIN
...
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -1
THEN
...
END IF;
END;

-- Good
DECLARE
e_employee_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(-1, e_employee_exists);
...
BEGIN
...
EXCEPTION
WHEN e_employee_exists THEN
...
END;

- Never assign predefined exception names to user defined exceptions


- Avoid use of WHEN OTHERS clause in an exception section without any other specific handlers.

-- Good
EXCEPTION
WHEN DUP_VAL_ON_INDEX
THEN
update_instead (...);
WHEN OTHERS
THEN
err.log;
RAISE;

- Avoid use of EXCEPTION_INIT pragma for -20, NNN error (-20,999 ~ -20,000)
- Use DBMS_UTILITY.format_error_stack instead of SQLERRM to obtain the full error message

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Dynamic SQL
- Always use a string variable to execute dynamic SQL, dont use the string literal behind the
EXECUTE IMMEDIATE directly. The reason is that using string variable is easier for debug. See
the example below for the illustration.

-- Bad
DECLARE
l_empno emp.empno%TYPE := 4711;
BEGIN
EXECUTE IMMEDIATE DELETE FROM emp WHERE epno = :p_empno USING l_empno;
END;

-- Good
DECLARE
l_empno emp.empno%TYPE := 4711;
l_sql VARCHAR2(32767);
BEGIN
l_sql := DELETE FROM emp WHERE epno = :p_empno;
EXECUTE IMMEDIATE l_sql USING l_empno;
EXCEPTION
WHEN others
THEN
DBMS_OUTPUT.PUT_LINE(l_sql);
END;

- Use bind variables. Do not concatenate strings unless needed to identify schema or table/view.
- Format dynamic SQL strings as nicely as in static SQL.

Packages, Functions and Procedures


- Try to keep packages small. Include only few procedures and functions that are used in the same
context.
- Always use forward declaration for private functions and procedures in packages.
- Avoid standalone procedures or functions put them in packages.
- Avoid using RETURN statements in a procedure (one way in one way out).
- Try to use no more than one RETURN statement within a function.
- Never use OUT parameters to return values from a function.

CASE/IF/DECODE/NVL/NVL2/COALESCE
- Try to use CASE rather than an IF statement with multiple ELSIF paths.

-- Bad
IF l_color = red
THEN
...
ELSIF l_color = blue
THEN

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
...
ELSIF l_color = black
THEN
...

-- Good
CASE l_color
WHEN red THEN ...
WHEN blue THEN ...
WHEN black THEN ...
END

- Use CASE rather than DECODE. DECODE is SQL function which cannot be used in PL/SQL.
- Always use COALESCE instead of NVL if the second parameter of the NVL function is a function
call or a SELECT statement. The NVL function always evaluates both parameters before deciding
which one to use. This can be harmful if parameter 2 is either a function call or a select
statement, as it will be executed regardless of whether parameter 1 contains a NULL value
or not.

-- Bad
SELECT NVL(dummy, function_call()) FROM dual;

-- Good
SELECT COALESCE(dummy, function_call()) FROM dual;

- Always use CASE instead of NVL2 if parameter 2 or 3 of NVL2 is either a function call or a SELECT
statement.

Some Built-in Utilities


PLSQL_OPTIMIZE_LEVEL
The PLSQL_OPTIMIZE_LEVEL parameter was introduced in 10g to control how much optimization the
compiler performs. There are 4 levels:

- 0: Code will compile and run in a similar way to 9i and earlier. New actions of BINARY_INTEGER
and PLS_INTEGER lost.
- 1: Performs a variety of optimizations, including elimination of unnecessary computations and
exceptions. Does not alter source order.
- 2: Performs additional optimizations, including reordering source code if necessary. This is the
default setting in 10g and 11g. The optimizer may inline code automatically.
- 3: New in 11g.

The optimization level associated with the library unit (plsql code) is visible using the views
(USER_PLSQL_OBJECT_SETTINGS, ALL_PLSQL_OBJECT_SETTINGS or DBA_PLSQL_OBJECT_SETTINGS).

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
select name, type, plsql_optimize_level from user_plsql_object_settings;

To change the value of the PLSQL_OPTIMIZE_LEVEL, issue the following statement

ALTER SESSION SET plsql_optimize_level=0;


ALTER SESSION SET plsql_optimize_level=1;
ALTER SESSION SET plsql_optimize_level=2;
ALTER SESSION SET plsql_optimize_level=3;

PLSQL_WARNINGS
Compiler warnings were introduced in 10g to provide programmers with indications of possible code
improvements. The PLSQL_WARNING parameter can be set using ALTER SYSTEM or ALTER SESSION
commands or the DBMS_WARNING package.

-- Disable warnings in current session


ALTER SESSION SET PLSQL_WARNINGS='DISABLE:ALL';
EXEC DBMS_WARNING.set_warning_setting_string('DISABLE:ALL', 'SESSION');

-- Enable warnings in current session


ALTER SESSION SET PLSQL_WARNINGS='ENABLE:ALL';
EXEC DBMS_WARNING.set_warning_setting_string('ENABLE:ALL', 'SESSION');

The warning level associated with the library unit is visible using the
ALL/USER/DBA_PLSQL_OBJECT_SETTINGS and ALL/USER/DBA_WARNING_SETTINGS views.

SELECT name, plsql_warnings FROM user_plsql_object_settings;

SELECT object_name, warning, setting FROM user_warning_settings;

PLSQL_DEBUG

The parameter PLSQL_DEBUG specifies whether or not PL/SQL library units will be compiled for
debugging. Its value can be true or false, and can be modified by ALTER SESSION or ALTER SYSTEM
command.

Please note that when PLSQL_DEBUG is set to true, PL/SQL library units will always compiled
INTEPRETED in order to be debuggable.

Native Compilation
PL/SQL code is interpreted by default. Prior to 11g, native compilation converts PL/SQL to Proc*C, which
is then compiled in shared libraries. As a result, the parameter plsql_native_library_dir should be set to
store the C libraries. In Oracle 11g, PL/SQL native compilation requires no C compiler. By setting the
PLSQL_CODE_TYPE to the value of NATIVE, the PL/SQL code is compiled directly to machine code and

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
stored in the SYSTEM tablespace. When the code is called, it is loaded into shared memory, making it
accessible for all sessions in that instance. The (USER/ALL/DBA)_PLSQL_OBJECT_SETTINGS views include
the current PLSQL_CODE_TYPE setting for each PL/SQL object.

Please note that native compilation will only improve the speed of procedure code, but has little effect
on the performance of SQL. When code performs lots of mathematical operations, native compilation
can produce considerable performance improves.

Set the parameter PLSQL_NATIVE_LIBRARY_DIR in the database with version earlier than 11g

-- Prior to 11g, set the parameter PLSQL_NATIVE_LIBRARY_DIR first


-- No need in 11g
CONN system/password AS SYSDBA
ALTER SYSTEM SET plsql_native_library_dir = '/u01/app/oracle/admin/DB10G/native';

Set the parameter PLSQL_CODE_TYPE in session level

ALTER SESSION SET plsql_code_type = 'INTERPRETED';


ALTER SESSION SET plsql_code_type = 'NATIVE';

Query the code compilation type

select name, plsql_code_type from user_plsql_object_settings;

Conditional Compilation
Conditional compilation was introduced in 10g to allow PL/SQL source code to be tailored to specific
environments using compilation directives. The PLSQL_CCFLAGS clauses is used to set the compiler flags,
and the compiler flags are identified by the $$ prefix in the PL/SQL code. Conditional control is
provided by the $IF-$THEN-$ELSE syntax. The database source (can be viewed through the view
user_source) contains all the directives, but the post-processed source is displayed using the
DBMS_PREPROCSSOR package.

Set the parameter PLSQL_CCFGAS at code unit level, session level and system level

ALTER PROCEDURE debug COMPILE PLSQL_CCFLAGS = 'debug_on:TRUE, show_date:TRUE' REUSE


SETTINGS;

ALTER SESSION SET PLSQL_CCFLAGS = 'max_sentence:100';

ALTER SYSTEM SET PLSQL_CCFLAGS = 'VARCHAR2_SIZE:100, DEF_APP_ERR:-20001';

Treat different Oracle versions

$IF dbms_db_version.ver_le_10 $THEN

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
-- version 10 and earlier code
$ELSIF dbms_db_version.ver_le_11 $THEN
-- version 11 code
$ELSE
-- version 12 and later code
$END

Print the post-processed source code

BEGIN
DBMS_PREPROCESSOR.print_post_processed_source (
object_type => 'PROCEDURE',
schema_name => USER,
object_name => 'DEBUG');
END;
/

DML Error Logging


By default, when a DML (update, delete, insert, merge) statement fails the whole statement is rolled
back, regardless of how many rows were processed successfully before the error was detected. Before
Oracle 10.2, the only way around this issue was to process each row individually, preferably with a bulk
operation using a FORALL loop with the SAVE EXCEPTIONS clause. In Oracle 10.2, the DML error logging
feature has been introduced to solve this problem. Adding the appropriate LOG ERRORS clause on to
most INSERT, UPDATE, MERGE and DELETE statements enables the operations to complete, regardless
of errors. Exceptional rows are added to a specially-created error table for further investigation.

There are two components to DML error logging as follows:

- LOG ERRORS clause to DML statements; and


- DBMS_ERRLOG package for managing error tables

Create error table for the DML table

BEGIN
DBMS_ERRLOG.CREATE_ERROR_LOG(
dml_table_name => 'TGT', --<-- required
err_log_table_name => 'TGT_ERRORS' --<-- optional
);
END;
/

Specify the LOG ERRORS clause in the DML statement

INSERT INTO tgt


SELECT * FROM src
LOG ERRORS INTO tgt_errors ('INSERT..SELECT..SRC')

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
REJECT LIMIT UNLIMITED;

Use contexts to store global data


A context is a set of application-defined attributes associated with a namespace. Context attributes can
be read by the SYS_CONTEXT function, but must be set by the package specified when the context is
created. Consider using the ACCESSED GLOBALLY clause on the context to make the context
information available to all session. The context values set in the session can be viewed in the table
SESSION_CONTEXT.

Create a context GLOBAL_CONTEXT and the context attribute values should be set in the package
GLOBAL_CONTEXT_API

CREATE OR REPLACE CONTEXT global_context USING global_context_api;

The package that used to set the context value

CREATE OR REPLACE PACKAGE BODY global_context_api IS

-- -----------------------------------------------------------------
PROCEDURE set_parameter (p_name IN VARCHAR2,
p_value IN VARCHAR2) IS
-- -----------------------------------------------------------------
BEGIN
DBMS_SESSION.set_context('global_context', p_name, p_value);
END set_parameter;
-- -----------------------------------------------------------------
...

Set the context value and retrieve the context value

global_context_api.set_parameter('audit_on','N');

DBMS_OUTPUT.put_line('audit_on: ' || SYS_CONTEXT('global_context', 'audit_on'));

DBMS_APPLICATION_INFO
The DBMS_APPLICATION_INFO package allows programs to add information to the V$SESSION and
V$SESSION_LONGOPS views. It can also be used to register long operations which useful for monitoring
the progress of jobs.

Monitor the PL/SQL block execution via V$SESSION

BEGIN
DBMS_APPLICATION_INFO.set_module(
module_name => 'module_action.sql',

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
action_name => 'Starting');

DBMS_APPLICATION_INFO.set_client_info(client_info => 'Part of PL/SQL tuning demo.');

FOR i IN 1 .. 60 LOOP
-- Update the current action.
DBMS_APPLICATION_INFO.set_action(action_name => 'Processing row ' || i);

-- Do some work...
DBMS_LOCK.sleep(1);
END LOOP;

DBMS_APPLICATION_INFO.set_action(action_name => 'Finished');


END;
/

-- query in another session


SELECT sid, serial#, module, action, client_info
FROM v$session
WHERE module = 'module_action.sql';

Monitor time consuming operation via V$SESSION_LONGOPS

DECLARE
l_rindex PLS_INTEGER;
l_slno PLS_INTEGER;
l_totalwork NUMBER;
l_sofar NUMBER;
l_obj PLS_INTEGER;
BEGIN
l_rindex := DBMS_APPLICATION_INFO.set_session_longops_nohint;
l_sofar := 0;
l_totalwork := 100;

WHILE l_sofar < 100 LOOP


-- Do some work
DBMS_LOCK.sleep(0.5);

l_sofar := l_sofar + 1;

DBMS_APPLICATION_INFO.set_session_longops(
rindex => l_rindex,
slno => l_slno,
op_name => 'BATCH_LOAD',
target => l_obj,
context => 0,
sofar => l_sofar,
totalwork => l_totalwork,
target_desc => 'BATCH_LOAD_TABLE',
units => 'rows');
END LOOP;
END;

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
/

-- query in another session


SELECT opname,
target_desc,
sofar,
totalwork,
units
FROM v$session_longops
WHERE opname = 'BATCH_LOAD';

DBMS_SESSION
Many applications manage user access internally and log into the database using a single user, which
makes identifying who is doing what difficult. The DBMS_SESSION package allows to set the
CLIENT_IDENTIFIER column of the V$SESSION view. The client identifier is also included in the audit trail.

-- in session 1
EXEC DBMS_SESSION.set_identifier(client_id => 'fang_test');
-- in session 2
EXEC DBMS_SESSION.set_identifier(client_id => 'fang_test');

-- query the v$session


select * from v$session where client_identifier='fang_test';

DBMS_PROFILER
The DBMS_PROFILER package was introduced in Oracle 8i to make identification of performance
bottlenecks in PL/SQL easier. It records the number of executions and the elapsed time for each line of
code, not the execution order of the code.

DBMS_TRACE
The DBMS_TRACE package records the execution order of PL/SQL, which may help to detect unusual
behavior not visible in the profiler results captured by DBMS_PROFILER.

Use DBMS_TRACE.set_plsql_trace for tracing

BEGIN
DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_calls);
trace_test(p_loops => 100);
DBMS_TRACE.clear_plsql_trace;

DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_sql);
trace_test(p_loops => 100);
DBMS_TRACE.clear_plsql_trace;

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
DBMS_TRACE.set_plsql_trace (DBMS_TRACE.trace_all_lines);
trace_test(p_loops => 100);
DBMS_TRACE.clear_plsql_trace;
END;
/

Query table PLSQL_TRACE_RUNS and PLSQL_TRACE_EVENTS to view trace results

SELECT r.runid,
TO_CHAR(r.run_date, 'DD-MON-YYYY HH24:MI:SS') AS run_date,
r.run_owner
FROM plsql_trace_runs r
ORDER BY r.runid;

SELECT e.runid,
e.event_seq,
TO_CHAR(e.event_time, 'DD-MON-YYYY HH24:MI:SS') AS event_time,
e.event_unit_owner,
e.event_unit,
e.event_unit_kind,
e.proc_line,
e.event_comment
FROM plsql_trace_events e
WHERE e.runid = 3
ORDER BY e.runid, e.event_seq;

Some Traps

Pipelined table function and cardinality


By default, the cardinality of pipelined table functions is based on the block size, so joining operations
can lead to performance issues.

Create a pipelined table function GET_TAB

CREATE TYPE t_tf_row AS OBJECT (


id NUMBER,
description VARCHAR2(50)
);
/

CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;


/

CREATE OR REPLACE FUNCTION get_tab RETURN t_tf_tab PIPELINED AS


BEGIN
FOR i IN 1 .. 100 LOOP
PIPE ROW (t_tf_row(i, 'Description for ' || i));
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
END LOOP;

RETURN;
END;
/

Query the execution plan

set autotrace trace explain


select * from table(get_tab);

Execution Plan
----------------------------------------------------------
Plan hash value: 3023735445

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 24 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB | | | | |
---------------------------------------------------------------------------------------------

set autotrace off

Please note that the ROWS returned shown in the execution plan is 8168 while the actual row returned
by the function GET_TAB is 100! This will lead to performance issue if join the rows returned by this
table function with other tables.

There are several ways to resolve this issue; the first one is to use the undocumented hint cardinality
(available since Oracle 9i) as follows,

SQL> select /*+cardinality(100)*/ * from TABLE(get_tab);

Execution Plan
----------------------------------------------------------
Plan hash value: 3023735445

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 200 | 24 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB | | | | |
---------------------------------------------------------------------------------------------

In Oracle 10g and above, a new undocumented hint opt_estimate can be used. This hint seems
doesnt work that well as cardinality. See the example below,

select /*+ opt_estimate(table, t, scale_rows=0.01*/ * from TABLE(get_tab) t;


---------------------------------------------
By Yu, Fang (franky.yu@163.com)
Execution Plan
----------------------------------------------------------
Plan hash value: 3023735445

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 8168 | 16336 | 24 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB | | | | |
---------------------------------------------------------------------------------------------

To use this hint, the scale_rows needs to be set correctly, which is calculated by the function of
round(actual rows/8168, 2) which is 0.01 in this case. However, seen from the execution plan the ROWS
returned is still 8168! So use this hint with caution.

In Oracle 11.1, another new hint dynamic_sampling was introduced. The difference between this one
and the two mentioned above is that this new hint is documented. Thats to say, this hint can be used
safely.

Besides taking advantage of Oracle hints, Oracle 10g also introduced a new feature called Extensible
Optimizer which can be used to tell the optimizer wheat the cardinality should be in a supported
manner. The disadvantage of using extensible optimizer is that it is more complex than hints.

First, need to change the definition of the function GET_TAB to add one parameter

CREATE OR REPLACE FUNCTION get_tab (p_cardinality IN INTEGER DEFAULT 1)


RETURN t_tf_tab PIPELINED AS
BEGIN
FOR i IN 1 .. 100 LOOP
PIPE ROW (t_tf_row(i, 'Description for ' || i));
END LOOP;

RETURN;
END;
/

Create one type T_PTF_STATS to implement some ODCI interface functions

CREATE OR REPLACE TYPE t_ptf_stats AS OBJECT (


dummy INTEGER,

STATIC FUNCTION ODCIGetInterfaces (


p_interfaces OUT SYS.ODCIObjectList
) RETURN NUMBER,

STATIC FUNCTION ODCIStatsTableFunction (


p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
p_args IN SYS.ODCIArgDescList,
p_cardinality IN INTEGER
) RETURN NUMBER
);
/

CREATE OR REPLACE TYPE BODY t_ptf_stats AS


STATIC FUNCTION ODCIGetInterfaces (
p_interfaces OUT SYS.ODCIObjectList
) RETURN NUMBER IS
BEGIN
p_interfaces := SYS.ODCIObjectList(
SYS.ODCIObject ('SYS', 'ODCISTATS2')
);
RETURN ODCIConst.success;
END ODCIGetInterfaces;

STATIC FUNCTION ODCIStatsTableFunction (


p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_cardinality IN INTEGER
) RETURN NUMBER IS
BEGIN
p_stats := SYS.ODCITabFuncStats(p_cardinality);
RETURN ODCIConst.success;
END ODCIStatsTableFunction;
END;
/

Associate the function GET_TAB with the type T_PTF_STATS

ASSOCIATE STATISTICS WITH FUNCTIONS get_tab USING t_ptf_stats;

Then the SQL explain plan shows the row count returned by the function is right

SQL> select * from table(get_tab(100));

Execution Plan
----------------------------------------------------------
Plan hash value: 3023735445

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 200 | 24 (0)| 00:00:01 |
| 1 | COLLECTION ITERATOR PICKLER FETCH| GET_TAB | | | | |
---------------------------------------------------------------------------------------------

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
DBMS_RANDOM
DBMS_RANDOM is usually used to generate a random value. However, if dont use this package with
care, the values generated by this package may be not random. Below is an example

Suppose there is one table name T which has one column of data type VARCHAR2. Whats more, there is
one unique constraint on this column. Since the system doesnt care the value of the string values as
long as they are unique in the table, package DBMS_RANDOM can be used. Below is one PL/SQL
function to generate the random string value,

CREATE OR REPLACE FUNCTION GET_NEXT_STR RETURN VARCHAR2


IS
V_SEED VARCHAR2(50);
BEGIN
SELECT TO_CHAR(SYSTIMESTAMP, 'ddmmyyyyHH24MMSSFF') INTO V_SEED FROM DUAL;
DBMS_RANDOM.SEED(V_SEED);

RETURN DBMS_RANDOM.STRING('A', 20);


END GET_NEXT_STR;

Before getting down to using this function in the table insertion SQL statement, perform a simple test to
see what the values are generated

set serveroutput on
BEGIN
FOR I IN 1..10
LOOP
DBMS_OUTPUT.PUT_LINE(GET_NEXT_STR);
END LOOP;
END;
And the results are like below

AwPUykNKErDexsElUnwC
snKBvcxsTUiMSLWuMCgo
snKBvcxsTUiMSLWuMCgo
snKBvcxsTUiMSLWuMCgo
snKBvcxsTUiMSLWuMCgo
snKBvcxsTUiMSLWuMCgo
zNuLksgAvpBiSqXGqCeA
zNuLksgAvpBiSqXGqCeA
zNuLksgAvpBiSqXGqCeA
zNuLksgAvpBiSqXGqCeA

What a surprise! The values generated by the function are not unique. The problem lies in the function
GET_NEXT_STR uses the current timestamp as the SEED for the random function. Due to the precise of
the timestamp and the fast execution speed of CPU, the seed value may be the same for several loops.
Thats why the random values returned by the DBMS_RANDOM are not random. To resolve this issue,
dont use the timestamp as the seed, use the seed generated by the system automatically.

---------------------------------------------
By Yu, Fang (franky.yu@163.com)
The literal string length limit in SQL statement
The maximum string length can be used in SQL statement is 4000 bytes while this value can be increased
up to 32767 in PL/SQL.

See below for some tests about the maximum string length supported in SQL select clause

SQL> select length(rpad('A', 3999, '*')) from dual;


LENGTH(RPAD('A',3999,'*'))
--------------------------
3999

SQL> select length(rpad('A', 4000, '*')) from dual;


LENGTH(RPAD('A',4000,'*'))
--------------------------
4000

SQL> select length(rpad('A', 4001, '*')) from dual;


LENGTH(RPAD('A',4001,'*'))
--------------------------
4000

SQL> select length(rpad('A', 40001, '*')) from dual;


LENGTH(RPAD('A',40001,'*'))
---------------------------
4000

The test result shows that the maximum string length can be supported in SQL select statement is 4000.
Even use the RPAD function to construct a string with length larger than 4000, the function LENGTH still
return 4000.

If reference one function which returns a string whose length larger than 4000, there will be an error
raised. See the example below for illustration

SQL> CREATE OR REPLACE FUNCTION test_varchar2_max_length RETURN varchar2 AS


2 BEGIN
3 return RPAD('A', 4001, '*');
4 END;
5 /

Function created.

SQL> select test_varchar2_max_length from dual;


select test_varchar2_max_length from dual
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "FRANK.TEST_VARCHAR2_MAX_LENGTH", line 3

SQL>
---------------------------------------------
By Yu, Fang (franky.yu@163.com)
To resolve this issue, the return type of the function test_varchar2_max_length needs to be changed to
CLOB or some collection type.

---------------------------------------------
By Yu, Fang (franky.yu@163.com)

You might also like