O'Reilly Databases

oreilly.comSafari Books Online.Conferences.

We've expanded our coverage and improved our search! Search for all things Database across O'Reilly!

Search Search Tips

advertisement
AddThis Social Bookmark Button

Listen Print Discuss Subscribe to Databases Subscribe to Newsletters

Native Compilation, CASE, and Dynamic Bulk Binding
Pages: 1, 2, 3, 4

In-Binding

Both the EXECUTE IMMEDIATE and FORALL (for bulk DML operations) offer a USING clause to bind variable values into the SQL statement. Let's follow the progression of explicit row-from-row processing to bulk binding to bulk binding in native dynamic DML to see how the USING clause is deployed.



We start with this kind of explicit FOR loop in our Oracle7 and Oracle8 code base:

FOR indx IN 
   employee_ids.FIRST .. employee_ids.LAST
LOOP
   UPDATE employees
      SET salary = salary * 1.1
    WHERE employee_id = employee_ids (indx);
END LOOP;

Then, with Oracle8i, we get rid of most of the context switches by moving to FORALL:

FORALL indx IN 
   employee_ids.FIRST .. employee_ids.LAST
  UPDATE employees
     SET salary = salary * 1.1
   WHERE employee_id = employee_ids (indx);

And that handles all of our needs-unless, once again, we need or would like to perform this same operation on different tables, based on location (or for any other kind of dynamic SQL situation). In this case, we can combine FORALL with EXECUTE IMMEDIATE, with these wonderful results:

CREATE OR REPLACE PROCEDURE upd_employees (
  loc_in IN VARCHAR2,
  employees_in IN employees_t )
IS
BEGIN
   FORALL indx in
      employees_in.first..
    employees_in.last
    EXECUTE IMMEDIATE
     'UPDATE '
        || loc_in
          || ' employees
        SET salary = salary*1.1'
          || ' WHERE employee_id = :the_id'
      USING employee_in (indx);
END;

Notice that in the USING clause, we must include both the name of the collection and the subscript for a single row using the same FORALL loop index variable.

Out-Binding

Let's again follow the progression from individual row updates to bulk bind relying on BULK COLLECT INTO to retrieve information, and finally the dynamic approach possible in Oracle 9i.

Oracle8 enhanced DML capabilities by providing support for the RETURNING clause. Shown in the following FOR loop, it allows us to obtain information (in this case, the updated salary) from the DML statement itself (thereby avoiding a separate and expensive query).

BEGIN
   FOR indx IN 
      employee_ids.FIRST .. employee_ids.LAST
   LOOP
      UPDATE    employees
            SET salary = salary * 1.1
          WHERE employee_id = employee_ids (indx)
      RETURNING salary
           INTO salaries (indx);
   END LOOP;
END;

Starting with Oracle8i, we can take advantage of FORALL to improve performance dramatically:

BEGIN
   FORALL indx IN employee_ids.FIRST .. employee_ids.LAST
      UPDATE    employees
            SET salary = salary * 1.1
          WHERE employee_id = employee_ids (indx)
      RETURNING salary
           BULK COLLECT INTO salaries;
END;

There's one seemingly odd aspect of this code you should remember: Inside the DML statement, any reference to the collection that drives the FORALL statement must be subscripted as in:

WHERE employee_id = employee_ids (indx)

In the RETURNING clause, however, you BULK COLLECT INTO the collection and not a single subscripted row of the collection.

That's all well and good, but what if (not to sound like a broken record) we want to execute this same update for any of the employee tables for different locations? Time to go to NDS and, with Oracle 9i only, also employ a RETURNING BULK COLLECT clause:

CREATE OR REPLACE PROCEDURE
   upd_employees (
      loc_in IN VARCHAR2,
    employees_in IN employees_t
)
IS
   my_salaries salaries_t;
