﻿<?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/Blogs/tabid/67/BlogId/13/Default.aspx</link>
    <language>en-US</language>
    <managingEditor>Steven Feuerstein</managingEditor>
    <webMaster>webmaster@toadworld.com</webMaster>
    <pubDate>Fri, 10 Oct 2008 14:54:18 GMT</pubDate>
    <lastBuildDate>Fri, 10 Oct 2008 14:54:18 GMT</lastBuildDate>
    <docs>http://backend.userland.com/rss</docs>
    <generator>Blog RSS Generator Version 3.2.0.15477</generator>
    <item>
      <title>Doing SQL in PL/SQL: key resource from Bryn Llewellyn</title>
      <description>&lt;div&gt;One of the highlights of Oracle Open World 2008 for me was the presentation by Bryn Llewellyn (PL/SQL Product Manager) on “Doing SQL in PL/SQL.”&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Bryn surely has the most thorough and clear understanding of the PL/SQL language of anyone I have met (definitely including me).&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;His talk was detailed and precise (and maybe just a little bit overwhelming. He needed twice the time allotted) on this most important topic.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;The bad news about this talk is that most PL/SQL developers will never be able to hear Bryn present it.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;The good news is that everything he talks about (and more) may be found in a newly published whitepaper of the same name.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Simply visit the &lt;a href="http://www.oracle.com/technology/tech/pl_sql/index.html"&gt;Oracle Technology Network PL/SQL Homepage&lt;/a&gt; and click on the hyperlink for that paper. Notice also that Bryn has published an important whitepaper on SQL injection. &lt;br /&gt;
 &lt;br /&gt;
&lt;img height="402" alt="" width="700" src="http://www.toadworld.com/Portals/0/blogimages/Steven Feuerstein/SF-Blog100808-1.gif" /&gt;&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Your understanding of PL/SQL will increase dramatically, as will your ability to write high quality PL/SQL programs, by reading both of these papers.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;Thanks, Bryn!&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/286/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/286/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=286</guid>
      <pubDate>Wed, 08 Oct 2008 17:09:00 GMT</pubDate>
      <slash:comments>0</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=286</trackback:ping>
    </item>
    <item>
      <title>For loops or While loops to scan collections?</title>
      <description>&lt;div&gt;I have generally recommended in the past that whenever you are writing code to iterate through the elements of a collection, you should use a while loop, combined with the FIRST-NEXT or LAST-PRIOR collection methods.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;The key advantage of this approach is that the code will not raise a NO_DATA_FOUND exception if your collection is sparse (there is an index value between FIRST and LAST that is not defined). And if your collection is empty, the loop will not execute at all, whereas with a for loop, an empty collection could cause a VALUE_ERROR exception if you are not careful.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Here is an example of the kind of code that requires the use of the while loop:&lt;/div&gt;
&lt;blockquote dir="ltr" style="margin-right: 0px"&gt;
&lt;pre&gt;DECLARE&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;TYPEemployee_tt&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;IS&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;TABLEOFemployees%ROWTYPE&lt;br /&gt;&lt;span&gt;         &lt;/span&gt;INDEXBYpls_integer; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;employee_cache&lt;span&gt;   &lt;/span&gt;employee_tt;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;/* Fill the collection using the employee ID&lt;br /&gt;&lt;span&gt;      as the index value - most like these values are&lt;br /&gt;&lt;/span&gt;&lt;span&gt;      NOT sequentially defined; primary keys can&lt;br /&gt;&lt;/span&gt;&lt;span&gt;      certainly have "gaps". */&lt;br /&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;FORrecIN(SELECT&lt;span&gt;   *&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;FROM&lt;span&gt;   &lt;/span&gt;employees)&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;employee_cache(rec.employee_id):=rec;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;FORindxIN1..employee_cache.COUNT&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;DBMS_OUTPUT.put_line(employee_cache(indx).last_name);&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP;&lt;br /&gt;END;&lt;br /&gt;/&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;When I execute this block, I get the following error:&lt;br /&gt;
 &lt;br /&gt;
&lt;img height="173" alt="" width="448" src="http://www.toadworld.com/Portals/0/blogimages/Steven Feuerstein/SF-Blog092208-1.gif" /&gt;&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Instead, I should use a while loop:&lt;/div&gt;
&lt;blockquote dir="ltr" style="margin-right: 0px"&gt;
&lt;pre&gt;DECLARE&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;TYPEemployee_tt&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;IS&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;TABLEOFemployees%ROWTYPE&lt;br /&gt;&lt;span&gt;         &lt;/span&gt;INDEXBYpls_integer;&lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;employee_cache&lt;span&gt;   &lt;/span&gt;employee_tt;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;l_index&lt;span&gt;           &lt;/span&gt;pls_integer;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;/* Fill the collection using the employee ID&lt;br /&gt;&lt;span&gt;      as the index value - most like these values are&lt;br /&gt;&lt;/span&gt;&lt;span&gt;      NOT sequentially defined; primary keys can&lt;br /&gt;&lt;/span&gt;&lt;span&gt;      certainly have "gaps". */&lt;br /&gt;&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;FORrecIN(SELECT&lt;span&gt;   *&lt;br /&gt;&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;FROM&lt;span&gt;   &lt;/span&gt;employees)&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;employee_cache(rec.employee_id):=rec;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP;&lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;l_index:=employee_cache.FIRST;&lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;WHILE(l_indexISNOTNULL)&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;DBMS_OUTPUT.put_line(employee_cache(l_index).last_name);&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;l_index:=employee_cache.NEXT(l_index);&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP;&lt;br /&gt;END;&lt;br /&gt;/&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;and then I will not see any errors.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;So, no doubt about it, this is good advice – but should you &lt;em&gt;always&lt;/em&gt; use the while loop?&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;I suggest that in situations when you know, when you are absolutely sure, that your collection is densely-filled, you should use the FOR loop, for two reasons:&lt;/div&gt;
&lt;ul type="disc"&gt;
    &lt;li&gt;It is a simpler solution, requiring less code (reducing the chance of a bug creeping into your code and making it easier to maintain).&lt;/li&gt;
    &lt;li&gt;It is more self-documenting. By using the for loop construct, you are stating that an assumption of this code is that the collection is densely-filled.&lt;/li&gt;
    &lt;li&gt;It is more efficient than the while loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;So when you can be certain that your collection is densely-filled? When....&lt;/div&gt;
