num NUMBER; BEGIN /* Also causes a PLS−00302 error! */ dyn_plsql ('dynamic.num := 5'); END; / Now suppose that I define the num variable inside a package called dynamic: CREATE OR REPLACE PACKAGE dynamic IS num NUMBER; END; / I am then able to execute the dynamic assignment to this newly defined variable successfully. BEGIN dyn_plsql ('dynamic.num := 5'); END; / What's the difference between these two pieces of data? In the first attempt, the variable num is defined locally in the anonymous PL/SQL block. In my second attempt, num is a public "global" 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 like 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 only make references to globally defined programs and data structures. These PL/SQL elements include stand alone 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, BEGIN dyn_plsql ('undefined.packagevar := ''abc'''); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE (sqlcode); END; / I will not get an unhandled exception. The dynpl/sql.tst file compares the performance of static PL/SQL execution (assigning a value to a global variable) with dynamic PL/SQL. 2.3 The DBMS_SQL Interface 2.5 DBMS_SQL Examples Copyright (c) 2000 O'Reilly & Associates. All rights reserved. [Appendix A] What's on the Companion Disk? 2.4.7 Executing DDL in PL/SQL 96 Chapter 2 Executing Dynamic SQL and PL/SQL 2.5 DBMS_SQL Examples This section contains extended examples of using the DBMS_SQL package. 2.5.1 A Generic Drop_Object Procedure The dynamic SQL of DBMS_SQL allows you to create completely generic modules to manipulate objects in the Oracle7 Server. You can, for instance, write a procedure that drops the specified table, but you can also create a module that will drop whatever kind of object you specify, as shown in this first version of drop_object: CREATE OR REPLACE PROCEDURE drop_object (type_in IN VARCHAR2, name_in IN VARCHAR2) IS /* Declare and create a cursor to use for the dynamic SQL */ cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR; fdbk PLS_INTEGER; BEGIN /* Construct the SQL statement, parse it and execute it. */ DBMS_SQL.PARSE (cur, 'DROP ' || type_in || ' ' || name_in, DBMS_SQL.NATIVE); fdbk := DBMS_SQL.EXECUTE (cur); DBMS_SQL.CLOSE_CURSOR (cur); END; / Well, that was straightforward enough. But how useful is it? Sure, it lets me execute DDL in PL/SQL, which wasn't possible before. But assuming that I have written this procedure as part of a broader interface to manage database objects from a screen, it is fairly limited. It is, in fact, simply equivalent to a DROP OBJECT statement. Boooring. Why not utilize the flexibility of the PL/SQL language to provide additional productivity, above and beyond the "straight" DDL? Wouldn't it be nice to, for example, drop all packages with names like "STR%" or drop all objects of any type in a schema with a single command? To implement these kinds of requests, I need to let the user pass in wildcarded object names and types. I can then use these values to identify N number of matching objects. Where are these objects defined? In the USER_OBJECTS or ALL_OBJECTS data dictionary view. Interestingly, then, in my final version of drop_object, I combine the use of both static and dynamic SQL to add value to the standard DROP OBJECT command: /* Filename on companion disk: dropobj.sp */* CREATE OR REPLACE PROCEDURE drop_object (type_in IN VARCHAR2, name_in IN VARCHAR2) IS /* The static cursor retrieving all matching objects */ CURSOR obj_cur IS SELECT object_name, object_type FROM user_objects WHERE object_name LIKE UPPER (name_in) 97 AND object_type LIKE UPPER (type_in) ORDER BY object_name; cur PLS_INTEGER := DBMS_SQL.OPEN_CURSOR; fdbk PLS_INTEGER; BEGIN /* For each matching object */ FOR obj_rec IN obj_cur LOOP /* Reusing same cursor, parse and execute the drop statement. */ DBMS_SQL.PARSE (cur, 'DROP ' || obj_rec.object_type || ' ' || obj_rec.object_name, DBMS_SQL.NATIVE); fdbk := DBMS_SQL.EXECUTE (cur); END LOOP; DBMS_SQL.CLOSE_CURSOR (cur); END; / Using this enhanced utility, I can now remove all objects in my schema that are used in the Order Entry system with this single command: SQL> exec drop_object ('%', 'oe%'); Or I could be more selective and simply drop all tables containing the substring "emp." SQL> exec drop_object ('table', '%emp%'); The drop_object procedure demonstrates the flexiblity and power that PL/SQL can bring to administrative tasks. It also clearly points out the inherent dangers of this power. With drop_object in place, you can remove everything in your schema with one program! Use this procedure −− and any other similar utilities you build −− with the utmost of care. 2.5.2 A Generic Foreign Key Lookup Function I am always looking for ways to cut down on the code I need to write to maintain foreign keys in Oracle−based applications. For all its great features, Oracle Forms −− just like its predecessor, SQL*Forms V3 −− does not offer comprehensive support for handling foreign keys. Sure, there is the LOV object and its associated record group, which can be used to look up and validate entries on the screen. This object cannot, however, be used to perform Post−Query lookups of a foreign key's description −− a ubiquitous operation in an application built on a normalized database. This same lookup process could also take place in a report, in a graph, in an embedded C program, and so on. Before the advent of DBMS_SQL, the only solution was to build a function for each separate entity that serves as a foreign key in a table. This function would take the foreign key and return the name or description. The specifications for such functions would look like these, FUNCTION caller_name (caller_id IN caller.caller_id%TYPE) RETURN VARCHAR2; FUNCTION company_name (company_id IN company.company_id%TYPE) RETURN VARCHAR2; FUNCTION company_type (company_type_cd IN company.company_type_cd%TYPE) RETURN VARCHAR2; and so on, for as many foreign keys as you've got. And every time a new foreign key is added to the mix, you must write a new function. [Appendix A] What's on the Companion Disk? 2.5.2 A Generic Foreign Key Lookup Function 98 Wouldn't it be just fabulous if you could construct a single generic function using dynamic SQL that would work for all foreign keys? Let's give it a shot. First, what information would I need to pass to this function in order to construct the SQL statement to retrieve the name or description? Here are some possibilities: Foreign key value The ID or code of the record we wish to locate. Table name The table containing the record identified by the foreign key value. Primary key column name The name of the column in the previous table that contains the foreign key value (in this, the "source table" for the foreign key, it is actually the table's primary key). Primary key name or description column name The name of the column in the previous table that contains the name or description of the primary key. These parameters would allow me to construct SQL statements that look like the following: SELECT caller_nm FROM caller WHERE caller_id = 154 SELECT call_type_nm FROM call_type WHERE call_type_id = 2 The steps I need to perform are straightforward: 1. Construct the SQL statement along the lines of the examples. 2. Fetch the matching record. 3. Retrieve the value from the cursor and return it to the calling program. My first version of this function, fk_name, shows a generic function that returns the name and description of a foreign key. /* Filename on companion disk: fkname.sf */* CREATE OR REPLACE FUNCTION fk_name (fk_id_in IN INTEGER, fk_table_in IN VARCHAR2, fk_id_col_in IN VARCHAR2, fk_nm_col_in IN VARCHAR2) RETURN VARCHAR2 IS /* Declare and obtain a pointer to a cursor */ cur INTEGER := DBMS_SQL.OPEN_CURSOR; /* Variable to receive feedback from package functions */ fdbk INTEGER; /* || The return value of the function. Notice that I have || to hardcode a size in my declaration. */ return_value VARCHAR2(100) := NULL; BEGIN /* [Appendix A] What's on the Companion Disk? 2.5.2 A Generic Foreign Key Lookup Function 99 || Parse the query. I construct most of the SQL statement from || the parameters with concatenation. I also include a single || bind variable for the actual foreign key value. */ DBMS_SQL.PARSE (cur, 'SELECT ' || fk_nm_col_in || ' FROM ' || fk_table_in || ' WHERE ' || fk_id_col_in || ' = :fk_value', DBMS_SQL.NATIVE); /* Bind the variable with a specific value −− the parameter */ DBMS_SQL.BIND_VARIABLE (cur, 'fk_value', fk_id_in); /* Define the column in the cursor for the FK name */ DBMS_SQL.DEFINE_COLUMN (cur, 1, fk_nm_col_in, 100); /* Execute the cursor, ignoring the feedback */ fdbk := DBMS_SQL.EXECUTE (cur); /* Fetch the row. If feedback is 0, no match found */ fdbk := DBMS_SQL.FETCH_ROWS (cur); IF fdbk > 0 THEN /* Found a match. Extract the value/name for the key */ DBMS_SQL.COLUMN_VALUE (cur, 1, return_value); END IF; /* || Close the cursor and return the description, which || could be NULL if no records were fetched. */ DBMS_SQL.CLOSE_CURSOR (cur); RETURN return_value; END; / I can now use this function in a Post−Query trigger in Oracle Forms, as follows: :call.name := fk_name (:call.caller_id, 'caller', 'caller_id', 'caller_nm'); :call.call_type_ds := fk_name (:call.call_type_id, 'call_type', 'call_type_id', 'call_type_nm'); Well, that was fun. I now have a generic look−up for foreign keys. Instead of stopping at this point, however, let's explore some ways to improve the functionality and ease of use of this function. Several things caught my eye on the first pass: • The programmer has to type in a lot of information in the parameter list in order to look up the foreign key. One might argue that it is almost as easy to type a SQL statement. While that's not true, I have found that programmers are resistant to using new toys unless their advantage is overwhelming. • The size of the value returned by COLUMN_VALUE is hardcoded at 100 bytes. Any sort of hardcoding is always something to be avoided. • There is a clear pattern to the names of the tables and columns in the previous examples. The foreign key column is always the table name with an "_ID" suffix. The description column is always the table name with an "_NM" suffix. It seems to me that the function should be able to take advantage of these kinds of naming conventions. • [Appendix A] What's on the Companion Disk? 2.5.2 A Generic Foreign Key Lookup Function 100 . to cut down on the code I need to write to maintain foreign keys in Oracle based applications. For all its great features, Oracle Forms −− just like its predecessor, SQL*Forms V3 −− does not. dynamic SQL of DBMS_SQL allows you to create completely generic modules to manipulate objects in the Oracle7 Server. You can, for instance, write a procedure that drops the specified table, but you. DBMS_SQL.CLOSE_CURSOR (cur); RETURN return_value; END; / I can now use this function in a Post−Query trigger in Oracle Forms, as follows: :call.name := fk_name (:call.caller_id, 'caller', 'caller_id',