BEGIN
   FORALL indx in
      employees_in.first..
    employees_in.last
    EXECUTE IMMEDIATE
     'UPDATE '
        || loc_in
          || ' employees
        SET salary = salary*1.1'
          || ' WHERE employee_id = :the_id
         RETURNING salary INTO :salaries'
      USING employee_in (indx)
    RETURNING BULK COLLECT INTO
       my_salaries;
END;

Handling and Reporting Exceptions

In Oracle8i, FORALL was wonderful for rapid processing of bulk DML statements. One problem with it, however, is that you lose some of the granularity in exception handling that you help with row-by-row processing. Suppose, for example, that we want to load a whole bunch of words into a vocabulary table. We can do it very efficiently as follows:

BEGIN
   FORALL indx IN 
      words.FIRST .. words.LAST
    INSERT INTO vocabulary
                  (text)
        VALUES (words (indx));
END;

If, however, an error occurred in any single INSERT, the entire FORALL statement would fail. This is the sort of scenario that was easily handled with row-level processing, such as with a loop like this:

FOR indx IN words.FIRST .. words.LAST
LOOP
   BEGIN
      INSERT INTO t text)
           VALUES (words (indx));
   EXCEPTION
      WHEN OTHERS
      THEN
         error_codes (indx) := SQLERRM;
   END;
END LOOP;

With this kind of code, we'd insert all rows that didn't cause an error to occur. With Oracle 9i, you can now do this with both static and dynamic FORALL SQL statements, by taking advantage of the SAVE EXCEPTIONS clause.

FORALL indx IN
    words.first..words.last
  SAVE EXCEPTIONS 
  INSERT INTO vocabulary ( text ) 
     VALUES ( words(indx) );

Use of SAVE EXCEPTIONS allows the FORALL to continue through all the rows indicated by the collection; it "saves up" the exceptions as it encounters them. This saving step begs the obvious question: How can you, the developer, get information about the errors that were "saved"? By taking advantage of the new SQL%BULK_COLLECTIONS pseudo-collection, as demonstrated in the code shown in Example 6.

Example 6. Using the SQL%BULK_COLLECTIONS pseudo-collection.

DECLARE
  bulk_errors   EXCEPTION;
  PRAGMA EXCEPTION_INIT (bulk_errors,  -24381);
BEGIN
  FORALL indx IN words.FIRST .. words.LAST
    SAVE EXCEPTIONS
    INSERT INTO t (text)
        VALUES (words (indx));
EXCEPTION
  WHEN bulk_errors
  THEN
    FOR j IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
    LOOP
      log_error (
         SQL%BULK_EXCEPTIONS(indx).ERROR_INDEX,
         SQLERRM(-1 * SQL%BULK_EXCEPTIONS(indx).ERROR_CODE)
      );
    END LOOP;
END;

Each row of this pseudo-collection is a record consisting of two fields: ERROR_INDEX and ERROR_CODE. The former field shows which index in the original bulk-load collection causes the failure. ERROR_CODE is the error number encountered.

You must both use the SAVE EXCEPTIONS construct and handle the BULK_ERRORS exception to get the intended benefit (that is, that all non-erroring rows are inserted).

The March of Progress

The new Oracle 9i features covered in this article should provide a comfortable feeling about Oracle's commitment to the PL/SQL language. Native compilation offers a path to formidable and transparent improvements in our application performance (even, and perhaps especially, existing "legacy" code). The CASE statement and expression, as well as support for dynamic SQL in bulk binding, help round out the language semantics.

This article was originally published in the April 2002 issue of Oracle Professional. The material in Feuerstein's articles--and those he cowrote with Bryn Llewellyn--are based on Oracle Corporation white papers originally prepared by Llewellyn for Oracle OpenWorld 2001 in San Francisco and OracleWorld Copenhagen in June 2002, and Feuerstein book, Oracle PL/SQL Programming, 3rd Edition.

In the next article, we'll embark on an in-depth examination of table functions and cursor expressions.

Steven Feuerstein is considered one of the world's leading experts on the Oracle PL/SQL language, having written ten books on the subject. Steven is a Senior Technology Advisor with Quest Software and has been developing software since 1980.

Bryn Llewellyn is PL/SQL Product Manager, Database and Application Server Technologies Development Group, at Oracle Corporation Headquarters.


O'Reilly & Associates recently released (September 2002) Oracle PL/SQL Programming, 3rd Edition.

  • Sample Chapter 10, Dates and Timestamps, is available free online.

  • You can also look at the Table of Contents, the Index, and the Full Description of the book.

  • For more information, or to order the book, click here.


Return to the O'Reilly Network.


What new Oracle 9i features are you using? Are they helpful in your job? Tell us about your experiences.
You must be logged in to the O'Reilly Network to post a talkback.
Post Comment
Full Threads Oldest First

Showing messages 1 through 2 of 2.

  • Author's reply to "Problem with the examples?"
    2003-01-22 12:42:09  bllewell [Reply | View]

    We presented two examples on p.3...

    CREATE OR REPLACE FUNCTION
    grade_translator (grade_in IN VARCHAR2)
    RETURN VARCHAR2
    IS
    retval VARCHAR2(100);
    BEGIN
    CASE
    WHEN grade_in = 'A'
    THEN retval := 'Excellent';
    WHEN grade_in = 'B'
    THEN retval := 'Very Good';
    WHEN grade_in = 'C'
    THEN retval := 'Good';
    WHEN grade_in = 'D'
    THEN retval := 'Fair';
    WHEN grade_in = 'F'
    THEN retval := 'Poor';
    ELSE retval := 'No such grade';
    END CASE;
    RETURN retval;
    END;

    ...and...

    CREATE OR REPLACE FUNCTION
    grade_translator (grade_in IN VARCHAR2)
    RETURN VARCHAR2
    IS
    BEGIN
    RETURN
    CASE
    WHEN grade_in = 'A'
    THEN 'Excellent'
    WHEN grade_in = 'B'
    THEN 'Very Good'
    WHEN grade_in = 'C'
    THEN 'Good'
    WHEN grade_in = 'D'
    THEN 'Fair'
    WHEN grade_in = 'F'
    THEN 'Poor'
    ELSE 'No such grade'
    END;
    END;

    While they both do compile and run OK, chrisrimmer is right - they're not the best examples of good style.

    In the case that the WHEN at every leg is an equality test on the same expression, then you should pull it out to the top of the CASE as you show.

    So we should have used an example like this...

    FUNCTION
    grade_translator (grade_in IN VARCHAR2)
    RETURN VARCHAR2
    IS
    BEGIN
    RETURN
    CASE
    WHEN grade_in = 'A'
    THEN 'Excellent'
    WHEN grade_in = 'B'
    THEN 'Very Good'
    WHEN grade_in = 'C'
    THEN 'Good'
    WHEN grade_in IN ('D', 'E', 'F' )
    THEN 'Could do better'
    ELSE 'No such grade'
    END;
    END;

    Thanks! Bryn.
  • Problem with the examples?
    2003-01-09 05:43:50  chrisrimmer [Reply | View]

    Perhaps I have misunderstood, but the 2 examples of the grade_translator function on p3 of the article do not seem to have a selector. They appear to be examples of the "searched" case statement described lower down p3. I think they should read:

    .....
    CASE grade_in
    WHEN 'A'
    THEN retval := 'Excellent';
    WHEN 'B'
    THEN retval := 'Very Good';
    .....

    and

    .....
    CASE grade_in
    WHEN 'A' THEN 'Excellent';
    WHEN 'B' THEN 'Very Good';
    .....


Tagged Articles

Be the first to post this article to del.icio.us

Sponsored Resources

  • Inside Lightroom

Related to this Article

Understanding Oracle Clinical Understanding Oracle Clinical
by Joan M. Johnson
June 2009
$9.99 USD

New Features in Oracle 9i New Features in Oracle 9i
by Howard J. Rogers
June 2009
$5.95 USD

Advertisement
O'Reilly Media

©2009, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com