﻿<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Steven Feuerstein's Blog</title>
    <description> Life’s short and there’s more to it than coding. How can we change the way we write our programs so that we can spend less time in front of a screen and more time with family, friends and planet earth? Visit my blog for tasty, tight, tidbits of advice and code that you can put to immediate use in your world of programming.</description>
    <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/BlogId/13/Default.aspx</link>
    <language>en-US</language>
    <managingEditor>Steven Feuerstein</managingEditor>
    <webMaster>webmaster@toadworld.com</webMaster>
    <pubDate>Fri, 25 Jul 2008 07:44:21 GMT</pubDate>
    <lastBuildDate>Fri, 25 Jul 2008 07:44:21 GMT</lastBuildDate>
    <docs>http://backend.userland.com/rss</docs>
    <generator>Blog RSS Generator Version 3.2.0.15477</generator>
    <item>
      <title>Oracle Open World Presentations</title>
      <description>&lt;p&gt;Hey folks, &lt;br /&gt;
 &lt;br /&gt;
For anyone attending Oracle Open World this year, here is my schedule of presentations: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Session ID: S300184&lt;br /&gt;
Session Title: &lt;strong&gt;Weird PL/SQL&lt;br /&gt;
&lt;/strong&gt;Track: Oracle Develop: Database&lt;br /&gt;
Room: Golden Gate C3&lt;br /&gt;
Date: 2008-09-21&lt;br /&gt;
Start Time: 15:45&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
Session ID: S300183&lt;br /&gt;
Session Title: &lt;strong&gt;Break Your Addiction to SQL!&lt;br /&gt;
&lt;/strong&gt;Track: Oracle Develop: Database&lt;br /&gt;
Room: Salon 02&lt;br /&gt;
Date: 2008-09-22&lt;br /&gt;
Start Time: 13:00
&lt;p&gt; &lt;/p&gt;
Session ID: S300185&lt;br /&gt;
Session Title: &lt;strong&gt;Why You Should Care About Oracle 11g PL/SQL Now&lt;/strong&gt;&lt;br /&gt;
Track: Oracle Develop: Database&lt;br /&gt;
Room: Salon 02&lt;br /&gt;
Date: 2008-09-23&lt;br /&gt;
Start Time: 11:30&lt;/blockquote&gt;
&lt;p&gt; &lt;/p&gt;
Hope to see you there!
&lt;p&gt; &lt;/p&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/252/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/252/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=252</guid>
      <pubDate>Tue, 22 Jul 2008 15:41:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=252</trackback:ping>
    </item>
    <item>
      <title>Always Bulk Collect</title>
      <description>&lt;div&gt;You learn something new every day, right? Well, I certainly do (more or less). Even about PL/SQL, about which I am sure many people think I already know &lt;em&gt;everything&lt;/em&gt;. Far from it.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In fact, I learned just last week from the PL/SQL Product Manager, Bryn Llewellyn, that his recommendation regarding cursor FOR loops and bulk collect is different from mine – and for a very good reason.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Several years ago, he informed me (and provided a script to verify the statement: 10g_optimize_cfl.sql in the &lt;a href="http://www.toadworld.com/LinkClick.aspx?fileticket=WdgBQ7kABao%3d&amp;tabid=67"&gt;demo.zip&lt;/a&gt;) that in Oracle10g, the compiler now optimizes every cursor FOR loop to execute at a level of performance similar to BULK COLLECT. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;This is a wonderful and non-trivial optimization. Rather than have to go through a manual rewrite of all cursor FOR loops, you can leave them in place and reap the benefits of the BULK COLLECT (sort of).&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Well, that's not quite true – and it's even less true than I thought.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;First of all, if your cursor FOR loop contains any DML statements, you will still want to explicitly convert to BULK COLLECT. The reason is that while the optimizer will improve the performance of the query step, it will &lt;em&gt;not&lt;/em&gt; optimize the DML statements to execute like FORALLs (the bulk processing analogue for updates, inserts and deletes).&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;So if you have code that looks like (cfl_to_bulk_0.sql in &lt;em&gt;&lt;u&gt;&lt;a href="http://www.toadworld.comhttp://www.toadworld.com/LinkClick.aspx?fileticket=WdgBQ7kABao%3d&amp;tabid=67"&gt;demo.zip&lt;/a&gt;&lt;/u&gt;&lt;/em&gt;):&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;font face="courier new" size="2"&gt;PROCEDURE upd_for_dept (&lt;br /&gt;
   dept_in     IN   employees.department_id%TYPE&lt;br /&gt;
 , newsal_in   IN   employees.salary%TYPE&lt;br /&gt;
)&lt;br /&gt;
IS&lt;br /&gt;
   CURSOR emp_cur&lt;br /&gt;
   IS&lt;br /&gt;
      SELECT employee_id, salary, hire_date&lt;br /&gt;
        FROM employees&lt;br /&gt;
       WHERE department_id = dept_in;&lt;br /&gt;
BEGIN&lt;br /&gt;
   FOR rec IN emp_cur&lt;br /&gt;
   LOOP&lt;br /&gt;
      BEGIN&lt;br /&gt;
         INSERT INTO employee_history&lt;br /&gt;
                     (employee_id, salary, hire_date&lt;br /&gt;
                     )&lt;br /&gt;
              VALUES (rec.employee_id, rec.salary, rec.hire_date&lt;br /&gt;
                     );&lt;br /&gt;
 &lt;br /&gt;
         adjust_compensation (rec.employee_id, rec.salary);&lt;br /&gt;
 &lt;br /&gt;
         UPDATE employees&lt;br /&gt;
            SET salary = newsal_in&lt;br /&gt;
          WHERE employee_id = rec.employee_id;&lt;br /&gt;
      EXCEPTION&lt;br /&gt;
         WHEN OTHERS&lt;br /&gt;
         THEN&lt;br /&gt;
            log_error;&lt;br /&gt;
      END;&lt;br /&gt;
   END LOOP;&lt;br /&gt;