&lt;ul type="disc"&gt;
    &lt;li&gt;The collection is populated by a BULK COLLECT query. In this situation, the first index used is always 1, and the collection is filled sequentially (2, 3, 4...) from that index.&lt;/li&gt;
    &lt;li&gt;The collection is a nested table assigned its contents from a MULTISET operator (UNION, INTERSECT and EXCEPT). These set level operators always fill sequentially a nested table from index value 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;And what kind of difference in performance can you expect to see? Not a very big difference, but the FOR loop is &lt;em&gt;definitely &lt;/em&gt;faster. I put together the script below (relying on the sf_timer package, included in the &lt;a href="http://www.toadworld.com/Portals/0/stevenf/demo.zip"&gt;demo.zip&lt;/a&gt;.&lt;/div&gt;
&lt;blockquote dir="ltr" style="margin-right: 0px"&gt;
&lt;pre&gt;DECLARE&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;l_index&lt;span&gt;     &lt;/span&gt;pls_integer;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;l_source&lt;span&gt;   &lt;/span&gt;DBMS_SQL.varchar2a;&lt;br /&gt;BEGIN&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.start_timer; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;       &lt;/span&gt;SELECT&lt;span&gt;    &lt;/span&gt;text&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;BULKCOLLECTINTO&lt;span&gt;   &lt;/span&gt;l_source&lt;br /&gt;&lt;span&gt;         &lt;/span&gt;FROM&lt;span&gt;    &lt;/span&gt;all_source; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.show_elapsed_time(&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;'Retrieved ' || TO_CHAR(l_source.COUNT) || ' elements'&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;);&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;--&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.start_timer; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;FORindxIN1..l_source.COUNT&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;NULL;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.show_elapsed_time('FOR loop through collection');&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;--&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.start_timer;&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;l_index:=l_source.FIRST; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;WHILE(l_indexISNOTNULL)&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;LOOP&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;NULL;&lt;br /&gt;&lt;span&gt;      &lt;/span&gt;l_index:=l_source.NEXT(l_index);&lt;br /&gt;&lt;span&gt;   &lt;/span&gt;ENDLOOP; &lt;/pre&gt;
&lt;pre&gt;&lt;span&gt;   &lt;/span&gt;sf_timer.show_elapsed_time('Full collection scan with NEXT');&lt;br /&gt;END;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;I saw the following results:&lt;/div&gt;
&lt;blockquote dir="ltr" style="margin-right: 0px"&gt;
&lt;pre&gt;Retrieved 3079394 elements - Elapsed CPU : 11.14 seconds.&lt;br /&gt;FOR loop through collection - Elapsed CPU : .05 seconds.&lt;br /&gt;Full collection scan with NEXT - Elapsed CPU : .48 seconds. &lt;/pre&gt;
&lt;/blockquote&gt;
&lt;div&gt;In other words, the while loop is an &lt;em&gt;order of magnitude slower &lt;/em&gt; than the for loop. That sounds like a big deal, but it's only a difference of .4 seconds with a scan of over 3,000,000 elements. With smaller collections, you'll probably never notice the difference.&lt;br /&gt;
 &lt;/div&gt;</description>
      <link>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/280/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/280/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=280</guid>
      <pubDate>Mon, 22 Sep 2008 16:21:00 GMT</pubDate>
      <slash:comments>1</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=280</trackback:ping>
    </item>
    <item>
      <title>Analyzing code coverage with the PL/SQL profiler</title>
      <description>&lt;p&gt;I have, for the past several years, focused heavily on designing and building an automated code testing tool for PL/SQL: &lt;a href="http://www.toadworld.com/LinkClick.aspx?link=431&amp;tabid=67"&gt;Quest Code Tester for Oracle&lt;/a&gt;. One feature that is often requested as a part of code testing is analysis of code coverage, answering questions like:&lt;/p&gt;
&lt;ul type="disc"&gt;
    &lt;li&gt;When I run my program do I use 50% of the code? 75% of the code?  &lt;/li&gt;
    &lt;li&gt;Are there chunks of logic that are never run&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;The only way to get this kind of information is to turn on the PL/SQL profiler (and/or the new hierarchical profiler delivered with Oracle11g). The profiler keeps track of each line run by the program during the profile session. You then query the contents of the plsql_profiler_data|units|runs tables and draw your own conclusions.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;And therein lies the rub.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Code Tester users don't want to see lengthy reports showing which lines where executed. Instead, they want us to provide them a single number that tells them all they need to know: "75% of my program was executed." Period.&lt;/div&gt;
&lt;div&gt;And I would like to provide that feature, I really would. But I am feeling a little bit stumped and thought I would share with you what I have done so far, and my areas of befuddlement, in hopes that you, my dear reader, may be able to help provide additional clarity.&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;So what's the problem? Well....to figure out the % of code coverage, I need to....&lt;/div&gt;
&lt;ul&gt;
    &lt;li&gt;decide &lt;em&gt;which&lt;/em&gt; lines of code I should include in the &lt;em&gt;total&lt;/em&gt; number of possible lines that could be executed. For example: do I include comments? What about the IS, BEGIN, END keywords?&lt;/li&gt;
    &lt;li&gt;understand what lines of the code the profiler actually pays attention to, and records as having been run. I have never found the profiler data to be entirely straightforward.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so I have decided to do some research and exploration. I created a package that contains lines of code with all sorts of line breaks to see if I could isolate how Oracle treats such code in the context of profiling.&lt;/p&gt;
&lt;p&gt;You will find below the code for this package, followed by a utility I wrote to show profiling data in a way that I thought would be useful, and then the output from a profiling session.&lt;/p&gt;
&lt;p&gt;Which leads to my questions for you:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Do you have any ideas on how I should go about computing this single, golden number regarding code coverage?&lt;/li&gt;
    &lt;li&gt;What lines do you think should be included or not?&lt;/li&gt;
    &lt;li&gt;Should I take a different approach in analyzing code and profiler behavior?&lt;/li&gt;
    &lt;li&gt;What do you think of the output I got from my "test" package?&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Looking forward to your comments,&lt;br /&gt;
 &lt;/div&gt;
&lt;div&gt;Steven&lt;br /&gt;
 &lt;hr /&gt;
&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="3"&gt;Package to exercise profiler&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;pre&gt;CREATE OR REPLACE PACKAGE what_is_profiled&lt;br /&gt;IS&lt;br /&gt;   TYPE aa1 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;      INDEX BY PLS_INTEGER;&lt;/pre&gt;
&lt;pre&gt;   TYPE aa2 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;      INDEX BY PLS_INTEGER;&lt;/pre&gt;
&lt;pre&gt;   PROCEDURE proc1 (arg IN NUMBER, arg2 OUT VARCHAR2);&lt;/pre&gt;
&lt;pre&gt;   FUNCTION func1&lt;br /&gt;      RETURN VARCHAR2;&lt;br /&gt;      &lt;br /&gt;      procedure driver ;&lt;br /&gt;END what_is_profiled;&lt;br /&gt;/&lt;/pre&gt;
&lt;pre&gt;CREATE OR REPLACE PACKAGE BODY what_is_profiled&lt;br /&gt;IS&lt;br /&gt;   TYPE p_aa1 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;      INDEX BY PLS_INTEGER;&lt;/pre&gt;
&lt;pre&gt;   TYPE p_aa2 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;      INDEX BY PLS_INTEGER;&lt;/pre&gt;
&lt;pre&gt;   PROCEDURE loops (arg IN NUMBER, arg2 OUT VARCHAR2)&lt;br /&gt;   IS&lt;br /&gt;      val&lt;br /&gt;      INTEGER;&lt;br /&gt;      condition1 boolean := true;&lt;br /&gt;      condition2 boolean &lt;br /&gt;      := &lt;br /&gt;      true;&lt;br /&gt;      &lt;br /&gt;   BEGIN&lt;br /&gt;      FOR indx IN 1 .. 100&lt;br /&gt;      LOOP&lt;br /&gt;         NULL;&lt;br /&gt;      END LOOP;&lt;br /&gt;            &lt;br /&gt;      FOR &lt;br /&gt;      indx &lt;br /&gt;      IN &lt;br /&gt;      1 &lt;br /&gt;      .. &lt;br /&gt;      100&lt;br /&gt;      LOOP&lt;br /&gt;         val := 1;&lt;br /&gt;      END &lt;br /&gt;      LOOP; &lt;br /&gt;      &lt;br /&gt;      FOR indx IN 1 .. 100 LOOP NULL; END LOOP;     &lt;/pre&gt;
&lt;pre&gt;      FOR rec IN (SELECT *&lt;br /&gt;                    FROM all_source&lt;br /&gt;                   WHERE ROWNUM &lt; 101)&lt;br /&gt;      LOOP&lt;br /&gt;         val := 1;&lt;br /&gt;      END LOOP;&lt;/pre&gt;
&lt;pre&gt;      FOR &lt;br /&gt;      rec &lt;br /&gt;      IN &lt;br /&gt;      (&lt;br /&gt;      SELECT *&lt;br /&gt;                    FROM all_source&lt;br /&gt;                   WHERE ROWNUM &lt; 101&lt;br /&gt;      )&lt;br /&gt;      LOOP&lt;br /&gt;         val := 1;&lt;br /&gt;      END &lt;br /&gt;      LOOP;&lt;br /&gt;      &lt;br /&gt;      WHILE (condition1 AND condition2)&lt;br /&gt;      LOOP&lt;br /&gt;         condition1 := FALSE;&lt;br /&gt;      END LOOP;&lt;/pre&gt;
&lt;pre&gt;      WHILE &lt;br /&gt;      (&lt;br /&gt;      condition1 &lt;br /&gt;      AND &lt;br /&gt;      condition2&lt;br /&gt;      )&lt;br /&gt;      LOOP&lt;br /&gt;         condition1 &lt;br /&gt;         := &lt;br /&gt;         FALSE&lt;br /&gt;         ;&lt;br /&gt;      END LOOP;&lt;br /&gt;      &lt;br /&gt;      DECLARE&lt;br /&gt;         indx   INTEGER := 1;&lt;br /&gt;      BEGIN&lt;br /&gt;         LOOP&lt;br /&gt;            EXIT WHEN indx &gt; 100;&lt;br /&gt;            indx := indx + 1;&lt;br /&gt;         END LOOP;&lt;br /&gt;      END;&lt;br /&gt;      &lt;br /&gt;      DECLARE&lt;br /&gt;         indx   INTEGER := 1;&lt;br /&gt;      BEGIN&lt;br /&gt;         LOOP&lt;br /&gt;            EXIT &lt;br /&gt;            WHEN &lt;br /&gt;            indx &lt;br /&gt;            &gt; &lt;br /&gt;            100;&lt;br /&gt;            indx := indx + &lt;br /&gt;            1&lt;br /&gt;            ;&lt;br /&gt;         END LOOP;&lt;br /&gt;      END;      &lt;br /&gt;   END;&lt;/pre&gt;
&lt;pre&gt;   PROCEDURE conditionals &lt;br /&gt;   IS&lt;br /&gt;   a &lt;br /&gt;   boolean;&lt;br /&gt;   b boolean;&lt;br /&gt;   c boolean&lt;br /&gt;   ;&lt;br /&gt;   BEGIN&lt;br /&gt;      IF (a AND b OR c)&lt;br /&gt;      THEN&lt;br /&gt;         NULL;&lt;br /&gt;         elsif&lt;br /&gt;         a&lt;br /&gt;         then&lt;br /&gt;         null;&lt;br /&gt;         else&lt;br /&gt;         dbms_output.put_line ('a');&lt;br /&gt;      END IF;&lt;br /&gt;      &lt;br /&gt;      a := case&lt;br /&gt;      true&lt;br /&gt;      when true&lt;br /&gt;      then&lt;br /&gt;      false&lt;br /&gt;      when &lt;br /&gt;      false then&lt;br /&gt;      true&lt;br /&gt;      else&lt;br /&gt;      false&lt;br /&gt;      end&lt;br /&gt;      ;&lt;br /&gt;      a := case true&lt;br /&gt;      when true&lt;br /&gt;      then&lt;br /&gt;      false&lt;br /&gt;      when &lt;br /&gt;      false then&lt;br /&gt;      true&lt;br /&gt;      else&lt;br /&gt;      false&lt;br /&gt;      end&lt;br /&gt;      ;  &lt;br /&gt;      &lt;br /&gt;      case when &lt;br /&gt;      sysdate &gt; sysdate + 1&lt;br /&gt;      then&lt;br /&gt;      a := false;&lt;br /&gt;      when 1 &gt; 2 then&lt;br /&gt;      b := false;&lt;br /&gt;      when 1&lt;br /&gt;      &gt; 2   &lt;br /&gt;      then&lt;br /&gt;      c := false;&lt;br /&gt;      else null; end case; &lt;br /&gt;   END;&lt;/pre&gt;
&lt;pre&gt;   FUNCTION p_func1&lt;br /&gt;      RETURN VARCHAR2&lt;br /&gt;   IS&lt;br /&gt;   BEGIN&lt;br /&gt;      RETURN NULL;&lt;br /&gt;   END;&lt;/pre&gt;
&lt;pre&gt;   PROCEDURE proc1 (arg IN NUMBER, arg2 OUT VARCHAR2)&lt;br /&gt;   IS&lt;br /&gt;   BEGIN&lt;br /&gt;      NULL;&lt;br /&gt;   END;&lt;/pre&gt;
&lt;pre&gt;   FUNCTION func1&lt;br /&gt;      RETURN VARCHAR2&lt;br /&gt;   IS&lt;br /&gt;   BEGIN&lt;br /&gt;      RETURN p_func1;&lt;br /&gt;   END;&lt;br /&gt;   &lt;br /&gt;   procedure driver is&lt;br /&gt;   l varchar2(100);&lt;br /&gt;   begin&lt;br /&gt;   loops(1, l);&lt;br /&gt;   conditionals;&lt;br /&gt;   proc1&lt;br /&gt;   (&lt;br /&gt;   1&lt;br /&gt;   ,&lt;br /&gt;   l);&lt;br /&gt;   GOTO checkloop;&lt;br /&gt;   &lt;&lt;checkloop&gt;&lt;/checkloop&gt;&gt;&lt;br /&gt;   dbms_output.put_line ('a');&lt;br /&gt;   end;&lt;br /&gt;END what_is_profiled;&lt;br /&gt;/&lt;/pre&gt;
&lt;pre&gt;&lt;hr /&gt;&lt;/pre&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="4"&gt;&lt;font size="3"&gt;Utility to show profiler data&lt;/font&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;pre&gt;CREATE OR REPLACE PROCEDURE show_lines_profiled&lt;br /&gt;/*&lt;br /&gt;Assumptions: the ONLY data inside the profiler tables are for a single&lt;br /&gt;run of what_is_profiled.driver&lt;br /&gt;*/&lt;br /&gt;IS&lt;br /&gt;   l_type varchar2( 10000 );&lt;br /&gt;BEGIN&lt;br /&gt;   DBMS_OUTPUT.put_line( 'Profiling Report' );&lt;br /&gt;   DBMS_OUTPUT.put_line( '  NOT PROFILED = No profile data for this line' );&lt;br /&gt;   DBMS_OUTPUT.put_line( '  ZERO RUNS    = Profiled, but TOTAL_OCCURS = 0' );&lt;br /&gt;   DBMS_OUTPUT.put_line( '  LINE RUN     = This line was executed at least once' );&lt;/pre&gt;
&lt;pre&gt;   DBMS_OUTPUT.put_line( 'Profile Info   Line Source');&lt;br /&gt;   DBMS_OUTPUT.put_line( &lt;br /&gt;'============== ==== ==============================================================');&lt;/pre&gt;
&lt;pre&gt;   FOR rec&lt;br /&gt;   IN (  SELECT line, text&lt;br /&gt;           FROM all_source als&lt;br /&gt;          WHERE     als.owner = USER&lt;br /&gt;                AND als.name = 'WHAT_IS_PROFILED'&lt;br /&gt;                AND als.TYPE = 'PACKAGE BODY'&lt;br /&gt;       ORDER BY line )&lt;br /&gt;   LOOP&lt;br /&gt;      BEGIN&lt;br /&gt;         SELECT CASE&lt;br /&gt;                   WHEN total_occur = 0 THEN 'ZERO RUNS'&lt;br /&gt;                   ELSE 'LINE RUN'&lt;br /&gt;                END&lt;br /&gt;                   profile_type&lt;br /&gt;           INTO l_type&lt;br /&gt;           FROM plsql_profiler_data ppd&lt;br /&gt;          WHERE ppd.line# = rec.line AND ppd.unit_number = 2;&lt;br /&gt;      EXCEPTION&lt;br /&gt;         WHEN NO_DATA_FOUND&lt;br /&gt;         THEN&lt;br /&gt;            l_type      := 'NOT PROFILED';&lt;br /&gt;      END;&lt;/pre&gt;
&lt;pre&gt; &lt;/pre&gt;
&lt;pre&gt;      DBMS_OUTPUT.put_line(   RPAD( l_type, 15 )&lt;br /&gt;                           || LPAD( rec.line, 4 )&lt;br /&gt;                           || ' '&lt;br /&gt;                           || rtrim (rec.text, chr(10)));&lt;br /&gt;   END LOOP;&lt;br /&gt;END show_lines_profiled;&lt;/pre&gt;
&lt;pre&gt;&lt;hr /&gt;&lt;/pre&gt;
&lt;div&gt;&lt;strong&gt;&lt;font size="4"&gt;&lt;font size="3"&gt;Results of profiling the package&lt;/font&gt;&lt;/font&gt;&lt;/strong&gt;&lt;/div&gt;
&lt;pre&gt;BEGIN&lt;br /&gt;   DELETE FROM plsql_profiler_data;&lt;/pre&gt;
&lt;pre&gt;   DELETE FROM plsql_profiler_units;&lt;/pre&gt;
&lt;pre&gt;   DELETE FROM plsql_profiler_runs;&lt;/pre&gt;
&lt;pre&gt;   DBMS_OUTPUT.put_line(&lt;br /&gt;                         DBMS_PROFILER.start_profiler( 'What is profiled?' )&lt;br /&gt;   );&lt;br /&gt;   what_is_profiled.driver( );&lt;br /&gt;   DBMS_PROFILER.stop_profiler;&lt;br /&gt;   --&lt;br /&gt;   show_lines_profiled();&lt;br /&gt;END;&lt;br /&gt;/&lt;br /&gt;Profiling Report&lt;br /&gt;  NOT PROFILED = No profile data for this line&lt;br /&gt;  ZERO RUNS    = Profiled, but TOTAL_OCCURS = 0&lt;br /&gt;  LINE RUN     = This line was executed at least once&lt;br /&gt;Profile Info   Line Source&lt;br /&gt;============== ==== ==============================================================&lt;br /&gt;NOT PROFILED      1 PACKAGE BODY what_is_profiled&lt;br /&gt;NOT PROFILED      2 IS&lt;br /&gt;NOT PROFILED      3    TYPE p_aa1 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;NOT PROFILED      4       INDEX BY PLS_INTEGER;&lt;br /&gt;NOT PROFILED      5 &lt;br /&gt;NOT PROFILED      6    TYPE p_aa2 IS TABLE OF VARCHAR2 (100)&lt;br /&gt;NOT PROFILED      7       INDEX BY PLS_INTEGER;&lt;br /&gt;NOT PROFILED      8 &lt;br /&gt;ZERO RUNS         9    PROCEDURE loops (arg IN NUMBER, arg2 OUT VARCHAR2)&lt;br /&gt;NOT PROFILED     10    IS&lt;br /&gt;NOT PROFILED     11       val&lt;br /&gt;NOT PROFILED     12       INTEGER;&lt;br /&gt;LINE RUN         13       condition1 boolean := true;&lt;br /&gt;LINE RUN         14       condition2 boolean &lt;br /&gt;NOT PROFILED     15       := &lt;br /&gt;NOT PROFILED     16       true;&lt;br /&gt;NOT PROFILED     17       &lt;br /&gt;NOT PROFILED     18    BEGIN&lt;br /&gt;LINE RUN         19       FOR indx IN 1 .. 100&lt;br /&gt;NOT PROFILED     20       LOOP&lt;br /&gt;LINE RUN         21          NULL;&lt;br /&gt;NOT PROFILED     22       END LOOP;&lt;br /&gt;NOT PROFILED     23             &lt;br /&gt;LINE RUN         24       FOR &lt;br /&gt;NOT PROFILED     25       indx &lt;br /&gt;NOT PROFILED     26       IN &lt;br /&gt;NOT PROFILED     27       1 &lt;br /&gt;NOT PROFILED     28       .. &lt;br /&gt;NOT PROFILED     29       100&lt;br /&gt;NOT PROFILED     30       LOOP&lt;br /&gt;LINE RUN         31          val := 1;&lt;br /&gt;NOT PROFILED     32       END &lt;br /&gt;NOT PROFILED     33       LOOP; &lt;br /&gt;NOT PROFILED     34       &lt;br /&gt;LINE RUN         35       FOR indx IN 1 .. 100 LOOP NULL; END LOOP;      &lt;br /&gt;NOT PROFILED     36 &lt;br /&gt;LINE RUN         37       FOR rec IN (SELECT *&lt;br /&gt;NOT PROFILED     38                     FROM all_source&lt;br /&gt;NOT PROFILED     39                    WHERE ROWNUM &lt; 101)&lt;br /&gt;NOT PROFILED     40       LOOP&lt;br /&gt;LINE RUN         41          val := 1;&lt;br /&gt;NOT PROFILED     42       END LOOP;&lt;br /&gt;NOT PROFILED     43 &lt;br /&gt;LINE RUN         44       FOR &lt;br /&gt;NOT PROFILED     45       rec &lt;br /&gt;NOT PROFILED     46       IN &lt;br /&gt;NOT PROFILED     47       (&lt;br /&gt;ZERO RUNS        48       SELECT *&lt;br /&gt;NOT PROFILED     49                     FROM all_source&lt;br /&gt;NOT PROFILED     50                    WHERE ROWNUM &lt; 101&lt;br /&gt;NOT PROFILED     51       )&lt;br /&gt;NOT PROFILED     52       LOOP&lt;br /&gt;LINE RUN         53          val := 1;&lt;br /&gt;NOT PROFILED     54       END &lt;br /&gt;NOT PROFILED     55       LOOP;&lt;br /&gt;NOT PROFILED     56       &lt;br /&gt;LINE RUN         57       WHILE (condition1 AND condition2)&lt;br /&gt;NOT PROFILED     58       LOOP&lt;br /&gt;NOT PROFILED     59          condition1 := FALSE;&lt;br /&gt;NOT PROFILED     60       END LOOP;&lt;br /&gt;NOT PROFILED     61 &lt;br /&gt;NOT PROFILED     62       WHILE &lt;br /&gt;NOT PROFILED     63       (&lt;br /&gt;NOT PROFILED     64       condition1 &lt;br /&gt;NOT PROFILED     65       AND &lt;br /&gt;NOT PROFILED     66       condition2&lt;br /&gt;NOT PROFILED     67       )&lt;br /&gt;NOT PROFILED     68       LOOP&lt;br /&gt;NOT PROFILED     69          condition1 &lt;br /&gt;NOT PROFILED     70          := &lt;br /&gt;NOT PROFILED     71          FALSE&lt;br /&gt;NOT PROFILED     72          ;&lt;br /&gt;NOT PROFILED     73       END LOOP;&lt;br /&gt;NOT PROFILED     74       &lt;br /&gt;NOT PROFILED     75       DECLARE&lt;br /&gt;LINE RUN         76          indx   INTEGER := 1;&lt;br /&gt;NOT PROFILED     77       BEGIN&lt;br /&gt;LINE RUN         78          LOOP&lt;br /&gt;LINE RUN         79             EXIT WHEN indx &gt; 100;&lt;br /&gt;LINE RUN         80             indx := indx + 1;&lt;br /&gt;NOT PROFILED     81          END LOOP;&lt;br /&gt;NOT PROFILED     82       END;&lt;br /&gt;NOT PROFILED     83       &lt;br /&gt;NOT PROFILED     84       DECLARE&lt;br /&gt;LINE RUN         85          indx   INTEGER := 1;&lt;br /&gt;NOT PROFILED     86       BEGIN&lt;br /&gt;LINE RUN         87          LOOP&lt;br /&gt;LINE RUN         88             EXIT &lt;br /&gt;NOT PROFILED     89             WHEN &lt;br /&gt;NOT PROFILED     90             indx &lt;br /&gt;NOT PROFILED     91             &gt; &lt;br /&gt;NOT PROFILED     92             100;&lt;br /&gt;LINE RUN         93             indx := indx + &lt;br /&gt;NOT PROFILED     94             1&lt;br /&gt;NOT PROFILED     95             ;&lt;br /&gt;NOT PROFILED     96          END LOOP;&lt;br /&gt;NOT PROFILED     97       END;      &lt;br /&gt;LINE RUN         98    END;&lt;br /&gt;NOT PROFILED     99 &lt;br /&gt;ZERO RUNS       100    PROCEDURE conditionals &lt;br /&gt;NOT PROFILED    101    IS&lt;br /&gt;NOT PROFILED    102    a &lt;br /&gt;NOT PROFILED    103    boolean;&lt;br /&gt;NOT PROFILED    104    b boolean;&lt;br /&gt;NOT PROFILED    105    c boolean&lt;br /&gt;NOT PROFILED    106    ;&lt;br /&gt;NOT PROFILED    107    BEGIN&lt;br /&gt;LINE RUN        108       IF (a AND b OR c)&lt;br /&gt;NOT PROFILED    109       THEN&lt;br /&gt;NOT PROFILED    110          NULL;&lt;br /&gt;NOT PROFILED    111          elsif&lt;br /&gt;LINE RUN        112          a&lt;br /&gt;NOT PROFILED    113          then&lt;br /&gt;NOT PROFILED    114          null;&lt;br /&gt;NOT PROFILED    115          else&lt;br /&gt;LINE RUN        116          dbms_output.put_line ('a');&lt;br /&gt;NOT PROFILED    117       END IF;&lt;br /&gt;NOT PROFILED    118       &lt;br /&gt;LINE RUN        119       a := case&lt;br /&gt;NOT PROFILED    120       true&lt;br /&gt;NOT PROFILED    121       when true&lt;br /&gt;NOT PROFILED    122       then&lt;br /&gt;NOT PROFILED    123       false&lt;br /&gt;NOT PROFILED    124       when &lt;br /&gt;NOT PROFILED    125       false then&lt;br /&gt;NOT PROFILED    126       true&lt;br /&gt;NOT PROFILED    127       else&lt;br /&gt;NOT PROFILED    128       false&lt;br /&gt;NOT PROFILED    129       end&lt;br /&gt;NOT PROFILED    130       ;&lt;br /&gt;LINE RUN        131       a := case true&lt;br /&gt;NOT PROFILED    132       when true&lt;br /&gt;NOT PROFILED    133       then&lt;br /&gt;NOT PROFILED    134       false&lt;br /&gt;NOT PROFILED    135       when &lt;br /&gt;NOT PROFILED    136       false then&lt;br /&gt;NOT PROFILED    137       true&lt;br /&gt;NOT PROFILED    138       else&lt;br /&gt;NOT PROFILED    139       false&lt;br /&gt;NOT PROFILED    140       end&lt;br /&gt;NOT PROFILED    141       ;  &lt;br /&gt;NOT PROFILED    142       &lt;br /&gt;LINE RUN        143       case when &lt;br /&gt;NOT PROFILED    144       sysdate &gt; sysdate + 1&lt;br /&gt;NOT PROFILED    145       then&lt;br /&gt;LINE RUN        146       a := false;&lt;br /&gt;NOT PROFILED    147       when 1 &gt; 2 then&lt;br /&gt;NOT PROFILED    148       b := false;&lt;br /&gt;NOT PROFILED    149       when 1&lt;br /&gt;NOT PROFILED    150       &gt; 2   &lt;br /&gt;NOT PROFILED    151       then&lt;br /&gt;NOT PROFILED    152       c := false;&lt;br /&gt;NOT PROFILED    153       else null; end case; &lt;br /&gt;NOT PROFILED    154    END;&lt;br /&gt;NOT PROFILED    155 &lt;br /&gt;ZERO RUNS       156    FUNCTION p_func1&lt;br /&gt;NOT PROFILED    157       RETURN VARCHAR2&lt;br /&gt;NOT PROFILED    158    IS&lt;br /&gt;NOT PROFILED    159    BEGIN&lt;br /&gt;ZERO RUNS       160       RETURN NULL;&lt;br /&gt;ZERO RUNS       161    END;&lt;br /&gt;NOT PROFILED    162 &lt;br /&gt;ZERO RUNS       163    PROCEDURE proc1 (arg IN NUMBER, arg2 OUT VARCHAR2)&lt;br /&gt;NOT PROFILED    164    IS&lt;br /&gt;NOT PROFILED    165    BEGIN&lt;br /&gt;LINE RUN        166       NULL;&lt;br /&gt;NOT PROFILED    167    END;&lt;br /&gt;NOT PROFILED    168 &lt;br /&gt;ZERO RUNS       169    FUNCTION func1&lt;br /&gt;NOT PROFILED    170       RETURN VARCHAR2&lt;br /&gt;NOT PROFILED    171    IS&lt;br /&gt;NOT PROFILED    172    BEGIN&lt;br /&gt;ZERO RUNS       173       RETURN p_func1;&lt;br /&gt;ZERO RUNS       174    END;&lt;br /&gt;NOT PROFILED    175    &lt;br /&gt;ZERO RUNS       176    procedure driver is&lt;br /&gt;NOT PROFILED    177    l varchar2(100);&lt;br /&gt;NOT PROFILED    178    begin&lt;br /&gt;LINE RUN        179    loops(1, l);&lt;br /&gt;LINE RUN        180    conditionals;&lt;br /&gt;LINE RUN        181    proc1&lt;br /&gt;NOT PROFILED    182    (&lt;br /&gt;NOT PROFILED    183    1&lt;br /&gt;NOT PROFILED    184    ,&lt;br /&gt;NOT PROFILED    185    l);&lt;br /&gt;LINE RUN        186    GOTO checkloop;&lt;br /&gt;NOT PROFILED    187    &lt;&lt;checkloop&gt;&lt;/checkloop&gt;&gt;&lt;br /&gt;LINE RUN        188    dbms_output.put_line ('a');&lt;br /&gt;LINE RUN        189    end;&lt;br /&gt;NOT PROFILED    190 END what_is_profiled;&lt;/pre&gt;</description>
      <link>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/267/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/tabid/67/EntryID/267/Default.aspx#Comments</comments>
      <guid isPermaLink="true">http://www.toadworld.com/Default.aspx?tabid=67&amp;EntryID=267</guid>
      <pubDate>Wed, 27 Aug 2008 22:55:00 GMT</pubDate>
      <slash:comments>1</slash:comments>
      <trackback:ping>http://www.toadworld.com/DesktopModules/Blog/Trackback.aspx?id=267</trackback:ping>
    </item>
    <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/Blogs/tabid/67/EntryID/252/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/239/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/228/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/204/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/200/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/187/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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/Blogs/tabid/67/EntryID/173/Default.aspx</link>
      <author>Steven Feuerstein</author>
      <comments>http://www.toadworld.com/Community/Blogs/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>
  </channel>
</rss>