DBPedias

Your Database Knowledge Community

Steven Feuerstein

  1. One Way Out of Functions

    I received this request from a reader in July:
    "Could you please explain why Oracle recommends that we not use OUT/IN-OUT parameters in functions?"
    Actually, it's not just Oracle. Most "gurus" in the software world make the same suggestion. There are two key reasons for the recommendation that you should only return data through the RETURN clause of a function, not the parameter list. I sum up these answers here, and then I offer a section from Oracle PL/SQL Best Practices, 2nd edition, that explores the same topic using the "story" style of that book.
    Two key reasons to use only IN parameters with functions:
    1. 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.
    2. A function that contains an OUT or IN OUT parameter cannot be called from within a SQL statement. Here's an example:

    Perhaps you don't often run across the need to call your own application-specific functions inside SQL. I take advantage of this feature frequently in the backend and APEX code of the PL/SQL Challenge, a site that offers quizzes on PL/SQL, SQL and APEX.
    So remember: just because Oracle let's you do something (like define an OUT parameter in a function's parameter list), doesn't mean that you should do it!
    And now an excerpt from Oracle PL/SQL Best Practices, 2nd edition (Chapter 8, "Playing with Blocks"):
    Where'd that data come from?
    AKA: Functions should return data only through the RETURN clause.
    Problem: Jasper returns data in a very confusing manner.
    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:
    FUNCTION excuse_title (
       excuse_id_in IN mfe_excuse.isbn%TYPE
     , author_out OUT mfe_excuse.author%TYPE
     , word_count_out OUT mfe_excuse.word_count%TYPE)
    RETURN mfe_excuse.title%TYPE
    IS BEGIN
       ... implementation unimportant! ...
    END excuse_title;
    And then he puts this function to use as follows:
    PROCEDURE process_excuse (excuse_id_in IN mfe_excuse.isbn%TYPE)
    IS
     l_title mfe_excuse.title%TYPE;
     l_author mfe_excuse.author%TYPE;
     l_word_count mfe_excuse.word_count%TYPE;
    BEGIN
       l_title := excuse_title (l_id, l_author, l_word_count);
       ...
    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:
    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 and I have to declare a bunch of individual variables to use the darned thing!
    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."
    Solution: Return multiple values through a single, composite structure or with a procedure.
    Here are the steps that Sunita takes to revamp the lookup function and make it more useful:
    • Make sure the name of the program reflects what it does: 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.
    • Pass everything in the RETURN clause: 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.
    Here, then, is the new header of the program:
    FUNCTION excuse_info (id_in IN mfe_excuse.id%TYPE)
       RETURN mfe_excuse%ROWTYPE
    And here is the revised usage of this function:
    PROCEDURE process_excuse (excuse_id_in IN mfe_excuse.isbn%TYPE)
    IS
       l_excuse mfe_excuse%ROWTYPE;
    BEGIN
       l_excuse := excuse_info (l_id);
    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:
    PACKAGE excuse_pkg
    IS
       TYPE key_info IS RECORD (
          title mfe_excuse.title%TYPE
        , author mfe_excuse.author%TYPE
        , word_count mfe_excuse.word_count%TYPE
       );
     
       FUNCTION excuse_info (id_in IN mfe_excuse.id%TYPE)
          RETURN key_info;
    END excuse_pkg;
    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.
    If you need to return multiple pieces of information, take one of the following approaches:
    • Return a record or collection of values 

      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. 
    • Break up a single function into multiple functions, all returning scalar values

      With this approach, you can call the functions from within SQL statements. 
       
    • Change a function into a procedure

      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
    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.
     

    More ...
    Tags: plsql,function,parameters
    Category: PL/SQL Obsession
  2. QuickTips: Using BULK COLLECT with Queries

    Oracle introduced a significant enhancement to PL/SQL’s SQL-related capabilities with the FORALL statement and BULK COLLECT clause for queries. Together, these are referred to as bulk processing statements for PL/SQL. Why, you might wonder, would this be necessary? We all know that PL/SQL is tightly integrated with the underlying SQL engine in the Oracle database. PL/SQL is the database programming language of choice for Oracle—even though you can now use Java inside the database as well.

    But this tight integration does not mean that there is no overhead associated with running SQL from a PL/SQL program. When the PL/SQL runtime engine processes a block of code, it executes the procedural statements within its own engine, but passes the SQL statements on to the SQL engine. The SQL layer executes the SQL statements and then returns information to the PL/SQL engine, if necessary.

    This transfer of control between the PL/SQL and SQL engines is called a context switch. Each time a switch occurs, there is additional overhead. There are a number of scenarios in which many switches occur and performance degrades. As you can see, PL/SQL and SQL might be tightly integrated on the syntactic level, but "under the covers" the integration is not as tight as it could be.

    With FORALL and BULK COLLECT, however, you can fine-tune the way these two engines communicate, effectively telling the PL/SQL engine to compress multiple context switches into a single switch, thereby improving the performance of your applications. 

    Tags: oracle,plsql,bulk collect,forall
    Category: PL/SQL Obsession
  3. Use Forward References in Package Bodies?

    A PL/SQL developer (let's call him Dan) sent me this request last week:

    =============================================================
    Would you please settle a minor argument for me? We're trying to institute coding standards at my company.
     
    I've been pushing for developers to put explicit declarations of private functions and procedures in their packages. My co-workers argue back that they are unnecessary. Other than giving us the ability to reorganize the source as we see fit, I can't find anything that states additional benefits of using them. However, as I've always used them, I feel uncomfortable not asking for them.
     
    Here's an example of how I use them:
    CREATE OR REPLACE PACKAGE BODY my_sample_package
    IS
       --===========================================
       -- PRIVATE DECLARATIONS
       --===========================================
       FUNCTION my_private_function_f (p_parm1_in IN NUMBER)
          RETURN BOOLEAN;
       PROCEDURE my_private_proc_p (
           p_parm1_in IN NUMBER, p_parm2_in IN NUMBER);
       --===========================================
       -- PRIVATE SOURCE
       --===========================================
       FUNCTION my_private_function_f (p_parm1_in IN NUMBER)
          RETURN BOOLEAN
       IS
       BEGIN
          IF p_parm1_in > 0
          THEN
             RETURN TRUE;
          ELSE
             RETURN FALSE;
          END IF;
       END my_private_function_f;
       PROCEDURE my_private_proc_p (
           p_parm1_in IN NUMBER, p_parm2_in IN NUMBER)
       IS
          l_my_var   NUMBER;
       BEGIN
          IF p_parm1_in > 0
          THEN
             l_my_var := p_parm2_in / p_parm1_in;
          END IF;
       END;
      
       --===========================================
       -- PUBLIC SOURCE
       --===========================================
       -- Blah
       -- Blah
    END my_sample_package;
    Can you give me any reasons why or why not private declarations should be used?
    =============================================================
     
    What Dan calls a "private declaration" is commonly referred to as a forward reference, also known as a forward declaration, which is defined by Wikipedia as:
    In computer programming, a forward declaration is a declaration of an identifier (denoting an entity such as a type, a variable, or a function) for which the programmer has not yet given a complete definition.

    As you can see in the code example, the section under "PRIVATE DECLARATIONS" contain the forward declarations: just the headers of the subprograms without their implementation, same as you would find in the package specification.

    I do not use forward declarations in my code in any sort of routine way. I only use them when they are required - and that happens when two programs are inter-dependent (A calls B and B calls A). In this scenario, my code will not compile without forward declarations. But that is quite the rare occurrence.

    The problem with using forward declarations routinely is that it introduces another kind of redundancy into my code. When and if the parameter list of a private subprogram changes, I have to change it in both places (forward declaration and actual definition of the subprogram). I'd really rather not have to do that if I do not need to.

    So instead I rely on the compiler to tell me when my subprograms are "out of order", and I cut and paste the entire subprogram to an earlier point in the package body to make sure that all references can be resolved.

    Well, that's my viewpoint on forward declarations. What do you think?


    More ...
    Tags: plsql,forward declaration
    Category: PL/SQL Obsession
  4. Write Tiny Little Chunks of Code - or Maybe Not

    I have for many years now promoted "Write tiny little chunks of code" as one of the most impactful and important steps we can all take to improve the readability and maintainability of code. The basic idea is that you should not let your executable sections get to be more than 50 or 60 lines of code in length. You should, in other words, be able to at a single glance take in the full algorithm/logical flow of the subprogram. Compare that to a procedure whose executable section goes on and on for hundreds of lines. Sure, you can page through all that, but how easy is it to step back and gain an overall understanding of what the module does?

    The best way to achieve this goal is to modularize one's code: hide detailed logic behind a name and then reference that logic by calling the procedure with that name. The name describes what is going on "under the covers"; you only drill down into the procedure as needed.

    You can modularize at any of thse levels:

    • Schema-level procedure or function: defined outside of a package, callable by any schema with execute authority on that module. I generally recommend against these. Put all your code in packages; easier to find, manage, enhance.
    • Packaged subprograms: this is the main method of modularization. You define a set of programs in the package specification that will be used by other subprograms in the appliation. You can also define private subprograms that can only be called by other subprograms in that package.
    • Local or nested subprograms: a procedure or function that is defined in the declaration section of a PL/SQL block (anonymous or named). This module is considered local because it is defined only within the parent PL/SQL block. It cannot be called by any other PL/SQL blocks defined outside that enclosing block.

    The following anonymous block, for example, declares a local  procedure:

    DECLARE
       PROCEDURE show_date (date_in IN DATE) IS
       BEGIN
          DBMS_OUTPUT.PUT_LINE (TO_CHAR (date_in, 'Month DD, YYYY');
       END show_date;
    BEGIN
       show_date (SYSDATE);
       show_date (SYSDATE+30);
    END ;

    I think that nested subprograms are one of the most powerful and underutilized techniques in the PL/SQL developer community. I use them all the time, all over the place. There are two central benefits to nested subprograms (aka, local modules):

    Reduce the size of the module by stripping it of repetitive code

    Code reduction leads to higher code quality because you have fewer lines to test and fewer potential bugs. It takes less effort to maintain the code because there is less to maintain. And when you do have to make a change, you make it in one place in the local module, and the effects are felt immediately throughout the parent module.

     Improve the readability of your code

    Even if you do not repeat sections of code within a module, you still may want to pull out a set of related statements and package them into a local module. This can make it easier to follow the logic of the main body of the parent module.

    When making use of lots of nested subprograms, your executable section becomes small and transparent in meaning. I will run through an example and then leave it to my readers to respond. The reason I say this is that I wrote this posting in response to a developer, who wrote to me as follows:

    "I would like to have a posting to discuss local sub programs or functions within a function or procedure. This could be a good debate because I have my issues on the subject but I was watching the "Say no to spaghetti code, say yes to Extreme Modularization" video and Steven was promoting that kind of programming."

    There are certainly potential downsides to nested subprograms; you can "go overboard," as with any other technique or feature. But I certainly would be more worried about inadequate modularization and the resulting spaghetti code, rather than overdoing it.

    An Example

    Suppose my team is building a support application. Customers call with problems, and we put their call in a queue if it cannot be handled immediately. I must now write a program that distributes unhandled calls out to members of the support team. While there are still unhandled calls in the queue, assign them to employees who are under-utilized (have fewer calls assigned to them then the average for their department). The technical specifications run to 50 pages of detailed documentation, and I could just "start coding" - transcribing specs into code. Instead, I translate the "executive summary" into the following tiny little program.

    PROCEDURE distribute_calls (
       department_id_in IN employees.department_id%TYPE
    )
    IS
    BEGIN
       WHILE (calls_still_unhandled ())
       LOOP
          FOR emp_rec IN support_dept_cur (department_id_in)
          LOOP
             IF call_analysis.current_caseload (emp_rec.employee_id)             call_analysis.avg_caseload_for_dept (department_id_in)
             THEN
                assign_next_open_call_to (emp_rec.employee_id, l_case_id);
                notify_customer (l_case_id);
             END IF;
          END LOOP;
       END LOOP;
    END distribute_calls;
    This executable section contains calls to predefined programs in the call_analysis, but also I simply come up with names for subprograms for other areas of functionality that are not yet implemented.

    I then create stubs for the yet-to-be-defined programs:

    PROCEDURE distribute_calls (
       department_id_in IN employees.department_id%TYPE
    )
    IS
       l_case_id   cases.ID%TYPE;
     
       CURSOR support_dept_cur (
          department_id_in IN employees.department_id%TYPE
       )
       IS
          SELECT *
            FROM employees
           WHERE department_id = department_id_in;
     
       FUNCTION calls_still_unhandled
          RETURN BOOLEAN
       IS
       BEGIN
         
              RETURN NULL;
       END calls_still_unhandled;
     
       PROCEDURE assign_next_open_call_to (
          employee_id_in IN employees.employee_id%TYPE
        , case_id_out OUT cases.ID%TYPE
       )
       IS
       BEGIN
         NULL;
       END assign_next_open_call_to;
     
       PROCEDURE notify_customer (case_id_in IN cases.ID%TYPE)
       IS
       BEGIN
          NULL;
       END notify_customer;
    BEGIN
       WHILE (calls_still_unhandled ())
       LOOP
          FOR emp_rec IN support_dept_cur (department_id_in)
          LOOP
             IF call_analysis.current_caseload (emp_rec.employee_id)
                        call_analysis.avg_caseload_for_dept (department_id_in)
             THEN
                assign_next_open_call_to (emp_rec.employee_id, l_case_id);
                notify_customer (l_case_id);
             END IF;
          END LOOP;
       END LOOP;
    END distribute_calls;
    I can now drill down into each nested subprogram and do the same thing: write the "executive summary," dream up new subprograms "on the fly" and delay worrying about the implementation details until I get down to that level.

    This approach lets me focus on logic flow, makes it easier to identify - proactively - opportunities for code reuse, and results in code that is so very easy to read.

    I am pretty well addicted to nested subprograms, but I fear that not everyone shares my passion on this topic. So...please contribute your thoughts.


    More ...
    Tags: plsql,oracle,package,anonymous block,procedure
    Category: PL/SQL Obsession
  5. REPLACE and CLOBs: Watch out for "_"!

    The ecosystem of Oracle technology is vast, ever-changing and full of surprises. These surprises are sometimes referred to as "bugs", "undocumented features" and just plain "What? Are you kidding me?"

    Michael Rosenblum of Dulcian, and co-author of Oracle PL/SQL for Dummies (I know, I know, sounds a bit silly - but it is an excellent "intro" book for beginner PL/SQL developers), recently asked me about some strange behavior he'd found:

    One of my developers just noticed a strange behavior in all versions of Oracle 10g that we could test (10.2.0.3, 10.2.0.5). Here is the scenario:

    • Use the REPLACE function with a CLOB variable
    • Replace "T_" with "ZZ"; the variables contains a string "T(", as well as several occurrences of "T_".
    •  In the result, Oracle replaces both "T_" and "T(" with "ZZ"!

    The underscore character is clearly being interpreted as a wild-card!

    This behavior does not occur with VARCHAR2 and it does not happen on Oracle Database 11g Release 2.

    Here's an example if you'd like to try this at home:

    DECLARE
       v_clob   CLOB:='T_T(a number)';
    BEGIN
       v_clob   :=REPLACE(v_clob,'T_','ZZ');
       DBMS_OUTPUT.put_line(v_clob);
    END;

    Result on 10g: "ZZZZa number)"

    Expected result (and result on 11g): "ZZT(a number)"

    Michael wondered if he'd missed something in the documentation or if this was a bug...and a search in Metalink revealed the following:

    Bug 4598943
    Database Version - 9.2.0.6
    Fixed in Product Version- 11.0.0.0.0
    Workaround: None
    So watch out for using REPLACE with clobs prior to 11g - you may not get the results you expect!
     

    More ...
    Tags: oracle,11g,10g,plsql,clob,replace
    Category: PL/SQL Obsession
  6. REPLACE and CLOBs: Watch out for "_"!

    The ecosystem of Oracle technology is vast, ever-changing and full of surprises. These surprises are sometimes referred to as "bugs", "undocumented features" and just plain "What? Are you kidding me?"

    Michael Rosenblum of Dulcian, and co-author of Oracle PL/SQL for Dummies (I know, I know, sounds a bit silly - but it is an excellent "intro" book for beginner PL/SQL developers), recently asked me about some strange behavior he'd found:

    One of my developers just noticed a strange behavior in all versions of Oracle 10g that we could test (10.2.0.3, 10.2.0.5). Here is the scenario:

    • Use the REPLACE function with a CLOB variable
    • Replace "T_" with "ZZ"; the variables contains a string "T(", as well as several occurrences of "T_".
    •  In the result, Oracle replaces both "T_" and "T(" with "ZZ"!

    The underscore character is clearly being interpreted as a wild-card!

    This behavior does not occur with VARCHAR2 and it does not happen on Oracle Database 11g Release 2.

    Here's an example if you'd like to try this at home:

    DECLARE
       v_clob   CLOB:='T_T(a number)';
    BEGIN
       v_clob   :=REPLACE(v_clob,'T_','ZZ');
       DBMS_OUTPUT.put_line(v_clob);
    END;

    Result on 10g: "ZZZZa number)"

    Expected result (and result on 11g): "ZZT(a number)"

    Michael wondered if he'd missed something in the documentation or if this was a bug...and a search in Metalink revealed the following:

    Bug 4598943
    Database Version - 9.2.0.6
    Fixed in Product Version- 11.0.0.0.0
    Workaround: None
    So watch out for using REPLACE with clobs prior to 11g - you may not get the results you expect!
     

    More ...
    Tags: oracle,11g,10g,plsql,clob,replace
    Category: PL/SQL Obsession
  7. QuickTips: Using FORALL with DML Statements

    This blog is the first in a series of "QuickTips" - a collection of advice and guidance on specific features of the Oracle PL/SQL language. The purpose of a QuickTip is not to provide a comprehensive resource on the topic, such as full description of syntax, etc. Instead, I will point you to Oracle documentation for such things, and focus instead on some "added value" content.

    I will inaugurate the series with FORALL, undoubtedly one of the most important features of the PL/SQL.

    I assume that you are running an instance of Oracle Database 10 Release 2 or higher.

    Tags: oracle,plsql,bulk collect,forall
    Category: PL/SQL Obsession
  8. QuickTips: Using FORALL with DML Statements

    This blog is the first in a series of "QuickTips" - a collection of advice and guidance on specific features of the Oracle PL/SQL language. The purpose of a QuickTip is not to provide a comprehensive resource on the topic, such as full description of syntax, etc. Instead, I will point you to Oracle documentation for such things, and focus instead on some "added value" content.

    I will inaugurate the series with FORALL, undoubtedly one of the most important features of the PL/SQL.

    I assume that you are running an instance of Oracle Database 10 Release 2 or higher.

    Tags: oracle,plsql,bulk collect,forall
    Category: PL/SQL Obsession
  9. Curious PL/SQL Code

    I discovered something curious today. Check out the following block; it runs without any compilation or runtime error:
    DECLARE
       l_char   CHAR (1);
    
       CURSOR x
       IS
          SELECT dummy INTO l_char FROM DUAL;
    BEGIN
       OPEN x;
    
       FETCH x INTO l_char;
    
       CLOSE x;
    END;
     
    Can anyone see why I find it odd and curious? I have sent a note to the PL/SQL Product Manager asking what he thinks. What do you think?
     
    Cheers, Steven

    More ...
    Tags: plsql
    Category: PL/SQL Obsession
  10. Curious PL/SQL Code

    I discovered something curious today. Check out the following block; it runs without any compilation or runtime error:
    DECLARE
       l_char   CHAR (1);
    
       CURSOR x
       IS
          SELECT dummy INTO l_char FROM DUAL;
    BEGIN
       OPEN x;
    
       FETCH x INTO l_char;
    
       CLOSE x;
    END;
     
    Can anyone see why I find it odd and curious? I have sent a note to the PL/SQL Product Manager asking what he thinks. What do you think?
     
    Cheers, Steven

    More ...
    Tags: plsql
    Category: PL/SQL Obsession
  1. 1
  2. Next ›
  3. Last »