END upd_for_dept;&lt;br /&gt;
&lt;/font&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;You will need to change the cursor FOR loop to BULK COLLECT so that you can fill up collections with data from the query, which can then be used in the FORALL statement. You will, sadly (due to increased complexity and program length) end up with something like this (cfl_to_bulk_5.sql in &lt;em&gt;&lt;u&gt;&lt;a href="http://www.toadworld.comhttp://www.toadworld.com/LinkClick.aspx?fileticket=WdgBQ7kABao%3d&amp;tabid=67"&gt;demo.zip&lt;/a&gt;&lt;/u&gt;&lt;/em&gt;):&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;font face="courier new" size="2"&gt;PROCEDURE upd_for_dept (&lt;br /&gt;
   dept_in     IN   employees.department_id%TYPE&lt;br /&gt;
 , newsal_in   IN   employees.salary%TYPE&lt;br /&gt;
)&lt;br /&gt;
IS&lt;br /&gt;
   bulk_errors    EXCEPTION;&lt;br /&gt;
   PRAGMA EXCEPTION_INIT (bulk_errors, -24381);&lt;br /&gt;
 &lt;br /&gt;
   TYPE employee_tt IS TABLE OF employees.employee_id%TYPE&lt;br /&gt;
      INDEX BY BINARY_INTEGER;&lt;br /&gt;
 &lt;br /&gt;
   employee_ids   employee_tt;&lt;br /&gt;
 &lt;br /&gt;
   TYPE salary_tt IS TABLE OF employees.salary%TYPE&lt;br /&gt;
     INDEX BY BINARY_INTEGER;&lt;br /&gt;
 &lt;br /&gt;
   salaries       salary_tt;&lt;br /&gt;
 &lt;br /&gt;
   TYPE hire_date_tt IS TABLE OF employees.hire_date%TYPE&lt;br /&gt;
      INDEX BY BINARY_INTEGER;&lt;br /&gt;
 &lt;br /&gt;
   hire_dates     hire_date_tt;&lt;br /&gt;
 &lt;br /&gt;
   CURSOR employees_cur&lt;br /&gt;
   IS&lt;br /&gt;
      SELECT     employee_id, salary, hire_date&lt;br /&gt;
            FROM employees&lt;br /&gt;
           WHERE department_id = dept_in&lt;br /&gt;
      FOR UPDATE;&lt;br /&gt;
 &lt;br /&gt;
   PROCEDURE fetch_data_quickly (&lt;br /&gt;
      limit_in           IN       PLS_INTEGER&lt;br /&gt;
    , employee_ids_out   OUT      employee_tt&lt;br /&gt;
    , salaries_out       OUT      salary_tt&lt;br /&gt;
    , hire_dates_out     OUT      hire_date_tt&lt;br /&gt;
   )&lt;br /&gt;
   IS&lt;br /&gt;
   BEGIN&lt;br /&gt;
      FETCH employees_cur&lt;br /&gt;
      BULK COLLECT INTO employee_ids_out, salaries_out, hire_dates_out LIMIT limit_in;&lt;br /&gt;
   END fetch_data_quickly;&lt;br /&gt;
 &lt;br /&gt;
   PROCEDURE adj_comp_for_arrays (&lt;br /&gt;
      employee_ids_io   IN OUT   employee_tt&lt;br /&gt;
    , salaries_io       IN OUT   salary_tt&lt;br /&gt;
   )&lt;br /&gt;
   IS&lt;br /&gt;
   BEGIN&lt;br /&gt;
      FOR indx IN 1 .. employee_ids_io.COUNT&lt;br /&gt;
      LOOP&lt;br /&gt;
         adjust_compensation (employee_ids_io (indx), salaries_io (indx));&lt;br /&gt;
      END LOOP;&lt;br /&gt;
   END adj_comp_for_arrays;&lt;br /&gt;
 &lt;br /&gt;
   PROCEDURE insert_history&lt;br /&gt;
   IS&lt;br /&gt;
   BEGIN&lt;br /&gt;
      FORALL indx IN employee_ids.FIRST .. employee_ids.LAST SAVE EXCEPTIONS&lt;br /&gt;
         INSERT INTO employee_history&lt;br /&gt;
                     (employee_id, salary, hire_date&lt;br /&gt;
                     )&lt;br /&gt;
              VALUES (employee_ids (indx), salaries (indx), hire_dates (indx)&lt;br /&gt;
                     );&lt;br /&gt;
   EXCEPTION&lt;br /&gt;
      WHEN bulk_errors&lt;br /&gt;
      THEN&lt;br /&gt;
         FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT&lt;br /&gt;
         LOOP&lt;br /&gt;
            -- Log the error&lt;br /&gt;
            log_error&lt;br /&gt;
                     (   'Unable to insert history row for employee '&lt;br /&gt;
                      || employee_ids&lt;br /&gt;
                                     (SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX)&lt;br /&gt;
                    , SQL%BULK_EXCEPTIONS (indx).ERROR_CODE&lt;br /&gt;
                     );&lt;br /&gt;
            /*&lt;br /&gt;
            Communicate this failure to the update phase:&lt;br /&gt;
            Delete this row so that the update will not take place.&lt;br /&gt;
            */&lt;br /&gt;
            employee_ids.DELETE (SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX);&lt;br /&gt;
         END LOOP;&lt;br /&gt;
   END insert_history;&lt;br /&gt;
 &lt;br /&gt;
   PROCEDURE update_employee&lt;br /&gt;
   IS&lt;br /&gt;
   BEGIN&lt;br /&gt;
      /*&lt;br /&gt;
        Use Oracle10g INDICES OF to avoid errors &lt;br /&gt;
        from a sparsely-populated employee_ids collection.&lt;br /&gt;
      */&lt;br /&gt;
      FORALL indx IN INDICES OF employee_ids SAVE EXCEPTIONS&lt;br /&gt;
         UPDATE employees&lt;br /&gt;
            SET salary = newsal_in&lt;br /&gt;
              , hire_date = hire_dates (indx)&lt;br /&gt;
          WHERE employee_id = employee_ids (indx);&lt;br /&gt;
   EXCEPTION&lt;br /&gt;
      WHEN bulk_errors&lt;br /&gt;
      THEN&lt;br /&gt;
         FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT&lt;br /&gt;
         LOOP&lt;br /&gt;
            log_error&lt;br /&gt;
                     (   'Unable to update salary for employee '&lt;br /&gt;
                      || employee_ids&lt;br /&gt;
                                     (SQL%BULK_EXCEPTIONS (indx).ERROR_INDEX)&lt;br /&gt;
                    , SQL%BULK_EXCEPTIONS (indx).ERROR_CODE&lt;br /&gt;
                     );&lt;br /&gt;
         END LOOP;&lt;br /&gt;
   END update_employee;&lt;br /&gt;
BEGIN&lt;br /&gt;
   OPEN employees_cur;&lt;br /&gt;
 &lt;br /&gt;
   LOOP&lt;br /&gt;
      fetch_data_quickly (100, employee_ids, salaries, hire_dates);&lt;br /&gt;
      EXIT WHEN employee_ids.COUNT = 0;&lt;br /&gt;
      insert_history;&lt;br /&gt;
      adj_comp_for_arrays (employee_ids, salaries);&lt;br /&gt;
      update_employee;&lt;br /&gt;
   END LOOP;&lt;br /&gt;
END upd_for_dept;&lt;br /&gt;
&lt;/font&gt;&lt;/font&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;Sorry, I know that I should probably explain this code but to be brutally honest my dad had heart bypass surgery and I am currently at the hospital and so you get the abbreviated version ....&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Anyway, that is one scenario where you definitely need to convert from the cursor FOR loop. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;But Bryn, to my surprise, recommends that you &lt;em&gt;always&lt;/em&gt; convert explicitly to BULK COLLECT. Why is that? For performance reasons. Apparently, the cursor FOR loop optimization makes the code run faster, but not as quickly as BULK COLLECT.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In my tests (which you can reproduce in cfl_vs_bulkcollect.sql), I found that BULK COLLECT ran 33% faster than the cursor FOR loop:&lt;/div&gt;
&lt;font size="2"&gt;
&lt;ul&gt;
    &lt;li&gt;Cursor For Loop Elapsed: 8.12 seconds. &lt;/li&gt;
    &lt;li&gt;Bulk Collect Elapsed: 5.46 seconds.  &lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;So if you really want the best performance, take the time and make the effort to switch over to BULK COLLECT.&lt;br /&gt;
 &lt;/div&gt;
&lt;/font&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/239/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/239/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=239</guid>
      <pubDate>Mon, 23 Jun 2008 14:34:00 GMT</pubDate>
      <slash:comments>4</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=239</trackback:ping>
    </item>
    <item>
      <title>How to Run an OS Command from PL/SQL</title>
      <description>&lt;p&gt;Oracle doesn't make it terribly easy to run operating system commands from within a PL/SQL block. I suppose that's understandable, given that PL/SQL is an embedded database-oriented language. Still, developers do ask me on a regular basis about how they can do this.&lt;/p&gt;
&lt;p&gt;As I understand it, there are basically three ways to do this:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Invoke a Java method from within a PL/SQL wrapper&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Call a C program as an external procedure from within PL/SQL.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Use the new DBMS_SCHEDULER package.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I will soon publish a DBMS_SCHEDULER solution (written by Bryn Llewellyn, PL/SQL Product Manager) on my OTN Best Practices column. In the meantime, you will find below a quick review of the steps needed to do this in Java and C.&lt;/p&gt;
&lt;p&gt;&lt;font size="3"&gt;&lt;strong&gt;Executing Host Command with Java&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;With the Java approach, you will take these steps:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Find the Java class that implements host command execution.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Build a class that invokes that host command method. Let's call it HostCommand.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Build a PL/SQL program that calls a method in HostCommand to run your command.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Acquire the privileges needed to execute host commands via Java in the database.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's go through each of these steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Find the Java class that implements host command execution.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Objects of the java.lang.Runtime class include an exec method that will execute a host command.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Build a class that invokes that host command method.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's call it HostCommand.&lt;/p&gt;
&lt;p&gt;Here's code to create a new Java class in the database to invoke this command for the Windows XP operating system:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED "UTLcmd"&lt;br /&gt;AS import java.lang.Runtime;&lt;br /&gt;public class execHostCommand&lt;br /&gt;{ &lt;br /&gt;  public static void execute (String command) &lt;br /&gt;    throws java.io.IOException&lt;br /&gt;  {&lt;br /&gt;   String osName = System.getProperty("os.name");&lt;br /&gt;   if(osName.equals("Windows XP"))&lt;br /&gt;       command = "cmd /c " + command;&lt;br /&gt;   Runtime rt = java.lang.Runtime.getRuntime();&lt;br /&gt;   rt.exec(command);&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;/&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can easily modify the execute method to support other operating systems based on the value of osName.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Build a PL/SQL program that calls a method in HostCommand to run your command.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;CREATE OR REPLACE PACKAGE host_command IS&lt;br /&gt;  PROCEDURE execute (cmd IN VARCHAR2) AS LANGUAGE JAVA NAME&lt;br /&gt;           'execHostCommand.execute(java.lang.String)';&lt;br /&gt;END;&lt;br /&gt;/&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notice that I "map" the VARCHAR2 datatype to the java.lang.String class in my call to the new Java method.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Acquire the privileges needed to execute host commands via Java in the database.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You need special privileges to execute host commands from within the database through Java. Otherwise when you try to execute your command you will see an error like this (the error message will vary depending on what you are trying to do):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img width="449" height="178" src="http://www.toadworld.com/Portals/0/blogimages/sf_blog060408.gif" alt="" /&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One way to obtain these privileges is to have the JAVASYSPRIV role granted to your schema. This role contains all the privileges you need (and more).&lt;/p&gt;
&lt;p&gt;For a more nuanced approach to granting the required privileges, you can also use the Java security API available in the Oracle database. For example, if I want to delete a file using a host command (perhaps it is not accessible through UTL_FILE), I will need to grant the following privileges to the schema in which the command is executed, such as HR:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;BEGIN&lt;br /&gt;   DBMS_JAVA.grant_permission ('HR'&lt;br /&gt;                             , 'SYS:java.io.FilePermission'&lt;br /&gt;                             , '&lt;&lt;ALL FILES&gt;&gt;'&lt;br /&gt;                             , 'execute'&lt;br /&gt;                              );&lt;br /&gt;   DBMS_JAVA.grant_permission ('HR'&lt;br /&gt;                             , 'SYS:java.lang.RuntimePermission'&lt;br /&gt;                             , 'writeFileDescriptor'&lt;br /&gt;                             , ''&lt;br /&gt;                              );&lt;br /&gt;   DBMS_JAVA.grant_permission ('HR'&lt;br /&gt;                             , 'SYS:java.lang.RuntimePermission'&lt;br /&gt;                             , 'readFileDescriptor'&lt;br /&gt;                             , ''&lt;br /&gt;                              );&lt;br /&gt;END;&lt;br /&gt;/&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;font size="3"&gt;&lt;strong&gt;Executing Host Command with C&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;To use C, you must define an external procedure and then invoke it within your PL/SQL block. It is not possible within this article to cover completely all the steps and issues involved in setting up such an external procedure. I will, instead, cover the highlights. For the full details, read Chapter 27 of Oracle PL/SQL Programming, 4th edition, in which my co-author Bill Pribyl thoroughly explains external procedures.&lt;/p&gt;
&lt;p&gt;As with Java, you will need help from your database administrator to supply the privileges needed to execute your host command in C. &lt;/p&gt;
&lt;p&gt;Here are the steps to follow with C:&lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Find (or build) the C program that implements host command execution.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Save the C source to a file and generate a shared library for it.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Save the library file where Oracle can find it.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Define a library inside Oracle that is associated with the shared library on disk.&lt;br /&gt;
     &lt;/li&gt;
    &lt;li&gt;Create a PL/SQL wrapper for the C function.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's go through each of these steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Find (or build) the C program that implements host command execution.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The C system function executes an operating system command. So I build a simple C function, extprocsh(), that accepts a string and passes it to the system function for execution:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;int extprocsh(char *cmd)&lt;br /&gt;{&lt;br /&gt;   return system(cmd);&lt;br /&gt;}&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;The function returns the result code as provided by system, a function normally found in the C runtime library (libc) on Unix, or in msvcrt.dll on Microsoft platforms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Save the C source to a file and generate a shared library for it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I save the source code in a file named extprocsh.c. I then use the GNU C compiler to generate a shared library. On a 64-bit Solaris machine running GCC 3.4.2 and Oracle Database 10g Release 2, the following compiler command worked to create a shared library:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;gcc -m64 extprocsh.c -fPIC -G -o extprocsh.so&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Similarly, on Microsoft Windows XP Pro running GCC 3.2.3 from Minimal GNU for Windows (MinGW), also with Oracle Database 10g Release 2, this works:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;c:\MinGW\bin\gcc extprocsh.c -shared -o extprocsh.dll&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;3. Save the library file where Oracle can find it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These commands generate a shared library file, extprocsh.so or extprocsh.dll. Now I need to put the library file somewhere that Oracle can find it. The default locations for Windows and Unix respectively are:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;$ORACLE_HOME/bin&lt;br /&gt;$ORACLE_HOME/lib&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you want to use a non-default location, you will need to edit the listener configuration file and supply path value(s) for the ENVS="EXTPROC_DLLS...".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Define a library inside Oracle that is associated with the shared library on disk.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After copying the file and/or making adjustments to the listener, you will then define a "library" inside Oracle to point to the DLL. For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;CREATE OR REPLACE LIBRARY extprocshell_lib &lt;br /&gt;   AS '/u01/app/oracle/local/lib/extprocsh.so';   -- Unix&lt;br /&gt;     &lt;br /&gt;CREATE OR REPLACE LIBRARY extprocshell_lib&lt;br /&gt;   AS 'c:\oracle\local\lib\extprocsh.dll';      -- Microsoft&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; performing this step requires Oracle's CREATE LIBRARY privilege.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Create a PL/SQL wrapper for the C function.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now I can create a PL/SQL call specification which uses the newly created library:&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;CREATE OR REPLACE FUNCTION exec_host_command (cmd IN VARCHAR2)&lt;br /&gt;   RETURN PLS_INTEGER&lt;br /&gt;AS&lt;br /&gt;   LANGUAGE C&lt;br /&gt;   LIBRARY extprocshell_lib&lt;br /&gt;   NAME "extprocsh"&lt;br /&gt;   PARAMETERS (cmd STRING, RETURN INT);&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, assuming that your DBA has set up the system environment to support external procedures, the exec_host_command function can now be called anywhere you can invoke a PL/SQL function.  &lt;/p&gt;
&lt;p&gt;Note that these operating system commands will execute with the same privileges as the Oracle Net listener that spawns the extproc process.&lt;/p&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/228/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/228/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=228</guid>
      <pubDate>Wed, 04 Jun 2008 14:11:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=228</trackback:ping>
    </item>
    <item>
      <title>Testing and refreshing data from production</title>
      <description>&lt;div&gt;As many of my readers likely know by now, I have been working for the past several years on the Quest Code Tester development effort.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Code Tester is the most powerful PL/SQL test automation tool available. You describe the expected behavior of your programs and Code Tester generates your test code, which can then be run from the UI or via a script. With Code Tester, you can build comprehensive regression tests and even implement the Test Driven Development methodology.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In the process of talking about Code Tester with many developers, I have come across a belief regarding code testing and the refreshing of data from production tables that I think actually reflects a misunderstanding about both how to use Code Tester specifically and, more generally, how to test our code.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Here's the way this belief was expressed by a customer recently:&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;"We have a development environment - let's call it TEST. Inside this environment is the supporting schema / repository for Code Tester, as well as the test definitions and generated test code."&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;"Once a week, we refresh the TEST environment from the production environment, PROD. PROD doesn't contain a Code Tester repository, so after I refresh, I lose my test repository and code."&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Clearly, this developer can do an export of the Code Tester schema and then import it after refresh, but he was concerned about having to add any overhead for the DBA to this refresh process.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Actually, I think the problem goes much deeper than that: if you refresh your test tables with production data on a regular basis, you will find it very difficult indeed to create stable regression tests that can be run against your code.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Why do I say that? Because when you refresh data from production, you change the "inputs" to your programs (contents of the tables) and therefore you will almost certainly have to change the expected results for your tests.&lt;/div&gt;
&lt;div&gt;In fact, I think that when it comes to &lt;em&gt;functional testing of your programs &lt;/em&gt;(does it meet user requirements?), you should &lt;em&gt;not&lt;/em&gt; be refreshing your test tables from production. To understand why I would say this, let's talk about....&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;How we make sure our programs work&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;There are as many kinds of tests as there are definitions of what it means for a program to "work." We need to make sure, for example, that our programs meet functional requirements (they are correct) and also that they run quickly enough to avoid user frustration. The programs need to scale up for many users and lots of data, etc.&lt;/div&gt;
&lt;div&gt; &lt;br /&gt;
&lt;a href="http://unittest.inside.quest.com/index.jspa"&gt;Quest Code Tester&lt;/a&gt; is designed specifically to help you implement tests on functional requirements; in essence, to verify that your program is correct.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;To do this, I will almost always want to compare the actual results of running my program with the expected results or control data. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;For example, if table XYZ contains a certain set of rows, then after running the program, table ABC should be changed in a specific way. Or the program is a function such that when I pass "ABC" for an IN argument, the function returns 100, and so on.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In general, there is no room for ambiguity here. Either the program works as expected or it does not. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Now, if I want to build regression tests and &lt;em&gt;automate&lt;/em&gt; the process of testing my program, I need to able to tell Code Tester that for a given set of inputs, I expect the associated outcomes. And – this is the key thing to realize – those inputs can't keep changing on me. Every time the inputs change, I would need to change the expected outcomes. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Constantly updating one's test code might make sense if you are perform manual tests from hand-coded scripts. But if you want to build comprehensive, serious regression tests, then you need a stable, consistent environment from which to run those tests.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Every time you refresh data from production, you change the values in your table and thus you cannot reliably execute your regression test.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;But don't we need real production data to really test?&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;Is this a big problem? Does this mean that we can't or shouldn't build static, repeatable regression tests for our code? After all (so the thinking goes), we need to test our code against production data to make sure that code handles "real world" situations.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Clearly, our code does need to work properly with production data.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;This does not mean, however, that you need to constantly change the data for your functional tests as production data changes. It &lt;em&gt;does&lt;/em&gt; mean that the data used in functional tests should &lt;em&gt;represent&lt;/em&gt; the variety of data found in production. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In other words, the data in your test tables must be sufficiently varied to allow you to exercise the program to verify all requirements. It doesn't really matter so much that the data is precisely &lt;em&gt;the same&lt;/em&gt; as that found in production.&lt;/div&gt;
&lt;div&gt;And, again, if you keep changing the test data, you must also change your test definition.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;OK, but don’t we need to test against production volume?&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;There's another problem with basing functionality testing around production data: the data volumes are generally too large, increasing the time it takes to complete the tests. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;You certainly do need to make sure your code executes with production volumes of data. But that sort of &lt;em&gt;stress testing&lt;/em&gt; should be done independently of your functionality testing. With functionality testing, you want your tests to run as quickly as possible, for these reasons:&lt;/div&gt;
&lt;ul type="disc"&gt;
    &lt;li&gt;You will have lots of (dozens, perhaps hundreds) of separate test cases to run; if each test takes five minutes due to data volume, the test cycle will take an enormous amount of time.&lt;br /&gt;
      &lt;/li&gt;
    &lt;li&gt;Ideally, you run your tests after each change you make to your program. That way, you can immediately determine if you have introduced any bugs. But if running those tests takes an hour, you will test less frequently and you will get less "return" on your investment of creating your tests. &lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;Conclusion: Segregate your functional test environment&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;The most important thing to verify about your program is that it is &lt;em&gt;correct&lt;/em&gt;: it meets user requirements. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;The best way to do this is to build a regression test that you can run after any change to the program, to ensure that it has no bugs. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;A regression test should not have to be changed as long as the program itself has not changed. It should work from a consistent set of "inputs" (values for IN arguments, contents of any tables queried by programs, etc.) that do &lt;em&gt;not&lt;/em&gt; change. [Of course, you may need to make some changes along the way as program requirements change, as you add more test cases, etc. That is different, however, from daily or weekly refreshes.]&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;In addition, you want regression tests to run as efficiently as possible. These tests focus on program functionality, not performance. So you want the &lt;em&gt;minimum &lt;/em&gt;volume of data in test tables that allow you to cover your requirements. Use different tests to verify adequate performance.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Consequently, when it comes to functionality testing, you should avoid refreshing your test tables from production. Instead, invest the time upfront to come up with setup scripts to populate tables with data that fully exercises your code. Include those setup scripts in your Code Tester test definitions and then you have an independent, consistent test environment.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt; &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/204/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/204/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=204</guid>
      <pubDate>Thu, 17 Apr 2008 16:21:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=204</trackback:ping>
    </item>
    <item>
      <title>Auto-formatting of templates for Toad</title>
      <description>&lt;div&gt;A few months ago, I posted on this blog an explanation of how to use Toad's Code Templates to standardize development and improve productivity. I included an XML document that contains over 20 templates that I thought you might find useful. I also asked my readers to produce XML transformations so that the XML document could be "output" in the format that Toad recognizes (and SQL Navigator as well).&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;I am happy to say that Sean Gilbert has done this for Toad 8 and is working on a transformation for Toad 9.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;For more information, check out &lt;a href="https://getonthetrail.blogsite.org/Blog/post/2008/04/Transform-Custom-TOAD-XML-Template-with-XSLT.aspx"&gt;https://getonthetrail.blogsite.org/Blog/post/2008/04/Transform-Custom-TOAD-XML-Template-with-XSLT.aspx&lt;/a&gt;. &lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Thanks, Sean!&lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/200/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/200/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=200</guid>
      <pubDate>Thu, 10 Apr 2008 16:14:00 GMT</pubDate>
      <slash:comments>2</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=200</trackback:ping>
    </item>
    <item>
      <title>Weird PL/SQL</title>
      <description>&lt;div&gt; &lt;br /&gt;
Collaborate08, annual conference of the International Oracle User Group, and several other national and international user groups, will be held in Denver this year, from April 13th to the 16&lt;sup&gt;th&lt;/sup&gt;. I am going to present three papers, including (for the first time) &lt;em&gt;Weird PL/SQL&lt;/em&gt;. I thought you might enjoy reading about some of the weirdnesses of PL/SQL in my ToadWorld blog, so here's an excerpt from the beginning of my whitepaper:&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;font size="3"&gt;&lt;font size="-0"&gt;Introduction&lt;/font&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;You probably think that PL/SQL is rather ordinary programming language. Well, it truly is a very powerful and straightforward language, but there are some features "less traveled" which can seem nothing less than &lt;em&gt;weird&lt;/em&gt;. This presentation explores some of  stranger nooks and crannies of the PL/SQL language, perhaps in the process making them a little bit less weird to the everyday programmer.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;All the code I reference in this paper is available at my PL/SQL Obsession site: www.ToadWorld.com/SF. Just click on the "Trainings" link and then click on the "demo.zip" link.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Disclosure: I will poke fun at Oracle, the PL/SQL language, and implicitly the folks who are responsible for building and enhancing PL/SQL. Please know that it is all done in a spirit of deep appreciation for what PL/SQL (and its developers) has done for me and so many others around the world. But, hey, there's always room for improvement and you've got to keep a sense of humor about all this stuff!&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;font size="3"&gt;Error Codes: Negative or Positive? Make up your mind, Oracle!&lt;/font&gt;&lt;br /&gt;
  &lt;br /&gt;
&lt;/strong&gt;I really would have thought that Oracle would have sorted this out by now. Here's the issue:&lt;/p&gt;
&lt;div&gt;When you see this string:&lt;/div&gt;
&lt;pre&gt;ORA-01855&lt;/pre&gt;
&lt;div&gt;do you interpret the "-" character as a &lt;em&gt;hyphen&lt;/em&gt; or a &lt;em&gt;negative sign&lt;/em&gt;? &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;I always considered it to be a negative sign, and that just about every single error code in the world of Oracle is negative. The only exceptions are 1 (user-defined exception) and 100 ("No data found" – which is another little weirdness in PL/SQL. The "No data found" exception has &lt;em&gt;two &lt;/em&gt;error codes: 100 and -1403).&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Yet sometimes Oracle treats the error codes as positive numbers, most notably when using the SAVE EXCEPTIONS clause of FORALL. When you include SAVE EXCEPTIONS, Oracle "saves up" any exceptions it encounters as it executes all the DML statements specified by the bind array. Then if at least one error occurred, it raises the ORA-24381 exception (Argh! Sometimes I worry about all the little bits of information that are stuck in my head!). It also populates the SQL%BULK_EXCEPTIONS pseudo-collection with all the exceptions that were raised. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Here's an example of a program that uses this feature:&lt;/div&gt;
&lt;pre&gt;/* bulkexc.sql */&lt;br /&gt;DECLARE&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;bulk_errors&lt;span&gt;          &lt;/span&gt;EXCEPTION;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;PRAGMA EXCEPTION_INIT (&lt;span&gt;bulk_errors,&lt;/span&gt; -&lt;span&gt;24381);&lt;br /&gt;&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;TYPE namelist_t IS TABLE OF VARCHAR2 (&lt;span&gt;1000);&lt;/span&gt; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;enames_with_errors&lt;span&gt;   &lt;/span&gt;namelist_t&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;:= namelist_t (&lt;span&gt;'ABC'&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                   &lt;/span&gt;, 'DEF'&lt;br /&gt;&lt;span&gt;                   &lt;/span&gt;, NULL&lt;br /&gt;&lt;span&gt;                   &lt;/span&gt;, 'LITTLE'&lt;br /&gt;&lt;span&gt;                   &lt;/span&gt;, RPAD (&lt;span&gt;'BIGBIGGERBIGGEST',&lt;/span&gt; 250, 'ABC')&lt;br /&gt;&lt;span&gt;                   &lt;/span&gt;, 'SMITHIE'&lt;br /&gt;&lt;span&gt;                    &lt;/span&gt;);&lt;br /&gt;BEGIN&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;FORALL indx IN enames_with_errors.FIRST .. enames_with_errors.LAST SAVE EXCEPTIONS&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;UPDATE employees&lt;br /&gt;&lt;span&gt;         &lt;/span&gt;SET last_name = enames_with_errors (&lt;span&gt;indx);&lt;br /&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;ROLLBACK;&lt;br /&gt;EXCEPTION&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;WHEN bulk_errors&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;THEN&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;FOR indx IN 1 .. SQL%BULK_EXCEPTIONS.COUNT&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;         &lt;/span&gt;DBMS_OUTPUT.put_line (&lt;span&gt;   &lt;/span&gt;'Error '&lt;br /&gt;&lt;span&gt;                               || &lt;/span&gt;indx&lt;br /&gt;&lt;span&gt;                               || &lt;/span&gt;' occurred on index '&lt;br /&gt;&lt;span&gt;                               || &lt;/span&gt;SQL%BULK_EXCEPTIONS (&lt;span&gt;indx).ERROR_INDEX&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                               || &lt;/span&gt;' with error code '&lt;br /&gt;&lt;span&gt;                               || &lt;/span&gt;SQL%BULK_EXCEPTIONS (&lt;span&gt;indx).ERROR_CODE&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                              &lt;/span&gt;);&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;END LOOP; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;      &lt;/span&gt;ROLLBACK;&lt;br /&gt;END;&lt;br /&gt;/&lt;/pre&gt;
&lt;div&gt;And this is what I see when I run the program:&lt;/div&gt;
&lt;pre&gt;Error 1 occurred on index 3 with error code 1407&lt;br /&gt;Error 2 occurred on index 5 with error code 12899&lt;/pre&gt;
&lt;div&gt;Hmmm. Oracle returns the error code as a positive number! So if I want to use SQLERRM to look up the error message for that code, I have to multiply it by -1:&lt;/div&gt;
&lt;pre&gt;FOR indx IN 1 .. SQL%BULK_EXCEPTIONS&lt;span&gt;.COUNT&lt;br /&gt;&lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;DBMS_OUTPUT.put_line&lt;br /&gt;&lt;span&gt;                      &lt;/span&gt;(&lt;span&gt;   &lt;/span&gt;'Error '&lt;br /&gt;&lt;span&gt;                       || &lt;/span&gt;indx&lt;br /&gt;&lt;span&gt;                       || &lt;/span&gt;' occurred on index '&lt;br /&gt;&lt;span&gt;                       || &lt;/span&gt;SQL%BULK_EXCEPTIONS (indx&lt;span&gt;).ERROR_INDEX&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                       || &lt;/span&gt;' with error '&lt;br /&gt;&lt;span&gt;                       || &lt;/span&gt;SQLERRM&lt;br /&gt;&lt;span&gt;                                &lt;/span&gt;( -1&lt;br /&gt;&lt;span&gt;                                 * &lt;/span&gt;SQL%BULK_EXCEPTIONS (indx&lt;span&gt;).ERROR_CODE&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                                &lt;/span&gt;)&lt;br /&gt;&lt;span&gt;                      &lt;/span&gt;);&lt;br /&gt;END LOOP;&lt;/pre&gt;
&lt;div&gt;And then I see this output:&lt;/div&gt;
&lt;pre&gt;Error 1 occurred on index 3 with error ORA-01407: cannot update () to NULL&lt;br /&gt;Error 2 occurred on index 5 with error ORA-12899: value too large for column &lt;/pre&gt;
&lt;p&gt;It would be nice if Larry would decide once and for all if error codes are positive or negative, and then &lt;em&gt;lay down the law.&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;font size="3"&gt;SQLERRM and DBMS_OUTPUT.PUT_LINE&lt;/font&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;Speaking of SQLERRM....let's talk about everybody's favorite program: DBMS_OUTPUT.PUT_LINE. For many, many years – all the way up to the release of Oracle Database 10g Release 2, if you tried to display a string of more than 255 characters with DBMS_OUTPUT.PUT_LINE, that built-in would raise an exception. This caused no end of teeth-gnashing and anguished moans from developers over the year. Fortunately, that restriction was lifted in Oracle Database 10g Release 2 – you can now display up to 32K characters. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;But there is still a problem with SQLERRM. We all know about SQLERRM. You call it to return the error message for the current error (obtained by calling SQLCODE). Did you know, however, that Oracle recommends that you &lt;em&gt;not use this function&lt;/em&gt;,. and instead call DBMS_UTILITY.FORMAT_ERROR_STACK? &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Why would that be? The problem is that SQLERRM may truncate your error message. In earlier versions of Oracle, truncated occurred at 255 characters. Now, it is 512. DBMS_UTILITY.FORMAT_ERROR_STACK, on the other hand, returns strings of up to 2000 bytes. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;I can just picture some developer years and years ago, confronted with a nasty problem:&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;He has found that SQLERRM can return error messages that get quite long, depending on application-specific information, like the names of identifiers. And when you try to display that string with DBMS_OUTPUT.PUT_LINE, an exception is raised.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;What's a developer to do?&lt;/div&gt;
&lt;blockquote style="margin-right: 0px;" dir="ltr"&gt;
&lt;div&gt;&lt;span&gt;1.&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;Fix DBMS_OUTPUT.PUT_LINE so it displays longer strings.&lt;/div&gt;
&lt;blockquote style="margin-right: 0px;" dir="ltr"&gt;
&lt;div&gt;or&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;span&gt;2.&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;Truncate your error message.&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;Well, obviously, the solution is to truncate the error message!&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Isn't that weird?&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;I am &lt;em&gt;so &lt;/em&gt;glad the PL/SQL team finally got around to fixing DBMS_OUTPUT.PUT_LINE! And according to the PL/SQL development manager at the time, all they had to was change one number!&lt;br /&gt;
 &lt;br /&gt;
So...there's a little bit of weirdness in PL/SQL...more to come at Collaborate08 and this blog.&lt;/div&gt;
&lt;div&gt;  &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/187/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/187/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=187</guid>
      <pubDate>Mon, 17 Mar 2008 19:57:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=187</trackback:ping>
    </item>
    <item>
      <title>Just how popular is PL/SQL?</title>
      <description>&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="4"&gt;Just how popular is PL/SQL?&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;And how many PL/SQL developers are there "out there"?&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;These are surprisingly hard questions to answer. Officially and even unofficially, Oracle Corporation's point people on PL/SQL do not have any idea (or refuse to say) how many PL/SQL developers there are. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;I take the approach of doing some rough extrapolations from numbers of books I and other PL/SQL developers have sold, and I conclude that there are perhaps a couple million PL/SQL developers, all told.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;That is, however, very unscientific.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;There are some websites, however, that offer an analysis of relative popularity of languages, usually based on data they have retrieved from various search sites.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="2"&gt;&lt;font size="3"&gt;TIOBE&lt;/font&gt;&lt;br /&gt;
 &lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;TIOBE compiles a list of relative popularity of programming languages at &lt;a href="http://www.tiobe.com/index.htm?tiobe_index"&gt;http://www.tiobe.com/index.htm?tiobe_index&lt;/a&gt;. &lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Here is the list as of January 2008:&lt;/div&gt;
&lt;div&gt;&lt;img height="366" alt="" width="532" src="http://www.toadworld.com/Portals/0/blogimages/SF-Blog0208-1.gif" /&gt;&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt; &lt;br /&gt;
Ok, fine, so PL/SQL is not the most popular language. We all know that. What I was very pleased to see is that PL/SQL had &lt;em&gt;increased &lt;/em&gt;in popularity over the last year. Based on my experience in India, I can see why; most of the increase probably came from there alone!&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Here is some information from the TIOBE website about how they calculate their ratings:&lt;/div&gt;
&lt;ul&gt;
    &lt;li&gt;The ratings are calculated by counting hits of the most popular search engines. The search query that is used is:   +"&lt;language&gt;&lt;/language&gt; programming"&lt;br /&gt;
      &lt;/li&gt;
    &lt;li&gt;The search query is executed for the regular Google, Google Blogs, MSN, Yahoo!, and YouTube web search for the last 12 months. The web site &lt;a target="_blank" href="http://www.alexa.com/"&gt;Alexa.com&lt;/a&gt; has been used to determine the most popular search engines.&lt;br /&gt;
      &lt;/li&gt;
    &lt;li&gt;The number of hits determine the ratings of a language. The counted hits are normalized for each search engine for the first 50 languages. In other words, the first 50 languages together have a score of 100%. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's define "hits50(SE)" as the sum of the number of hits for the first 50 languages for search engine SE and "hits(PL,SE)" as the number of hits for programming language PL for search engine SE, then the formal definition of the ratings becomes&lt;/p&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;pre&gt;((hits(PL,SE1)/hits50(SE1) + ... + hits(PL,SEn)/hits50(SEn))/n&lt;br /&gt; where n is the number of search engines used.&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;LangPop&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;The nice thing about the TIOBE site is that they rank PL/SQL explicitly. All the other language popularity pages I could find simply list "SQL," which I took to mean "SQL and all procedural language extensions to SQL."&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;The &lt;a href="http://www.langpop.com/"&gt;http://www.langpop.com/&lt;/a&gt; site shows the following graph:&lt;/div&gt;
&lt;div&gt;&lt;img alt="" src="http://www.toadworld.com/Portals/0/blogimages/SF-Blog0208-2.gif" /&gt;&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;O'Reilly Media Popularity by Book Sales&lt;br /&gt;
 &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;&lt;a href="http://www.oreilly.com/"&gt;O'Reilly Media&lt;/a&gt;, publisher of all my books on PL/SQL, has a research arm ("O'Reilly Radar") that projects programming language trends based on book sales. Again, there is just a single entry for SQL: &lt;br /&gt;
 &lt;br /&gt;
&lt;/div&gt;
&lt;div&gt;&lt;a href="http://radar.oreilly.com/archives/2005/04/book_sales_as_a.html"&gt;http://radar.oreilly.com/archives/2005/04/book_sales_as_a.html&lt;/a&gt;&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;&lt;img alt="" src="http://www.toadworld.com/Portals/0/blogimages/SF-Blog0208-3.gif" /&gt; &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/173/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/173/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=173</guid>
      <pubDate>Mon, 04 Feb 2008 18:56:00 GMT</pubDate>
      <slash:comments>1</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=173</trackback:ping>
    </item>
    <item>
      <title>Functions should return data only through the RETURN clause</title>
      <description>&lt;div&gt;You will find below an excerpt from my latest publication: the 2&lt;sup&gt;nd&lt;/sup&gt; edition of &lt;em&gt;&lt;u&gt;&lt;a href="http://www.oreilly.com/catalog/9780596514105/ "&gt;Oracle PL/SQL Best Practices&lt;/a&gt;&lt;/u&gt;&lt;/em&gt;.  This edition is a complete rewrite of the 1&lt;sup&gt;st&lt;/sup&gt; edition. I decided that since software is still, for the most part, written by humans, I would create a cast of characters who write the software I reference and critique in this book.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;They work for a company named My Flimsy Excuse, Inc (MFE) and their web site is &lt;u&gt;&lt;font color="#0000ff"&gt;&lt;a href="http://myflimsyexcuse.com"&gt;myflimsyexcuse.com&lt;/a&gt;&lt;/font&gt;&lt;/u&gt;. The mission statement of My Flimsy Excuse is simple and powerful:&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008080" size="2"&gt;Provide a wide array of excuses (flimsy and otherwise) to people in need of a way to explain away questionable behavior.&lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;The MFE founders love technology and don't want to have any flimsy excuses for failure, so they have chosen Oracle as their database technology. They have several different development teams working on various aspects of the web-deployed business plan. The team, which has graciously volunteered to share their experiences with my readers, is made up of the following individuals:&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt; &lt;/font&gt; &lt;/div&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Sunita&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;The team leader, a very smart woman with a fast, razor-sharp mind. She is always busy, always constructive, and somewhat intimidating. She was a programmer in years past, mostly trained in Fortran and then some C+. She isn't sure if that makes her a better manager or a bigger danger to the team, but she still likes to get her hands "dirty," now and then, writing code. &lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;font color="#008000"&gt;Delaware &lt;/font&gt;&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;A classic anarchistic, anti-standards, big ego kind of programmer. He has a hard time learning from anyone else, and writes code that is hard to understand....but he is very productive and very bright. If you need a job done overnight (literally), Delaware is the guy to do it. He claims to read up on all the latest features of PL/SQL, but he generally programs in a rut – relying on techniques learned years past in Oracle7. He keeps his thinning hair neatly trimmed, with just a hint of a comb-over, and favors three-piece suits from the Men's Wearhouse. &lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Lizbeth &lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;The anchor of the team. She used to write in Cobol and is continually shocked at the lack of strong process in the PL/SQL world. She can't understand why programmers today make fun of the Cobol programmers of the past. Didn't they write the software that made the first phase of the Information Revolution a broad success? Lizbeth is methodical and careful, but not, on the whole, the best problem solver—she too easily falls into the trap of seeing things only from her own perspective. &lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Jasper&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;The junior member of the team. He is new to PL/SQL and new to MFE. Jasper is eager to learn from anyone and everyone and has nice thick skin, but he is not very creative (not ready to take risks in his code). He always wears jeans, preferably of the distressed variety, complemented by polo shirts with the logos of animals on them. Lizbeth thinks of him as the son she never had, and Delaware treats him like the mascot of the team.&lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;And here is my recommended best practice and explanation for this first blog entry of the new year:&lt;/div&gt;
