16.4. Dynamic PL/SQLDynamic PL/SQL offers some of the most interesting and challenging coding opportunities. Think of it: while a user is running your application, you can take advantage of NDS to do any of the following:
I have used this technique to build very flexible code generators, softcoded calculation engines for users, and much more. Dynamic PL/SQL allows you to work at a higher level of generality, which can be both challenging and exhilarating. There are some rules and tips you need to keep in mind when working with dynamic PL/SQL blocks and NDS:
Let's explore these rules. First, I will build a little utility to execute dynamic PL/SQL:
This one program encapsulates many of the rules mentioned previously for PL/SQL execution. By enclosing the string within a BEGIN-END anonymous block, I guarantee that whatever I pass in will be executed as a valid PL/SQL block. For instance, I can execute the calc_totals procedure dynamically as simply as this:
Now let's use this program to examine what kind of data structures you can reference within a dynamic PL/SQL block. In the following anonymous block, I want to use dynamic SQL to assign a value of 5 to the local variable num:
This string is executed within its own BEGIN-END block, which appears to be a nested block within the anonymous block named "dynamic." Yet when I execute this script, I receive the following error:
The PL/SQL engine is unable to resolve the reference to the variable named num. I get the same error even if I qualify the variable name with its block name:
Now suppose that I define the num variable inside a package called dynamic:
I can now successfully execute the dynamic assignment to this newly defined variable :
What's the difference between these two pieces of data? In my first attempt, the variable num is defined locally in the anonymous PL/SQL block. In my second attempt, num is a public global variable defined in the dynamic package. This distinction makes all the difference with dynamic PL/SQL. It turns out that a dynamically constructed and executed PL/SQL block is not treated as a nested block; instead, it is handled as if it were a procedure or function called from within the current block. So any variables local to the current or enclosing blocks are not recognized in the dynamic PL/SQL block; you can make references only to globally defined programs and data structures. These PL/SQL elements include standalone functions and procedures and any elements defined in the specification of a package. Fortunately, the dynamic block is executed within the context of the calling block. If you have an exception section within the calling block, it will trap exceptions raised in the dynamic block. So if I execute this anonymous block in SQL*Plus:
I will not get an unhandled exception.
The following sections offer a few examples of dynamic PL/SQL to spark your interest and, perhaps, inspire your creativity. 16.4.1. Replace Repetitive Code with Dynamic BlockThis is a true story, I kid you not. During a consulting stint at an insurance company here in Chicago, I was asked to see what I could do about a particularly vexing program. It was very large and continually increased in sizesoon it would be too large to even compile. Much to my amazement, this is what the program looked like:
Each one of those line numbers represented fine print in an insurance policy that helped the company achieve its primary objective (minimizing the payment of claims). For each line number, there was a "process_line" program that handled those details. And as the insurance company added more and more exceptions to the policy, the program got bigger and bigger. Not a very scalable approach to programming! To avoid this kind of mess, a programmer should be on the lookout for repetition of code. If you can detect a pattern, you can either create a reusable program to encapsulate that pattern, or you can explore the possibility of expressing that pattern as a dynamic SQL construction. At the time, I fixed the problem using DBMS_SQL, but dynamic SQL would have been a perfect match. Here's the NDS implementation:
From thousands of lines of code down to one executable statement! Of course, in most cases, identification of the pattern and conversion of that pattern into dynamic SQL will not be so straightforward. Still, the potential gains are enormous. 16.4.2. Implement Method 4 Dynamic SQL in NDSMethod 4 dynamic SQL, introduced early in this chapter, means that at compile time, you don't know how many columns you will be querying and/or how many bind variables will need to be set. This level of uncertainty greatly complicates the code you will have to write. This section examines the kind of code you need to write to implement dynamic SQL Method 4 with native dynamic SQL. Later in the chapter, we review how you can implement Method 4 with DBMS_SQL. First, let's take a look at an example that will drive home the complexities involved. Suppose that I want to retrieve a single row by a dynamic WHERE clause and from a table whose name is specified at runtime. My program would, at first glance, look something like this:
No matter what table name I pass to this procedure, "SELECT *" will retrieve all the columns for all the rows. "So what's the problem?" you might be asking. The problem may be found in the INTO clause. I reference a variable named l_row. Yet I must declare that variable before I can compile and run my code. This variable must be a record, whose structure matches that of the table. Yet I cannot declare a variable based on a dynamic record type. OK, so maybe what I should do is avoid hardcoding the record variable and instead select into individual variables, and then just make that list variable, as in:
Conceptually, that may sound fine, but it simply won't work. The bottom line regarding Method 4 and EXECUTE IMMEDIATE is that the INTO and USING clauses of this statement are static, not dynamic. So is it impossible to implement Method 4 requirements with NDS? No, it is, in fact, possible, but we will have to switch gears, and use dynamic PL/SQL. Consider the following revised implementation of get_rows:
Now the EXECUTE IMMEDIATE runs a dynamic PL/SQL block. All the static parts of this block are in bold. The highlights of this implementation include the following:
Shifting from dynamic SQL to dynamic PL/SQL is the key step to implementing Method 4 with NDS. What I have shown you is a very simple example. There are, of course, complexities. If you have to deal with a dynamic number of bind variables, you will have to write a parsing routine to ferret out each appearance of the placeholders and replace them with your variable values. In addition, if you need to pass the data that you have queried from the dynamic block to the outer, static block, you will run into additional complexities. That outer block will not know the structure of the data you have populated with your query. At this point, however, the implementation becomes very much application-specific. For a more complete demonstration of using dynamic PL/SQL to implement Method 4 with NDS, check out the intab9i.sp file available on the book's web site. The intab procedure offers a PL/SQL implementation of "SELECT * FROM table," that is, it queries and displays (via DBMS_OUTPUT) the contents of the specified table. While it is possible to implement Method 4 with NDS, it is an awkward and complicated process. This is one dynamic requirement for which DBMS_SQL might provide a better solution. Finally, if you do choose NDS for Method 4, you may still want to take advantage of the DBMS_SQL.DESCRIBE_COLUMNS procedure. Both of these topics are addressed later in this chapter. |
Thursday, October 22, 2009
Section 16.4. Dynamic PL/SQL
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment