16.6. When to Use DBMS_SQLNative dynamic SQL should be your first choice (over DBMS_SQL) to satisfy dynamic SQL requirements in your PL/SQL programs for the following reasons:
There are, however, situations when you will want or need to use DBMS_SQL. The following sections describe these situations. 16.6.1. Parse Very Long StringsEXECUTE IMMEDIATE executes a single string, with a maximum length of 32K. What if your SQL statement exceeds that length? While that scenario is unlikely for any SQL statements you construct yourself, generated dynamic SQL statements based on tables with many columns (you can now have up to 1,000 columns in a table) could easily exceed that limit. DBMS_SQL to the rescue! Use a special overloading of DBMS_SQL.PARSE to parse arbitrarily long SQL and PL/SQL statements by passing a collection to the built-in that contains the full text of the dynamic statement. The collection must be of type DBMS_SQL.VARCHAR2S (maximum bytes per line is 256) or DBMS_SQL.VARCHAR2A (maximum bytes per line is 32,676). The following example of compiling a program defined in a file will demonstrate the necessary steps. Here I focus on the DBMS_SQL-specific steps; please check the file for the full implementation.
Here is an explanation of the key sections of this program:
16.6.2. Obtain Information About Query ColumnsDBMS_SQL allows you to describe the columns of your dynamic cursor, returning information about each column in an associative array of records. This capability offers the possibility of writing very generic cursor-processing code; this program may come in particularly handy when you are writing Method 4 dynamic SQL, and you are not certain how many columns are being selected. When you call this program, you need to have declared a PL/SQL collection based on the DBMS_SQL.DESC_TAB collection type (or DESC_TAB2, if your query might return column names that are greater than 30 characters in length). You can then use collection methods to traverse the table and extract the needed information about the cursor. The following anonymous block shows the basic steps you will perform when working with this built-in:
To make it easier for you to use DESCRIBE_COLUMNS, I have created a package that hides much of the underlying detail. Here is the package specification:
Notice that I provide named constants for each of the column datatypes returned in the DBMS_SQL collection. Use these constants to avoid hardcoding values when you need to check on the type of data in a column. You can also use the for_query function when you want to get information about the columns of a dynamic query, but might not otherwise be using DBMS_SQL. Here is a script demonstrating the usage of this package:
16.6.3. Meet Method 4 Dynamic SQL RequirementsDBMS_SQL supports Method 4 dynamic SQL (variable number of columns selected of variables bound) more naturally than NDS. We have already seen that in order to implement Method 4 with NDS, you must switch to dynamic PL/SQL, which is generally a higher level of abstraction than many developers want to deal with. When would you run into Method 4? It certainly arises when you build a front end to support ad-hoc query generation by users, or when you want to build a generic report program, which constructs the report format and contents dynamically at runtime. Let's step through the implementation of a variation on this theme: the construction of a PL/SQL procedure to display the contents of a tableany table, as specified by the user at runtime. Here I cover only those aspects pertaining to the dynamic SQL itself; check out the intab.sp file on the book's web site for the full implementation. 16.6.3.1 The "in table" procedural interfaceSo I will use PL/SQL and DBMS_SQL. But before building any code, I need to come up with a specification. How will the procedure be called? What information do I need from my user (a developer, in this case)? What should a user have to type to retrieve the desired output? I want my procedure (which I call "intab" for "in table") to accept the inputs in the following table.
Given these inputs, the specification for my procedure becomes the following:
Here are some examples of calls to intab, along with their output. First, the entire contents of the emp table:
And now let's see just those employees in department 10, specifying a maximum length of 20 characters for string columns:
And now an entirely different table, with a different number of columns:
Notice that the user does not have to provide any information about the structure of the table. My program will get that information itselfprecisely the aspect of intab that makes it a Method 4 dynamic SQL example. 16.6.3.2 Steps for intab constructionTo display the contents of a table, follow these steps:
16.6.3.3 Constructing the SELECTTo extract the data from the table, I have to construct the SELECT statement. The structure of the query is determined by the various inputs to the procedure (table name, WHERE clause, etc.) and the contents of the data dictionary. Remember that the user does not have to provide a list of columns. Instead, I must identify and extract the list of columns for that table from a data dictionary view. I have decided to use the ALL_TAB_COLUMNS view in the intab procedure so the user can view the contents not only of tables he owns (which are accessible in USER_TAB_COLUMNS), but also any table for which he has SELECT access. Here is the cursor I use to fetch information about the table's columns:
With this column cursor, I extract the name, datatype, and length information for each column in the table. How should I store all of this information in my PL/SQL program? To answer this question, I need to think about how that data will be used. It turns out that I will use it in many waysfor example:
Therefore, I need to work with the column information several times throughout my program, yet I do not want to read repeatedly from the data dictionary. As a result, when I query the column data out of the ALL_TAB_COLUMNS view, I will store that data in three PL/SQL collections:
So if the third column of the emp table is SAL, then colname(3) = 'SAL', coltype(3) = 'NUMBER', and collen(3) = 7, and so forth. The name and datatype information is stored directly from the data dictionary. Calculating the column length is a bit trickier, but also not crucial to learning how to write Method 4 dynamic SQL. I will leave it to the reader to study the file. I apply all of my logic inside a cursor FOR loop that sweeps through all the columns for a table (as defined in ALL_COLUMNS). This loop (shown in the following example) fills my PL/SQL collection:
When this loop completes, I have constructed the select list, populated my PL/SQL collections with the column information I need for calls to DBMS_SQL.DEFINE_COLUMN and DBMS_SQL.COLUMN_VALUE, and also created the column header line. Now that was a busy loop! Now it is time to parse the query, and then construct the various columns in the dynamic cursor object. 16.6.3.4 Defining the cursor structureThe parse phase is straightforward enough. I simply cobble together the SQL statement from its processed and refined components, including, most notably, the column list I just constructed (the col_list variable):
Of course, I want to go far beyond parsing. I want to execute this cursor. Before I do that, however, I must give some structure to the cursor. With DBMS_SQL, when you open a cursor, you have merely retrieved a handle to a chunk of memory. When you parse the SQL statement, you have associated a SQL statement with that memory. But as a next step, I must define the columns in the cursor so that it can actually store fetched data. With Method 4 dynamic SQL, this association process is complicated. I cannot hardcode the number or type of calls to DBMS_SQL.DEFINE_COLUMN in my program; I do not have all the information until runtime. Fortunately, in the case of intab, I have kept track of each column to be retrieved. Now all I need to do is issue a call to DBMS_SQL.DEFINE_COLUMN for each row defined in my PL/SQL table colname. Before we go through the actual code, here are some reminders about DBMS_SQL.DEFINE_COLUMN. The header for this built-in procedure is as follows:
There are three things to keep in mind with this built-in:
In the context of the intab procedure, the row in the PL/SQL table is the Nth position in the column list. The datatype is stored in the coltype PL/SQL table, but must be converted into a call to DBMS_SQL.DEFINE_COLUMN using the appropriate local variable. These complexities are handled in the following FOR loop:
When this loop is completed, I will have called DEFINE_COLUMN for each column defined in the PL/SQL tables. (In my version, this is all columns for a table. In your enhanced version, it might be just a subset of all these columns.) I can then execute the cursor and start fetching rows. The execution phase is no different for Method 4 than it is for any of the other simpler methods. Specify:
where fdbk is the feedback returned by the call to EXECUTE. Now for the finale: retrieval of data and formatting for display. 16.6.3.5 Retrieving and displaying dataI use a cursor FOR loop to retrieve each row of data identified by my dynamic cursor. If I am on the first row, I will display a header (this way, I avoid displaying the header for a query that retrieves no data). For each row retrieved, I build the line and then display it:
The line-building program is actually a numeric FOR loop in which I issue my calls to DBMS_SQL.COLUMN_VALUE. I call this built-in for each column in the table (information that is stored inyou guessed itmy PL/SQL tables). As you can see below, I use my is_* functions to determine the datatype of the column and therefore the appropriate variable to receive the value. Once I have converted my value to a string (necessary for dates and numbers), I pad it on the right with the appropriate number of blanks (stored in the collen PL/SQL table) so that it lines up with the column headers.
There you have it. A very generic procedure for displaying the contents of a database table from within a PL/SQL program. Again, check out intab.sp for the full details; the intab_dbms_sql.sp file also contains a version of this procedure that is updated to take advantage of Oracle 9i Database features and is more fully documented. 16.6.4. Minimize Parsing of Dynamic CursorsOne of the drawbacks of EXECUTE IMMEDIATE is that each time the dynamic string is executed it will be reprepared, which will usually involve parsing, optimization, and plan generation. For most dynamic SQL requirements, the overhead of these steps will be compensated for by other benefits of NDS (in particular, the avoidance of calls to a PL/SQL API as happens with DBMS_SQL). In some cases, however, the parse phase may be quite expensive. For such scenarios, DBMS_SQL may be a better solution, precisely for the same reason that usually makes this built-in package unattractive: you have control overand have to code foreach explicit step in the process. With DBMS_SQL, you can explicitly avoid the parse phase when you know that the SQL string you are executing dynamically is changing only its bind variables. All you have to do is avoid calling DBMS_SQL.PARSE again, and simply rebind the variable values with calls to DBMS_SQL.BIND_VARIABLE. Let's look at a very simple example, demonstrating the specific calls you make to the DBMS_SQL package. The following anonymous block executes a dynamic query inside a loop:
Within my loop, I take the following actions:
This is all valid (and, of course, you would usually follow up the execution of the query with fetch and retrieve steps), yet it also is a misuse of DBMS_SQL. Consider the following rewrite of the same steps:
In this usage of DBMS_SQL, I now declare the cursor only once, because I can reuse the same cursor with each call to DBMS_SQL.PARSE. I also move the parse call outside of the cursor. Because the structure of the SQL statement itself doesn't change, I don't need to reparse for each new value of i. So I parse once and then, within the loop, bind a new variable value into the cursor, and execute. When I am all done (after the loop terminates), I close the cursor. The ability to perform each step explicitly and separately gives developers enormous flexibility (and also headaches from all the code and complexity of DBMS_SQL). If that is what you need, DBMS_SQL is hard to beat. If you do use DBMS_SQL in your application, I encourage you to take advantage of the package found in the dynalloc.pkg file. This "dynamic allocation" package helps you to:
|
Monday, October 26, 2009
Section 16.6. When to Use DBMS_SQL
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment