Using Oracle Database 10g PL/SQL New Features

In this tutorial, you learn about the new PL/SQL features that are introduced in Oracle Database 10g.

Approximately 30 minutes

Topics

This tutorial covers the following topics:

Using PL/SQL Conditional Compilation

Examining Bulk Binding with the INDICES OF and VALUES OF Keywords

Place the cursor over this icon to load and view all the screenshots for this tutorial. (Caution: This action loads all screenshots simultaneously, so, depending on your Internet connection, may result in a slow response time.)

Note: Alternatively, you can place the cursor over an individual icon in the following steps to load and view only the screenshot associated with that step. You can hide an individual screenshot by clicking it.

In Oracle Database 10g, several enhancements are available in the PL/SQL language. In this overview section, you cover the following topics:

1.

PL/SQL Conditional Compilation

2. Examining Bulk Binding Enhancements
3. Debugging PL/SQL with JDeveloper

Back to Topic List

PL/SQL Conditional Compilation

Oracle Database 10g Release 2 introduces support for the conditional compilation of PL/SQL. New syntax allows the compiler to select parts of a program, based on specified conditions, for compilation.  Application developers use conditional compilation by embedding directives in their PL/SQL source program. When the PL/SQL program is submitted for compilation, the directives are evaluated to select the intended parts of the program to be compiled. This feature is very useful in the following cases:

Applications often contain debugging or tracing code that is written specifically for testing purposes during the development cycle. You don’t want this code to run when the application runs in production. In earlier releases, you had either to remove this code completely for production (hindering subsequent debugging) or to surround it with run-time IF tests (incurring a run-time cost in both program size and execution speed). Now you can enable debugging or tracing functionality in the development environment and disable that functionality in the production application.

 

Applications often contain self-checking code, to comply with good programming practices. Such code raises an exception if an invariant on which the program design depends is violated. Such exceptions ideally should never be raised in the production environment. However these self-tests incur a run-time cost especially when they occur inside deeply nested loops. But to remove them all, after system testing is done and before the application goes live, would compromise the ability of the debugging team to diagnose bugs that were first seen in the production environment. Few development shops maintain parallel copies of an application’s source: one for debugging and one for production. PL/SQL conditional compilation supports a new best practice: by selectively compiling the self-checks, the diagnosing benefit is retained without cost for the production environment.

 

Packages usually contain private "helper" subprograms and, of course, these need to be tested during development. But because they're private, they can't be tested by an external test harness. By declaring these "private" subprograms within a conditional directive in the package specification, you can call these programs from outside the package during development and testing and then conceal them in the production environment.

 

Application code often calls “service” subprograms. Such a service subprogram might return a PL/SQL record describing an employee when called with the employee's unique identifier. Best practice dictates that this service routine raises a well-defined exception if the supposedly unique key finds more than one record. (This would occur in the event of data corruption, caused by a different, and buggy, component of the total system.) Best practice further dictates that the application code that calls the service subprogram must behave correctly when this exception is raised, for example by showing the user a polite and relatively non-specific “problem occurred” message and by logging all the relevant information to specify the context in which the exception was raised to a trace file. Of course, this robustness feature of the application code must be tested during the development cycle. It's often tricky and time-consuming to contrive the corrupt data that would cause the service subprogram to raise the exception. Conditional compilation lets you achieve the same effect cheaply by conditionally including code in the service subprogram to raise the given exception at will.

 

During development, programmers often create two different versions of a compilation unit that accomplish the same result. Both versions have to be tested to determine the most efficient version. Usually, the two different versions are substantially textually identical but differ in small critical spots distributed over the whole source file. In such cases it's easier, and less prone to human error, to write one program containing both versions rather than to write two separate programs. Conditional compilation comfortably supports this approach where earlier a hand-crafted scheme for generating the two versions of the source code would have been needed.

 

Some applications must be written to run on not only the latest version of Oracle Database but also on earlier versions. Typically, the development shop wants to maintain only a single body of source code. This has forced developers to avoid using the new and powerful features (exposed by new PL/SQL syntax) in the latest release which has penalized users who do deploy the code on the latest release. Conditional compilation can be used to select the optimal version of the program source code when the program is installed on the latest release and a fallback when it is installed on an earlier release.

 

Conditional compilation has been made available in Oracle Database 10g Release 10.1.0.4 and later.

Back to Topic

Examining Bulk Binding Enhancements

Oracle Database 10g extends the enhancements that were introduced in Oracle9i in the area of bulk binding. The SAVE EXCEPTIONS syntax was introduced in Oracle9i to capture exceptions while bulk inserting (deleting or updating) rows. Although this feature enables the data manipulation language (DML) to continue (saving any exceptions in the SQL%BULK_EXCEPTIONS collection), the performance of the operation is greatly affected. In a scenario in which the collection being processed is sparse due to the application logic involved, this would be an unnecessary overhead. In Oracle Database 10g, you can overcome these issues with the new INDICES OF and VALUES OF features.

Back to Topic

Debugging PL/SQL with JDeveloper

Starting with version 9.0.3, JDeveloper supports PL/SQL debugging. This is achieved with the Java Debugging Wire Protocol (JDWP). With Oracle9i, release 2 (and later versions), the debugger uses the industry standard, whereas for earlier releases (Oracle8i and Oracle9i, release 1), the DBMS_DEBUG package is used. The debugging UI gives you the ability to single step, step into, and step over PL/SQL objects as well as the ability to set PL/SQL expressions in the Watch and Inspector windows, set conditional breakpoints, and view collection data.

Back to Topic

Before your perform this tutorial, you should:

1.

Perform the Installing Oracle Database 10g on Windows tutorial.

2.

Download and unzip the plsql.zip file into your working directory (i.e. c:\wkdir).

Back to Topic List

Using PL/SQL Conditional Compilation

This section shows you how to use conditional compilation and gives some usage examples.

Back to Topic List

Preprocessor Directives

Directives are composed of directive control tokens "$"and ordinary PL/SQL text. Conditional compilation uses three directives, selection, inquiry, and error. A special trigger character '$' denotes the conditional compilation directives. The selection directive is essential to the conditional compilation mechanism; the inquiry and error directives support useful extra functionality.

Back to Topic

Selection directive

The selection directive evaluates conditional expressions and selects code to be included in the compilation based on the outcome of that evaluation.The code that is not selected is ignored completely. There is no interference with existing programs, which do not use conditional compilation. A conditional selection directive starts with $if and has the general syntax:

$if <B-expr > $then
  <PL/SQL-code-fragment>
$elsif <B-expr > $then
  <PL/SQL-code-fragment>

$else
  <PL/SQL-code-fragment>
$end

Where <B-Expr> stands for static BOOLEAN expression. A static BOOLEAN expression is any combination of one or more package constants or one or more inquiry directives. An example of a selection directive employing a package constant as the static BOOLEAN expression is:

$if Trace_Pkg.Trace > 2 $then … $end  

Where Trace_pkg is the name of the package and Trace is a constant declared as a PLS_INTEGER. Notice that when a constant declared in package Trace_pkg is used in a selection directive in a PL/SQL compilation unit U , then U has a dependency on Trace_pkg just as if Trace_pkg had been referred to in regular PL/SQL code.

Back to Topic

Inquiry Directive

The inquiry directive allows access to the compilation environment,  so that selection may be based on the current environment. An inquiry directive takes the form $$<PL/SQL-identifier>. For example, the value of a PL/SQL compiler parameter (such as PLSQL_OPTIMIZE_LEVEL) for the unit being compiled. Such an inquiry can be used in conjunction with the conditional selection directive to select the parts of the program to compile. For example, you can use the inquiry directive to query the value of a PL/SQL compiler parameter. An inquiry directive starts with two trigger characters $$ such as:

$if $$debug_level > 3 $then … $end

The identifier debug_level is defined using the plsql_ccflags initialization parameter (new in Oracle Database 10g Release 2) thus:

...plsql_ccflags    = 'debug_level:4, ...'

Back to Topic

Error Directive

An error directive takes the form

$error <VARCHAR2-expression> $end.

It causes the compiler to report a compilation error including the message provided in the VARCHAR2 expression.

Back to Topic

Predefined Compiler Parameters

The Oracle Database 10g Release 2 provides the following PL/SQL compiler parameters that can be used in conditional inquiry directives:

PLSQL_CCFLAGS.

PLSQL_DEBUG

PLSQL_OPTIMIZE_LEVEL

PLSQL_CODE_TYPE

PLSQL_WARNINGS

NLS_LENGTH_SEMANTICS

The values of the PL/SQL compiler parameters at the time of compilation are stored with the compilation unit and can be inspected using the all_plsql_object_settings view family. In addition the predefined inquiry directives $$PLSQL_UNIT, $$PLSQL_LINE are available. For more information and details on the syntax for conditional compilation directives, refer to the PL/SQL User Guide and Reference manual.

Back to Topic

Using Directives

Example 1: Tracing and Debugging Using Package Constants

Example 2:Using Compiler Warnings and PLSQL_CCFLAGS

Example 3:Using DBMS_PREPROCESSOR Procedures to Print or Retrieve Source Text 

Example 4: Using Conditional Compilation to Branch Code to Determine Version with Best Performance

Example5: Using Conditional Compilation with Different Versions of the Oracle Database

Back to Topic

Example 1: Tracing and Debugging using Package Constants

Using package constants in conditional compilation directives provides a method to control one or more PL/SQL compilation units with a single mechanism. For example suppose that your application consists of many PL/SQL compilation units. Within the application you have embedded methods that perform debugging or tracing. These methods can be turned on with conditional compilation directives using package constants. Therefore you can change the value of the constants by recompiling the package at anytime. When the package is recompiled all dependent objects are automatically recompiled therefore accepting the new values of the package constant. This can be used for turning tracing and debugging on throughout the entire application. When you are done tracing and debugging, recompile the package specification with new values of the constants that guard the tracing and debugging code. This will invalidate all dependent PL/SQL units so that on next use the tracing and debugging will not be selected for compilation. The use of package constants is an effective mechanism to control all of the dependent PL/SQL units that use the packaged constants within selection directives for conditional processing. The following example demonstrates this use case.

1.

Open a terminal window and execute the following commands:

cd\wkdir
sqlplus hr/hr

 

2.

Create the package STATIC_CONSTANTS to declare package constants that can be used in conditional compilation. From your SQL*Plus session, execute the following script:

@static_constants

The static_constants.sql script contains the following:

CREATE OR REPLACE PACKAGE static_constants is
debug CONSTANT BOOLEAN := FALSE;
trace CONSTANT BOOLEAN := FALSE;
END ;
/

 

3.

Create two procedures CHECK_DEBUG which checks whether the value of the package constant debug is TRUE and CHECK_TRACE which checks if the value of the package trace is TRUE. From your SQL*Plus session, execute the following script:

@debug_trc

The debug_trc.sql script contains the following:

CREATE OR REPLACE PROCEDURE check_Debug IS
BEGIN
$IF static_constants.debug $THEN DBMS_OUTPUT.put_line('Debugging ON');
$ELSE DBMS_OUTPUT.put_line('Debugging OFF'); $END
END;
/
CREATE or REPLACE PROCEDURE Check_trace IS
BEGIN
$IF static_constants.trace $THEN DBMS_OUTPUT.put_line('Tracing ON');
$ELSE DBMS_OUTPUT.put_line('Tracing OFF'); $END
END;
/

 

4.

Set serveroutput on  using the new SIZE UNLIMITED syntax available in Oracle Database 10g Release 2. Now execute both the procedures. You will see that it displays that both tracing and debugging are tuned off.

Note: You can copy paste each line individually into sqlplus however do not copy and paste the 3 statements together.

set serveroutput on size unlimited
exec check_debug
exec check_trace

 

5.

Now you will change the values of the package constants. From your SQL*Plus session, execute the following script:

@reset_const

The reset_const.sql script contains the following:

CREATE OR REPLACE PACKAGE static_constants is
debug CONSTANT BOOLEAN := TRUE;
trace CONSTANT BOOLEAN := TRUE;
END ;
/

 

6.

Now execute the two procedures again. You will see that both debugging and tracing are on automatically. When a package is recompiled all dependent objects become invalid and get recompiled at the next execution. Therefore the new values of the package constants apply to all the dependent objects immediately.

exec check_debug 
exec check_trace

This can be extended to any number of dependent programs. This method is most useful when a large number of programs need to be controlled with a single mechanism. Other examples of use cases are changing tax by state for the same application or changing software features based on licensing options and so on.

 

Back to Subtopic

Example 2 : Using Compiler Warnings and PLSQL_CCFLAGS

You can use the conditional parameter PLSQL_CCFLAGS to provide fine-grained access to specific programs by setting the value of PLSQL_CCFLAGS. You can either use an ALTER SESSION command or an ALTER...COMPILE command to do this. The ALTER COMPILE command only affects the program being compiled. The ALTER SESSION command can be used to affect more than one program by recompiling these programs with CREATE or REPLACE following the ALTER SESSION statement.

In the following example you are creating a procedure called GET_RECORD that accepts the value of an employee_id and displays the corresponding record. To maintain good programming practice, this procedure calls a CHECK_UNIQUE procedure that verifies the uniqueness of each employee record. In the rare case that a duplicate exists, an user friendly message is received and an alert with the employee id is sent to the DBA using a procedure called SEND_MESSAGE_TO_DBA. However since the data in the testing database does not contain any duplicates you can simulate duplicates using PLSL_CCFLAGS with a static BOOLEAN expression in an inquiry directive.

1.

Alter the session to show all compiler warnings. Execute the following from your SQL*Plus session:

ALTER SESSION SET  plsql_warnings = 'enable:all';

Note that all conditional compilation directive have been removed.

 

2.

Create the SEND_MESSAGE_TO_DBA and CHECK_UNIQUE procedures. The CHECK_UNIQUE procedure contains two different pieces of code, one that is used in development for testing and the other that is deployed in the final production environment. The development code uses conditional compilation directives and the production code uses a traditional IF-THEN-ELSE logic to check for duplicate records. From your SQL*Plus session, execute the following scripts:

@dba_email
@chk_unq
show errors

The dba_email.sql script contains the following:

CREATE OR REPLACE PROCEDURE send_message_to_DBA(emp_id number)
IS
mailhost VARCHAR2(64) := 'mailhost.fictional-domain.com';
sender VARCHAR2(64) := 'HR_APP@fictional-domain.com';
recipient VARCHAR2(64) := 'DBA@fictional-domain.com';
mail_conn utl_smtp.connection;
BEGIN
mail_conn := utl_smtp.open_connection(mailhost, 25);
utl_smtp.helo(mail_conn, mailhost);
utl_smtp.mail(mail_conn, sender);
utl_smtp.rcpt(mail_conn, recipient);
-- open_data(), write_data(), and close_data() into a single call to data().
utl_smtp.open_data(mail_conn);
utl_smtp.write_data(mail_conn, 'A primary key violation has occurred for record '||
emp_id || 'in the EMPLOYEES table. This is an automatically generated e-mail message. Please do not respond to this, this is an alert.' || chr(13));
utl_smtp.write_data(mail_conn, 'This is line 2.' || chr(13));
utl_smtp.close_data(mail_conn);
utl_smtp.quit(mail_conn);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/

The chk_unq.sql script contains the following:

Create or replace Procedure check_unique(emp_id NUMBER) is
v_num number;
force_pk_violation exception;
Begin
Select count(*) into v_num from employees where employee_id = emp_id;
-- production code
If v_num > 1
then SEND_MESSAGE_TO_DBA(emp_id);
raise force_pk_violation;
END IF ;
-- Development code
$if $$FORCE
$then SEND_MESSAGE_TO_DBA(emp_id);
raise force_pk_violation;
$end
END;
/


3.

Now alter the session to set the value of $$force to FALSE. Execute the following command from SQL*Plus.

ALTER SESSION SET PLSQL_CCFLAGS='force:FALSE';

 

4.

Now execute chk_unq.sql again.

The warning does not appear as the variable $$force now has a value of FALSE;

 

5.

Create the GET_RECORD procedure. This procedure accepts an employee id and returns the employee record. If a duplicate exists as checked by the CHECK_UNIQUE procedure a friendly message is displayed . Execute the following script in your SQL*Plus session:

@get_record

The get_record.sql script contains the following:

Create or replace procedure get_record(emp_id IN NUMBER)
as
emp_record employees%rowtype;
Begin
check_unique(emp_id);
Select * into emp_record from employees where employee_id =emp_id;
Dbms_output.put_line ( 'Name: '|| emp_record.first_name||' '||emp_record.last_name||' '||'Hiredate: ' ||emp_record.hire_date||'
'||'Job: '||' '||emp_record.job_id);
Exception
When others then
DBMS_OUTPUT.PUT_LINE('We are unable to process your request at this time.
Please try again later. We apologize for any inconvenience');
End;
/


6.

Now execute the GET_RECORD procedure passing in 100 as the argument.

exec GET_RECORD(100)      

Since there are no duplicates the procedure executes successfully .

 

7.

Now to test whether the message is displayed when there are duplicates, alter the session and set the value of $$force to TRUE and execute GET_RECORD again. Execute the following commands from your SQL*Plus session.

ALTER SESSION SET PLSQL_CCFLAGS='force:TRUE';
exec GET_RECORD(100) 

The procedure is not affected as it has not been recompiled.


8.

Recompile the CHECK_UNIQUE procedure using ALTER...COMPILE statement and provide the new value of the PLSQL_CCFLAGS parameter. Use the REUSE SETTINGS option. Then execute GET_RECORD again for a value of 100. Execute the following commands from your SQL*Plus session.

ALTER PROCEDURE CHECK_UNIQUE compile 
  plsql_ccflags = 'force:TRUE' REUSE SETTINGS;
exec GET_RECORD(100) 

The PLSQL_CCFLAGS value now is in effect and therefore the program issues the message. This method can be used to affect specific programs within a session as is post production debugging. However each program that needs the new setting has to be recompiled either using CREATE OR REPLACE or ALTER...COMPILE statements.

 

Back to Subtopic

Example 3: Using DBMS_PREPROCESSOR Procedures to Print or Retrieve Source Text

DBMS_PREPROCESSOR subprograms print or retrieve the post-processed source text of a PL/SQL unit after processing the conditional compilation directives. This post-processed text is the actual source used to compile a valid PL/SQL unit. Perform the following steps:

1.

Execute the following command to see the actual source code used to compile a valid PL/SQL program: 

EXEC DBMS_PREPROCESSOR.PRINT_POST_PROCESSED_SOURCE('PROCEDURE', 'HR', 'CHECK_UNIQUE');

Note that all conditional compilation directive have been removed.

 

Back to Subtopic

Example 4: Using Conditional Compilation to Branch Code to Determine Version with Best Performance

Oracle Database 10g introduces the BINARY_DOUBLE datatype that can be used for arithmetic intensive operations. In this example, you will test how the BINARY_DOUBLE datatype compares with the NUMBER datatype. You will create the same code in two different versions, one using NUMBER and the other using BINARY_DOUBLE. You can then include both versions within the same procedure to test using PLSQL_CCFLAGS. Perform the following steps:

1.

Issue the ALTER SESSION to set flags to choose between the two versions. First you are choosing to use the NUMBER datatype. From your SQL*Plus session, execute the following command:

ALTER SESSION SET PLSQL_CCFLAGS = ' numversion:TRUE';

 

2.

You want to see which code executes faster. You will create the CALC_CIRCLE procedure, which calculates the area and circumference of a circle for a given radius. From your SQL*Plus session, execute the following script:

@num_bdbl

The num_bdbl.sql script contains the following:

CREATE or REPLACE PROCEDURE Calc_circle( RADIUS $IF $$NUMVERSION $THEN NUMBER 
$ELSE BINARY_DOUBLE $END)
as
SUBTYPE my_real IS
$IF $$numversion $THEN
NUMBER;
$ELSE
BINARY_DOUBLE;
$END
num_circ my_real;
num_area my_real;
BDBL_circ my_real;
BDBL_AREA my_real;
BEGIN
num_CIRC:= (3.14016408289008292431940027343666863227 * 2 * RADIUS);
NUM_AREA := (3.14016408289008292431940027343666863227*radius*radius);
DBMS_OUTPUT.PUT_LINE('The circumference is: '||num_circ);
DBMS_OUTPUT.PUT_LINE('The area is: '||num_area);
END ;
/

 

3.

Execute the CALC_CIRCLE procedure passing in the number 1234567890 as the radius

set timing on
exec calc_circle(1234567890)

 

4.

Change the value of $$numversion to FALSE using ALTER COMPILE command to compile CALC_CIRCLE and execute CALC_CIRCLE again with the same argument.

ALTER PROCEDURE  calc_circle COMPILE plsql_ccflags = 'numversion :false' REUSE SETTINGS; 
exec calc_circle(1234567890)
set timing off

You can see the difference in performance. This example illustrates that it is possible to test two different versions of code selectively within the same program using conditional compilation.

 

Back to Subtopic

Example 5: Using Conditional Compilation with Different Versions of the Oracle Database

The following example shows the use of a DBMS_DB_VERSION constant with conditional compilation. Both the Oracle database version and release are checked. This CHECK_VERSIONS procedure uses the COMMIT WRITE IMMEDIATE NOWAIT command which has been introduced in Oracle Database 10g Release 2. If this procedure were to be executed in a prior release of the Oracle Database the command would not be recognized. Therefore the procedure uses DBMS_DB_VERSION package to check for the version of the database. If the version returned is lower the 10.2 then a regular COMMIT is used to commit the transaction. When the procedure is executed in a version of the database that is 10.2 or higher then the COMMIT WRITE IMMEDIATE NOWAIT command is used to complete the transaction.

1.

Now you can test the version of the Oracle Database. Execute the following script from your terminal window:

@check_version

The check_version.sql script contains the following:

CREATE OR REPLACE PROCEDURE check_version AS
BEGIN
-- some code which performs transaction processing ...
$if DBMS_DB_VERSION.VER_LE_10_2 $then
-- traditional commit
COMMIT;
DBMS_OUTPUT.PUT_LINE ('The transaction has been successfully committed.');
$else
-- faster COMMIT supported in 10.2
COMMIT WRITE IMMEDIATE NOWAIT;
DBMS_OUTPUT.PUT_LINE ('The transaction has been successfully committed.');
$end
END;
/

 

2.

Now execute the check_version procedure by executing the following command in your SQL*Plus window.

exec check_version
quit

The output of check_version shows that regardless of the version the procedure completes successfully and is transparent to the end user. Therefore there is no reason to have different program units in different versions of the database.

 

Back to Subtopic

The following example illustrates the use of the INDICES OF and VALUES OF keywords for bulk binding in PL/SQL.

The INDICES OF keyword can be used in a scenario in which a collection of records (which is dense) is validated programmatically and invalid records (those not meeting the specified criterion) are removed from the collection. This results in a sparse collection of valid elements that must then be bulk inserted into a table. By using the INDICES OF keyword, the exceptions for the missing records are not generated.

The VALUES OF keyword can be used in a scenario in which a collection of records (sparse or dense) must be copied to one or more collection variables, based on some condition whereby certain records may or may not be copied, and then inserted into a table. This can be efficiently done by using the "VALUES OF" syntax and using a pointer array whose elements are pointers to the selected records within the original collection. This reduces the need to create multiple copies of data. Exception handling is done per pointer record, which means that if two or more pointer records are pointing to the same "original" data, any exceptions that are reported indicate the iteration number (pointer element number).

This example uses the EMPLOYEES table to build a collection of employees that is deliberately sparse.

1.

You must first create a NEW_EMPLOYEES table from the existing EMPLOYEES table (in the hr schema) and create a unique index on the EMPLOYEE_ID column. This generates an exception when inserting a duplicate record. Execute the following commands from a terminal window:

sqlplus hr/hr
@setup

The setup.sql script contains the following:

drop table new_employees;
create table new_employees as select * from employees where 1=2;
create unique index new_employees_employee_id 
  on new_employees (employee_id);

 

2.

You next create the P_BULK_BIND package. This package contains various procedures that create the sparse collection and bulk insert the records into the NEW_EMPLOYEES table using three different approaches.

@cr_p

Click cr_p.sql to see what is contained in this script.

 

3.

To see how bulk binding works in Oracle Database 10g, we first look at how it was done in Oracle9i. The FORALL statement uses the SAVE EXCEPTIONS syntax to suppress exceptions raised due to missing records, which are later printed in the EXCEPTION block. Execute the following commands:

set serverout on size 100000
exec p_bulk_bind.Bulk_Insert_Pre_10g (i_make_sparse => true)

-- this code is located in the Bulk_Insert_Pre_10g procedure
...
forall j in g_emp_recs.First()..g_emp_recs.Last()
save exceptions
  insert into new_employees values g_emp_recs(j);
exception when bulk_errors then
  for j in 1..sql%bulk_exceptions.Count()
  loop
    Dbms_Output.Put_Line ( 'Error from element #' ||
    To_Char(sql%bulk_exceptions(j).error_index) || ': ' ||
    Sqlerrm(SQL%bulk_exceptions(j).error_code) );
  end loop;
...

From the output, you see that each missing record has generated an exception (ORA-22160). As mentioned earlier, even though the remaining records are processed via the SAVE EXCEPTIONS syntax, this results in a performance degradation if the number of deleted records is fairly high.

 

4.

The next step is to use the Oracle Database 10g INDICES OF syntax to ignore the missing records and thereby obtain much better performance. Execute the following commands:

exec p_bulk_bind.Bulk_Insert_With_Indices_Of (i_cause_exception => false)

In this case, the Boolean input value is used to ignore a block of code that is responsible for creating an exception condition (as you will see in the next step).

-- this code is located in the Bulk_Insert_With_Indices_Of 
-- procedure
...
forall j in indices of g_emp_recs
save exceptions
  insert into new_employees values g_emp_recs(j);
...

This time there are no exceptions raised because of the missing records.

 

5.

The preceding procedure can also be run with the input parameter set to true. This causes an exception while inserting that must be trapped (by SAVE EXCEPTIONS) and later processed. The condition causing the exception is simulated via a procedure call, which nulls the EMAIL columns for the employees with the last name of "Ernst" and "Urman." Because the EMAIL column has a NOT NULL restriction, this causes an exception when the two corresponding records are inserted. The exceptions are later processed from the SQL%BULK_EXCEPTIONS collection. Execute the following commands:

exec p_bulk_bind.Bulk_Insert_With_Indices_Of (i_cause_exception => true)

-- this code is located in the exception area
-- in the Bulk_Insert_With_Indices_Of procedure
...
forall j in indices of g_emp_recs
-- between g_emp_recs.First() and g_emp_recs.Last() 
-- optional
save exceptions
  insert into new_employees values g_emp_recs(j);
exception when bulk_errors then
  declare
    v_iteration pls_integer;
    n pls_integer;
    k pls_integer;
  begin
    for j in 1..sql%bulk_exceptions.Count()
    loop
      v_iteration := SQL%bulk_exceptions(j).error_index;
      Dbms_Output.Put_Line (
        'Error on the ' || To_Char(v_iteration) 
         || 'th iteration' );
 ...
         

Note: The error is reported for the iteration number. This is new to the exception handling in Oracle Database 10g.

 

6.

Another approach is to use the VALUES OF clause to create a second collection of pointers to the elements of the original array. Execute the following commands:

exec p_bulk_bind.Bulk_Insert_With_Values_Of -
   (i_null_email => true, i_violate_pk => false)

The Point_To_Sparse procedure is used to create a collection of pointers based on the same logic as before. That is, it points to the elements with the first occurrence of each letter in the last name. It is called from the Bulk_Insert_With_Values_Of procedure.

-- this code is located in the Bulk_Insert_With_Values_Of 
-- procedure
...
forall j in values of g_values_of_tab
save exceptions
  insert into new_employees values g_emp_recs(j);
...

The result is the same as in step 5: An exception is raised for the fifth and nineteenth iterations.

 

7.

To see the effect of reporting the iteration number (or pointer element number) as opposed to the index of the erroneous record, you can run the preceding procedure with i_violate_pk => true. To simulate the error condition, the Cause_Exception_For_Values_Of procedure is used to insert a duplicate index entry from the bound collection (G_EMP_RECS) into the pointer collection (G_VALUES_OF_TAB). Because of this, the unique key constraint (created in step 1) is violated and the exception is raised upon bulk insert into the NEW_EMPLOYEES table. Execute the following commands:

exec p_bulk_bind.Bulk_Insert_With_Values_Of -
   (i_null_email => false, i_violate_pk => true)

-- this code is located in the Bulk_Insert_With_Values_Of 
-- procedure
...
exception when bulk_errors then
  declare
     v_iteration pls_integer;
   begin
     for j in 1..sql%bulk_exceptions.Count()
     loop
       v_iteration := SQL%bulk_exceptions(j).error_index;
       Dbms_Output.Put_Line (
         'Error on the ' || To_Char(v_iteration) ||
         'th iteration' );
-- Find the index of the offending element 
-- from the iteration number
       Dbms_Output.Put_Line (
         'last_name for error element: ' ||
          g_emp_recs(g_values_of_tab(v_iteration)).last_name );
       Dbms_Output.Put_Line (
         'Error was: ' || 
          Sqlerrm(SQL%bulk_exceptions(j).error_code) );
     end loop;
   end;
... 

Back to Topic List

In this tutorial, you learned how to:

How PL/SQL Conditional Compilation can be Performed
Examine the Relaxation of Overall Limit for DBMS_OUTPUT PL/SQL Package.
Examine the Bulk Binding Feature

Back to Topic List