&lt;div&gt;&lt;hr /&gt;
&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt;&lt;font size="4"&gt;Functions should return data only through the RETURN clause.&lt;/font&gt;&lt;/font&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;em&gt;&lt;font size="3"&gt; &lt;/font&gt;&lt;/em&gt;&lt;/strong&gt; &lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="3"&gt;Problem: Jasper returns data in a very confusing manner.&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;strong&gt;&lt;font size="3"&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;/font&gt;&lt;/strong&gt;
&lt;div&gt;&lt;font size="2"&gt;Jasper needs a program to retrieve several pieces of information about an excuse: the title, the author and the word count. In just a few moments he builds the following function:&lt;/font&gt;&lt;/div&gt;
&lt;font size="2"&gt;
&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;
&lt;/font&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;pre&gt;FUNCTION excuse_title (&lt;br /&gt;   excuse_id_in IN mfe_excuse.isbn%TYPE&lt;br /&gt; , author_out OUT mfe_excuse.author%TYPE&lt;br /&gt; , word_count_out OUT mfe_excuse.word_count%TYPE)&lt;br /&gt;RETURN mfe_excuse.title%TYPE&lt;br /&gt;IS BEGIN &lt;br /&gt;   ... implementation unimportant! ... &lt;br /&gt;END excuse_title;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt; &lt;font size="2"&gt;And then he puts this function to use as follows:&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;pre&gt;PROCEDURE process_excuse (excuse_id_in IN mfe_excuse.isbn%TYPE)&lt;br /&gt;IS&lt;br /&gt; l_title mfe_excuse.title%TYPE;&lt;br /&gt; l_author mfe_excuse.author%TYPE;&lt;br /&gt; l_word_count mfe_excuse.word_count%TYPE;&lt;br /&gt;BEGIN&lt;br /&gt;   l_title := excuse_title (l_id, l_author, l_word_count);&lt;br /&gt;   ...&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;Jasper is proud that he took the time to put this retrieval logic into its own program. Now it can be used in many places in the application. He shows everyone excuse_title at the weekly code review session. Imagine his dismay when Delaware snorts:&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;div&gt;&lt;font color="#008080" size="2"&gt;Humph. Well, Jasper, it's absolutely peachy keen that you wrote a reusable function. But that is not a program I would ever want to use. The name says you are giving me a title, and you are, but then you are also passing back all that other stuff. It's self-contradictory &lt;em&gt;and&lt;/em&gt; I have to declare a bunch of individual variables to use the darned thing!&lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;Jasper pouts; he was so looking forward to a pat or two on the back. Sunita gives Delaware a dirty look and shakes her head. "Jasper," she says, drawing his attention away from Delaware's gloomy outlook, "hiding the lookup was an excellent move. You just need to take a step or two further, and make sure there are no mixed messages. Let's redesign excuse_title together."&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="3"&gt;Solution: Return multiple values through a single, composite structure or with a procedure.&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="3"&gt; &lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="4"&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;Here are the steps that Sunita takes to revamp the lookup function and make it more useful:&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;ul&gt;
    &lt;li&gt;&lt;font size="2"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;Make sure the name of the program reflects what it does:&lt;/font&gt;&lt;/em&gt;&lt;font color="#000080"&gt; In this case, the program doesn't return just the title; it returns several pieces of information about an excuse. Let's call it the "excuse_info" function.&lt;/font&gt;&lt;/font&gt; &lt;/li&gt;
    &lt;li&gt;&lt;font size="2"&gt;&lt;em&gt;&lt;font color="#ff0000"&gt;Pass everything in the RETURN clause:&lt;/font&gt;&lt;/em&gt;&lt;font color="#000080"&gt; Rather than returning one value through the RETURN clause and another through the parameter list, let's pass everything back in the RETURN clause, using a composite structure—in this case, a record.&lt;/font&gt;&lt;/font&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;font size="2"&gt;Here, then, is the new header of the program:&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;pre&gt;FUNCTION excuse_info (id_in IN mfe_excuse.id%TYPE)&lt;br /&gt;   RETURN mfe_excuse%ROWTYPE&lt;br /&gt;And here is the revised usage of this function:&lt;br /&gt;PROCEDURE process_excuse (excuse_id_in IN mfe_excuse.isbn%TYPE)&lt;br /&gt;IS&lt;br /&gt;   l_excuse mfe_excuse%ROWTYPE;&lt;br /&gt;BEGIN&lt;br /&gt;   l_excuse := excuse_info (l_id);&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;Now the code is leaner and cleaner. Everything that is returned by the function is deposited into a single record. And if you are concerned about returning all the data in a table's row, when you need only a small subset of its columns, you can always create your own user-defined record, as shown here:&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;pre&gt;PACKAGE excuse_pkg&lt;br /&gt;IS&lt;br /&gt;   TYPE key_info IS RECORD (&lt;br /&gt;      title mfe_excuse.title%TYPE&lt;br /&gt;    , author mfe_excuse.author%TYPE&lt;br /&gt;    , word_count mfe_excuse.word_count%TYPE&lt;br /&gt;   );&lt;br /&gt; &lt;br /&gt;   FUNCTION excuse_info (id_in IN mfe_excuse.id%TYPE)&lt;br /&gt;      RETURN key_info;&lt;br /&gt;END excuse_pkg;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;To sum up: the whole point of a function is to return a value (whether it’s a single, scalar value or a composite, such as a record or a collection). If you also return data back through the parameter list with OUT or IN OUT arguments, the purpose and usage of the function will be obscured. &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;If you need to return multiple pieces of information, take one of the following approaches: &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;&lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Return a record or collection of values &lt;/font&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Make sure to publish the structure of your record or collection (the TYPE statement) in a package specification so that developers can understand and use the function more easily. &lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Break up a single function into multiple functions, all returning scalar values&lt;/font&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;With this approach, you can call the functions from within SQL statements. &lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Change a function into a procedure&lt;/font&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;font color="#008000" size="2"&gt;Unless you need to call a function to return this information, just change it to a procedure returning multiple pieces of information through the OUT arguments in the parameter list&lt;/font&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;font size="2"&gt;If you follow these guidelines, your subprograms will be more likely to be used and reused, because they will be defined in ways that make them easy to understand and apply in your own code. &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt; &lt;/font&gt;&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;&lt;font size="2"&gt;Your function may also then be callable from within a SQL statement, which encourages even wider use of this program. Note, though, that there are restrictions on function calls from SQL. You may not call a function with an OUT argument from within SQL, You also may not call a function that returns a record (the datatypes of all parameters must be SQL-compatible). &lt;/font&gt;&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/166/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/166/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=166</guid>
      <pubDate>Mon, 07 Jan 2008 21:46:00 GMT</pubDate>
      <slash:comments>1</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=166</trackback:ping>
    </item>
    <item>
      <title>Single quotes and CLOBs in 10.2.0.3</title>
      <description>&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;I usually write about best practice principles and general issues.&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Today I thought I would share a bit of my pain with you on a very specific topic: single quotes and CLOBs, and specifically on Oracle 10.2.0.3.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;I figure that I lost an hour or two of my life to this one, and would like to pass on the warning to you all.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;In Quest Code Tester, we store in put and expected values that you provide in your test definition as CLOBs. We maintain "internal" and "external" values – the external value is a string that is ready to be concatenated directly into the generated test code.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Thus, if the input value is one single quote, we save it in the external_value column as&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;''''&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;That is, four single quotes – which evaluate to a single quote. Got it so far, right?&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;When we generate test code, those four single quotes need to be treated as NULL, so we wrote this function to tell us if a CLOB is &lt;em style="mso-bidi-font-style: normal"&gt;really&lt;/em&gt; to be considered as NULL:&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;img height="172" alt="" width="400" src="http://www.toadworld.com/Portals/0/blogimages/SingleQuotes01.png" /&gt;&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;  &lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;And it worked just fine in all sorts of situations, but then we got a support issue logged by a user. He was getting the wrong test results: Quest Code Tester was showing that he had provided a NULL value when he had instead entered the single quote character!&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;What the heck was going on?&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Well, it took quite a while to narrow things down. The error did not occur on Oracle9i, Oracle11g, on Oracle 10g 10.2.0.1. It only seems to happen on 10.2.0.3. &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;And after a while of testing and experimenting, I discovered that on this version of Oracle, you get some very strange behavior when performing a test like:&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; BACKGROUND: white; FONT-FAMILY: Tahoma; mso-highlight: white"&gt;value_in = ''''''&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;It's easy to see; you can try it yourself. Copy and paste the following block of code into Toad and run it:&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;o:p&gt;&lt;img height="350" alt="" width="400" src="http://www.toadworld.com/Portals/0/blogimages/SingleQuotes02.png" /&gt;&lt;/o:p&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;You should see this output:&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal" style="MARGIN: 0in 0in 0pt; mso-layout-grid-align: none"&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; BACKGROUND: white; FONT-FAMILY: Tahoma; mso-highlight: white"&gt;FOUR QUOTES&lt;br /&gt;
&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; BACKGROUND: white; FONT-FAMILY: Tahoma; mso-highlight: white"&gt;FOUR QUOTES&lt;/span&gt;&lt;/strong&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;And that is wrong, so wrong. It looks like it doesn't matter how many single quote characters you put inside two single quotes; that equality check will always return TRUE.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Ugh.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;So I built the following workaround:&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;img height="227" alt="" width="400" src="http://www.toadworld.com/Portals/0/blogimages/SingleQuotes03.png" /&gt;&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;And that seems to work reasonably well – though I would much rather NOT have to do this.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Now you know and hopefully you will never run into this issue – but if you do, you have been warned!&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/164/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/164/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=164</guid>
      <pubDate>Thu, 20 Dec 2007 00:41:00 GMT</pubDate>
      <slash:comments>3</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=164</trackback:ping>
    </item>
    <item>
      <title>Error Management functions and features for PL/SQL Developers</title>
      <description>&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Winter is descending on &lt;st1:city w:st="on"&gt;&lt;st1:place w:st="on"&gt;Chicago&lt;/st1:place&gt;&lt;/st1:city&gt;; we had our first snow (flurries, really) on Thanksgiving Day, and the sun is weak. For many, this is a depressing time – and I mean that the lack of that and cold actually does make people feel depressed. Perhaps that is why I found myself thinking negatively – that is, about exceptions in PL/SQL.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;So I thought I would share with you some of the features and functions available in PL/SQL, especially those introduced in recent versions of Oracle, which will help you trap and log error information.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;For each of the topics below, I describe them briefly and then point to files in my demo.zip archive that illustrate the technique, and also the hyperlink to Oracle documentation on the topic.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;You can download my demo.zip archive by clicking &lt;a href="http://www.toadworld.com/LinkClick.aspx?fileticket=WdgBQ7kABao=&amp;tabid=155"&gt;here&lt;/a&gt;.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Enjoy! And remember: never be sad about Oracle exceptions. Just make sure you have a powerful, general utility for raising, handling and logging those errors. If you don't already have such a thing, then please do download and try the freeware &lt;a href="http://www.toadworld.com/Education/StevenFeuersteinsPLSQLObsession/MyPetProjectsandContributions/QuestErrorManagerQEM/tabid/210/Default.aspx"&gt;Quest Error Manager&lt;/a&gt;. I wrote it myself – just for you!&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Save DML errors to a log table instead of raising an exception (Oracle10g)&lt;/strong&gt;&lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;As soon as the SQL engine raises an exception from processing a DML statement, your executable section terminates. You can, of course, trap the exception, log it, and then keep going, but once an exception is raised, performance degrades terribly.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/span&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Use the DBMS_ERRLOG package to define an error log table for your own table. Then when you write your DML statement, include the LOG ERRORS clause. With this approach, Oracle will save DML errors to a log table instead of raising an exception; your program will finish more quickly and then afterwards, you can query the information from the log table and either log the errors in your own system, try to recover or display error information.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;My demo.zip files of relevance&lt;br /&gt;
&lt;/strong&gt;&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;dbms_errlog*.*&lt;br /&gt;
&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;forall_with_dbms_errlog.sql&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Oracle documentation reference&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt; &lt;/strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;a target="_blank" href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_errlog.htm%23sthref2860"&gt;http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_errlog.htm#sthref2860&lt;/a&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Save exceptions till the end of your FORALL execution&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;FORALL is an absolutely, deliriously wonderful enhancement to DML execution introduced in Oracle8i. With FORALL, you can execute multiple DML statements in "bulk," which means they run in a fraction of the time it would take if they ran them on a row by row basis.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;
&lt;p class="MsoNormal"&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;As with LOG ERRORS above, you can tell FORALL to continue past any exceptions encountered as it runs each DML operation by adding the SAVE EXCEPTIONS clause.&lt;span style="mso-spacerun: yes"&gt;  &lt;/span&gt;Then if one or more exceptions were encountered, when FORALL is done, Oracle will raise the ORA-24381 error and also populates a "pseudo-collection" called SQL%BULK_EXCEPTIONS with the error code and the index in the collection that raised the error.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;My demo.zip files of relevance&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;cfl_to_bulk*.sql&lt;br /&gt;
&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;bulkexc.sql&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Oracle documentation reference&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt; &lt;/strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;a target="_blank" href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/tuning.htm%23sthref2201"&gt;http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14261/tuning.htm#sthref2201&lt;/a&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Find the line number on which the error was raised (Oracle10g)&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Call the DBMS_UTILITY.FORMAT_ERROR_BACKTRACE to retrieve a string that shows the stack of calls that trace back to the line number and program name from which the most recent error was raised.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;My demo.zip files of relevance&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;backtrace*.sql&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Oracle documentation reference&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;a target="_blank" href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_util.htm%23sthref9387"&gt;http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_util.htm#sthref9387&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Get the error message&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;br /&gt;
&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;What? You thought you should call the SQLERRM function? No way! Oracle recommends that you not use this function, because it might truncate your error message. Instead, call the DBMS_UTILITY.FORMAT_ERROR_STACK function and it will return the full error message (and sometimes even a stack!).&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;My demo.zip files of relevance&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt; &lt;br /&gt;
&lt;/strong&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;Sorry, none! It's such an easy, little thing to use...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma"&gt;&lt;strong&gt;Oracle documentation reference&lt;o:p&gt;&lt;/o:p&gt;&lt;/strong&gt;&lt;/span&gt; &lt;span style="FONT-SIZE: 9pt; FONT-FAMILY: Tahoma; mso-fareast-font-family: 'Times New Roman'; mso-ansi-language: EN-US; mso-fareast-language: EN-US; mso-bidi-language: AR-SA"&gt;&lt;a target="_blank" href="http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_util.htm%23sthref9392"&gt;http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_util.htm#sthref9392&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <link>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/159/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/QuestExpertsBlogs/tabid/67/EntryID/159/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=159</guid>
      <pubDate>Wed, 05 Dec 2007 03:41:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=159</trackback:ping>
    </item>
  </channel>
</rss>