You are on page 1of 16

Oracle

Solutions for High-End


Oracle® DBAs and Developers Professional

Cutting Through
Nasty Design
with Multi-level
Collections
Steven Feuerstein
In this article, Steven Feuerstein demonstrates how you can use some of the coolest, August 2004
newest PL/SQL features—namely, multi-level, string-indexed collections—to make it Volume 11, Number 8
easier to write code based on poorly designed table structures.
1 Cutting Through Nasty Design

A
S I noted in the dedication to the first edition of Oracle PL/SQL with Multi-level Collections
Programming, fundamentally, our job as programmers is all about Steven Feuerstein
problem-solving. We’re given a problem or a set of requirements, and
8 Poor Man’s Source Control,
we need to come up with a solution, a series of commands written in the Part 2
PL/SQL language, for that problem. Darryl Hurley
Occasionally, the solution is obvious, intuitive, simple, and concise.
11 Oracle Enterprise Manager
And just as occasionally, people walk up to me on the street and hand me
10g: Not Just for “GUI
$1,000,000 worth of bearer bonds. Right! No, we don’t very frequently DBAs” Anymore!
encounter simple problems—or, more to the point, problems with simple Bill Burke
solutions. Our software is supposed to model the real world, and the real
15 Tip: Deferred Constraints
world is ugly, complicated, and full of exceptions. That’s why we get paid
Daniel Clamage
the big bucks.
So what’s my problem? Among other things, one problem that nags at me 16 August 2004 Downloads
is that the pressure of producing code under deadlines often results in us
writing some, shall we say, sub-optimal software. It might (and often does)
meet the requirements. It might, in other words, “work,” but it might be
almost impossible to maintain, enhance, or debug. Not a pretty situation, not Indicates accompanying files are available online at
an elegant application. www.pinnaclepublishing.com.
And perhaps even worse than writing sub-optimal • A negative position means that the item is an
code is having to inherit such stuff—and then maintain it appetizer or starter.
or enhance it. This article presents one such example of a • A position of 0 means that the item is free, a
rather horrible design (and resulting code) and then special promotional deal for which ENEL is
shows how we can apply some of the newest features in justifiably famous.
PL/SQL collections to make our lives peachy-keen and • A NULL ingredient name means that this is an
our code squeaky-clean. item that should be displayed on the menu, along
with the price. Otherwise, price is ignored.
You call this relational?
Oracle is an object-relational database. Oh, all right, skip Now, I’m sure that many of the readers of this fine
the marketing. Oracle is a relational database, and you publication are snickering aloud at the silliness of this
can use some object-like features if you’re so inclined. design. But surely you can think of applications that have
Almost everyone doing Oracle database design and employed a design not entirely dissimilar to this one. And
development follows the relational paradigm. Sometimes, in any case, you might glance at ALL_ARGUMENTS, a
however, you come across an application in which it very handy but somewhat opaque data dictionary view
appears that Codd’s 12 Rules of Database Design had provided by Oracle.
never been perused. The bottom line is that we can laugh all we want, but
Here’s one such scenario: Peter works at Eat Now Peter moved on to greener pastures at the Finer Eats To
Eat Later (ENEL), a restaurant chain that specializes in All (FETA) restaurant chain and I was just hired to take
a wide variety of food served around the clock. They over for him at ENEL. He left behind this data structure
wanted to automate their menu management (create, and a working—let me emphasize that: a working—
change, print) and assigned the job of back-end design application used by ENEL restaurant managers every
and development to Peter. After giving it some thought, day to produce their menus.
he came up with this table structure: There’s no way I could convince HQ management
that Peter was messed up in the head when it came
CREATE TABLE all_ingredients (
id INTEGER to fourth normal form, and that they should let me
, day_name VARCHAR2(30) rebuild the application. It worked for them. In fact, on
, meal_name VARCHAR2(30)
, position INTEGER my second day on the job they handed me 20 pages of
, item_name VARCHAR2(30)
, ingredient_name INTEGER
urgently needed enhancements, and told me to get to
, quantity NUMBER work. What’s a person to do? Make the best of a bad
, text VARCHAR2(200)
, price NUMBER situation, of course.
);

ALTER TABLE all_ingredients ADD


CONSTRAINT pk_all_ingredients
PRIMARY KEY (id);

He figured that it held all the


information he’d need, all in one
easy-to-access table. That would save
him the trouble of having to join lots
of structures together. He also came
up with some rules for the data in
this table:
• Day names are the full names
of the days in uppercase
(MONDAY, TUESDAY, and
so on).
• Meal names are one of the
following: BREAKFAST,
LUNCH, DINNER, BRUNCH,
or SNACK.
• The item name is the name of the
item that appears on the menu.
• Position is the position of the
item in the menu within each
meal name. Figure 1. Defining the Ingredients component in Qnxo.

2 Oracle Professional August 2004 www.pinnaclepublishing.com


What that means for me is to 1) put into place some and delete rows in the table.
best practices that will also give me a higher comfort • all_ingredients_up—Utility package that contains a
level, and 2) leverage cool, new features whenever variety of utility programs.
possible so at a minimum I can keep myself reasonably
well-entertained. When I need to write custom logic against the
all_ingredients table, I’ll put them in the all_ingredients_
Establishing a comfortable baseline xp (eXtra stuff) package.
As you might expect from Peter’s design, he didn’t put For example, I’m going to create a copy of the
a lot of store in thinking things through. His PL/SQL all_ingredients table in my development schema, so I
code was a typical mess of spaghetti logic and redundant can do my investigations and changes without affecting
SQL statements. As I’ve long preached (and I can sound production. I’ll then populate that table with a small set of
awfully preachy, can I not?) in these pages, I’d much data, as shown in Listing 1. Notice that I’m not writing
rather call a PL/SQL procedure to insert a row and call a INSERT statements. Instead, I call the all_ingredients_
function to retrieve data than write that SQL over and cp.ins procedure to do the work for me (and guarantee
over again. consistent error handling behavior).
Today, I can take advantage of a new tool I’ve
been building for the past year (Qnxo, formerly known
Listing 1. Using encapsulated procedures to insert test data.
as Swyg) to help me establish a baseline of data
encapsulation packages and other utilities to make me DECLARE
comfortable and productive. (Note: You can download l_rows PLS_INTEGER;
BEGIN
Qnxo from www.qnxo.com. We currently offer a free trial all_ingredients_cp.del_by (where_clause_in => '1=1'
version; Qnxo will be available on a subscription basis , rows_out => l_rows);
--
later in 2004.) all_ingredients_cp.ins
In just a few minutes, I’ve created a Menu application (day_name_in => 'MONDAY'
,meal_name_in => 'BREAKFAST'
in Qnxo with an Ingredients component that contains ,position_in => 1
within it all of the database objects that have the word ,item_name_in => 'COFFEE'
,ingredient_name_in => NULL
INGREDIENTS in the name (see Figure 1). ,quantity_in => 1
I can then immediately produce a set of encapsulation ,text_in => 'Traditional brew'
,price_in => 1.50
packages by running the Generate Table API definitions for );
application “Menu” task (see Figure 2). These packages are: all_ingredients_cp.ins
(day_name_in => 'MONDAY'
• all_ingredients_tp—Package of TYPE and ROWTYPE ,meal_name_in => 'DINNER'
,position_in => -99
declarations against the base table. ,item_name_in => 'SHRIMP COCKTAIL'
• all_ingredients_qp—Query package to retrieve data ,ingredient_name_in => NULL
,quantity_in => 6
from the table. ,text_in => '6 jumbo shrimp and sauce'
• all_ingredients_cp—Change package to insert, update, ,price_in => 10.95

Figure 2.
Producing a set
of encapsulation
packages.

www.pinnaclepublishing.com Oracle Professional August 2004 3


);
all_ingredients_cp.ins Listing 2. First pass at using collections, using encapsulated logic.
(day_name_in => 'MONDAY'
,meal_name_in => 'LUNCH'
,position_in => 1 CREATE OR REPLACE PACKAGE menu_pkg --menu_pkg1.pkg
IS
,item_name_in => 'HAMBURGER'
PROCEDURE generate_report;
,ingredient_name_in => NULL END menu_pkg;
,quantity_in => 1 /
,text_in => 'All-American heart food'
,price_in => 5.50 CREATE OR REPLACE PACKAGE BODY menu_pkg
); IS
COMMIT; PROCEDURE generate_report
END; IS
l_ingredients all_ingredients_tp.all_ingredients_tc;
BEGIN
Having prepared my tables and created a baseline set l_ingredients := all_ingredients_qp.allrows;
END generate_report;
of packages with which to write my application, I can END menu_pkg;
move on to the central programming task. /

Programming in a rut Listing 3. Package body that exposes logic to BULK COLLECT
As I read through the requirements for the new menu- table to collection.
printing program, I realize that I’ll need to read from the
all_ingredients table several times, and combine menu CREATE OR REPLACE PACKAGE BODY menu_pkg --menu_pkg2.pkg
IS
information in a variety of ways. PROCEDURE generate_report
IS
I could take the approach of writing a variety of TYPE all_ingredients_tc IS TABLE
queries against the all_ingredients table (get the list of OF all_ingredients%ROWTYPE
INDEX BY BINARY_INTEGER;
distinct day names, get all the meals for a given day, l_ingredients all_ingredients_tc;
BEGIN
and so on). I’d then use cursor FOR loops to retrieve the SELECT *
rows (more than once, perhaps with different ORDER BULK COLLECT INTO l_ingredients
FROM all_ingredients
BYs), and then write the PL/SQL code to manipulate ORDER BY day_name,meal_name,item_name,POSITION;
that data, and so forth. This is the kind of thing that END generate_report;
END menu_pkg;
PL/SQL developers have been doing for over a decade /
now. And I could get it all to
work—but it probably wouldn’t
Listing 4. Checking for a new day name as I scan the collection.
be as efficient as I’d like.
Fortunately, I’ve been reading 1 CREATE OR REPLACE PACKAGE BODY menu_pkg -- menu_pkg3.pkg
about collections and BULK 2 IS
3 PROCEDURE generate_report
COLLECT. I find myself thinking 4 IS
that perhaps I should use BULK 5 TYPE all_ingredients_tc IS TABLE OF all_ingredients%ROWTYPE
6 INDEX BY BINARY_INTEGER;
COLLECT to dump the contents 7
of my table into a collection. That 8 l_ingredients all_ingredients_tc;
9 l_current_day_name all_ingredients.day_name%TYPE;
would be faster than a bunch of 10
separate cursor FOR loops. I could 11 PROCEDURE new_day_name_processing (
12 ingred_row_in IN all_ingredients%ROWTYPE
then stay within my familiar 13 )
14 IS
PL/SQL procedural world to 15 BEGIN
handle all of the special cases and 16 NULL;
17 END new_day_name_processing;
requirements. 18 BEGIN
With that inspirational 19 SELECT *
20 BULK COLLECT INTO l_ingredients
thought, I quickly throw together 21 FROM all_ingredients
the skeleton of a program (shown 22 ORDER BY day_name, meal_name, item_name, POSITION;
23
in Listing 2) that follows this 24 IF l_ingredients.COUNT > 0
25 THEN
approach, filling up a local 26 FOR ingred_index IN l_ingredients.FIRST .. l_ingredients.LAST
collection with the contents of 27 LOOP
28 IF l_ingredients (ingred_index).day_name
the all_ingredients table. Hmm. 29 != l_current_day_name
There isn’t much to see, is 30 OR l_current_day_name IS NULL
31 THEN
there? That’s because I’m using 32 l_current_day_name := l_ingredients (ingred_index).day_name;
functionality generated by Qnxo. 33 new_day_name_processing (l_ingredients (ingred_index));
34 END IF;
Listing 3 shows the code that you’d 35 END LOOP;
write if you had to actually write 36 END IF;
37 END generate_report;
it yourself. 38* END menu_pkg;

4 Oracle Professional August 2004 www.pinnaclepublishing.com


Great. Now all my data is sitting in my collection and Of course, this ingredients table has more than one
I can scan through its contents performing my analysis, level of information in it, so after I’m done checking for a
breaking out the data, and putting together my report. new day name, I need to check for a new meal name, as
I need to break out the information for each day, for shown in Listing 5. I expect you can detect a pattern here;
instance. To do that, I’d write logic like that shown in I mostly repeated the logic I had written for day names.
Listing 4, which involves the steps listed in Table 1. The main thing to conclude from this growing body of
code is this: Something seems wrong with my approach.
It’s too complicated, I’m writing too much code—
Table 1. The steps involved in the logic from Listing 4.
and I haven’t even gotten to anything interesting
Line # Significance (complicated) yet. Surely there’s a better, simpler way
4 Declare a local variable to hold the current day name, to process this data. I could simply give up on the idea
initially NULL.
of using collections and go back to using a set of
24 Don’t start the scan unless I’m sure I have something to look at.
nested queries, but I’d lose the performance advantage
28-30 Do I have a new day name? Of course, I need to check for a
NULL value—that is, we’re at the very beginning of our scan. of BULK COLLECT.
32 Set the current day name.
33, 11-17 Call a local procedure to take care of all the “new day Challenge those assumptions
name” processing. Do you ever find yourself in that “place” where nothing
is coming together
Listing 5. Checking for new day names and new meal names. smoothly, when problems
can only be solved by
1 CREATE OR REPLACE PACKAGE BODY menu_pkg making the code even more
2 IS
3 PROCEDURE generate_report complex, less readable? In
4 IS those moments, it’s very
5 TYPE all_ingredients_tc IS TABLE OF all_ingredients%ROWTYPE
6 INDEX BY BINARY_INTEGER important to stop, take a
7
8 l_ingredients all_ingredients_tc
step back, and evaluate
9 l_current_day_name all_ingredients.day_name%TYPE your assumptions. Perhaps
10 l_current_meal_name all_ingredients.meal_name%TYPE
11 you (or someone else)
12 PROCEDURE new_day_name_processing ( made the wrong decision
13 ingred_row_in IN all_ingredients%ROWTYPE
14 ) very early on, and lots of
15 IS your troubles stem from
16 BEGIN
17 NULL that mistake.
18 END new_day_name_processing
19 In this case, I started
20 PROCEDURE new_meal_name_processing ( out with a problematic data
21 ingred_row_in IN all_ingredients%ROWTYPE
22 ) structure: all_ingredients.
23 IS Yet I wasn’t in a position to
24 BEGIN
25 NULL re-engineer this low-level
26 END new_meal_name_processing
27 BEGIN
data structure. I was pretty
28 SELECT * much stuck with it, right?
29 BULK COLLECT INTO l_ingredients
30 FROM all_ingredients Yes and no. It’s true that
31 ORDER BY day_name, meal_name, item_name, POSITION I couldn’t really change
32
33 IF l_ingredients.COUNT > 0 the all_ingredients table
34 THEN itself. That doesn’t mean,
35 FOR ingred_index IN l_ingredients.FIRST .. l_ingredients.LAST
36 LOOP however, that I had to
37 IF l_ingredients (ingred_index).day_name != l_current_day_name
38 OR l_current_day_name IS NULL reproduce that poorly
39 THEN designed table in my
40 l_current_day_name := l_ingredients (ingred_index).day_name
41 l_current_meal_name := NULL collection definition.
42 new_day_name_processing (l_ingredients (ingred_index)) By doing so, by simply
43 END IF
44 passing on all of that
45 IF l_ingredients (ingred_index).meal_name !=
46 l_current_meal_name
compressed information
47 OR l_current_day_name IS NULL into the l_all_ingredients
48 THEN
49 l_current_meal_name := l_ingredients (ingred_index).meal_name collection, I then placed the
50 new_meal_name_processing (l_ingredients (ingred_index)) burden on my PL/SQL
51 END IF
52 END LOOP program to deal with it. The
53 END IF buck was stopping in the
54 END generate_report
55* END menu_pkg; meal_pkg.generate_report

www.pinnaclepublishing.com Oracle Professional August 2004 5


procedure and it was a very big,
Listing 6. Defining a four-level nested collection structure.
heavy buck. Was this absolutely
necessary? Not at all! 1 CREATE OR REPLACE PACKAGE BODY menu_pkg
One of the central problems with 2 IS
3 TYPE all_ingredients_tc IS TABLE OF all_ingredients%ROWTYPE
the all_ingredients table is that it’s a 4 INDEX BY BINARY_INTEGER;
denormalized data structure; there’s 5
6 TYPE items_aat IS TABLE OF all_ingredients_tc
a natural hierarchy in all_ingredients: 7 INDEX BY all_ingredients.item_name%TYPE;
8
Day of week 9 TYPE meals_aat IS TABLE OF items_aat
Meal 10 INDEX BY all_ingredients.meal_name%TYPE;
Position/Item 11
Ingredient 12 TYPE menus_aat IS TABLE OF meals_aat
13 INDEX BY all_ingredients.day_name%TYPE;
14
Perhaps I could better reflect 15* g_menu menus_aat;
this hierarchy in my program data
structures, namely my collection(s).
Listing 7. Loading the data into multiple collections.
In Oracle9i Release 2 and above, I’m
now able to define collections within 1 PROCEDURE load_menu_data
collections; I can also use strings as 2 IS
3 l_ingredients all_ingredients_tc;
indexes for my associative arrays. 4 BEGIN
Let’s see how I could apply this 5 SELECT *
6 BULK COLLECT INTO l_ingredients
feature to all_ingredients. 7 FROM all_ingredients;
8
9 IF l_ingredients.COUNT > 0
Multi-level string-indexed 10 THEN
collections to the rescue! 11 FOR ingred_index IN l_ingredients.FIRST .. l_ingredients.LAST
12 LOOP
Listing 6 shows the code I wrote to 13 g_menu
define a four-level nested collection 14 (l_ingredients (ingred_index).day_name)
15 (l_ingredients (ingred_index).meal_name)
structure to hold information from 16 (l_ingredients (ingred_index).item_name)
the all_ingredients table. Table 2 17 (l_ingredients (ingred_index).position) :=
18 l_ingredients (ingred_index);
provides an explanation of that code. 19 END LOOP;
(Note: Listings for the remainder of 20 END IF;
21* END load_menu_data;
the article are taken from menu_
pkg.pks and menu_pkg.pkb.)

load_menu_data procedure. This procedure is called by


Table 2. Explanation of the code in Listing 6.
the generate_report procedure to populate the collections
Line # Significance so that I can then use them to generate menus and other
3-4 Declare a collection type that mirrors the structure reports for the restaurant managers. Table 3 gives an
of all_ingredients. This looks just like the previous explanation of the procedure.
definition. The only difference is that the index values
will in this case be the POSITION of a particular
ingredient.
Table 3. Explanation of the procedure in Listing 7.
6-7 Declare a collection type that’s indexed by the item
name. Each row in this collection contains the list of Line # Significance
3 Declare a collection to hold the “flat data” from
ingredients for that item.
all_ingredients.
9-10 Declare a collection type that’s indexed by the meal
5-7 Fill up that flat data collection using BULK COLLECT.
name. Each row in this collection contains the list of
We now have a local copy of the all_ingredients table
items for that meal.
in my l_ingredients collection.
12-13 Declare a collection type that’s indexed by the day
11-19 Use a numeric FOR loop to go through each row of
name. Each row in this collection contains the list of
all_ingredients and transfer that flat data into the
meals for that day.
four-level nested collection.
15 Declare the four-level nested collection that contains
all of the information for the menu. On the one hand, I’ve now explained this program.
On the other hand, that superficial presentation doesn’t
So, you’ll now be asking: What does all that really do justice to all the wonderful work that PL/SQL is
do for us? Let’s take a look at how I’d load up this doing on my behalf in lines 13-17, the actual transfer
complicated structure. assignment. Let’s look at this a bit more closely.
Listing 7 shows the implementation of the I assign a record (one row from the all_ingredients
6 Oracle Professional August 2004 www.pinnaclepublishing.com
table) to a row in the lowest level of my four-level nesting;
Listing 8. The various “show” programs that are used to display
the index of that row is the position column value (its
successively nested layers of data within the g_menus structure.
position in the menu, which is presumably unique for a
given food item). To do that, I must specify its location in PROCEDURE show_ingredients
all of the collections above it. So I specify the day name for (ingredients_in IN all_ingredients_tc)
IS
that row as the index in the top level collection: l_ingredient all_ingredients%ROWTYPE;
l_row PLS_INTEGER;
BEGIN
(l_ingredients (ingred_index).day_name) l_row := ingredients_in.FIRST;

WHILE (l_row IS NOT NULL)


I then use the meal name from that same row in the LOOP
all_ingredients table to tell PL/SQL which row in the pl ('Ingredient name = '
|| ingredients_in (l_row).ingredient_name
second level of nesting to use: ,indent_in => 9
);
(l_ingredients (ingred_index).meal_name) pl (' qty = '
|| ingredients_in (l_row).quantity
,indent_in => 9
Within a given meal, I want to pull together all of my );
l_row := ingredients_in.NEXT (l_row);
items, so I use the item name as the index in the third END LOOP;
END show_ingredients;
level of nesting:
PROCEDURE show_item (item_in IN items_aat)
(l_ingredients (ingred_index).item_name) IS
l_itemname all_ingredients.item_name%TYPE;
BEGIN
Finally, I’m down to the lowest level of information: pl ('Number of distinct items = '
|| item_in.COUNT, indent_in => 6);
the ingredients of a given item. For this collection, I’ll l_itemname := item_in.FIRST;
use the position—at last, a good old-fashioned integer
WHILE (l_itemname IS NOT NULL)
index!—to locate the actual record of data. LOOP
show_ingredients (item_in (l_itemname));
l_itemname := item_in.NEXT (l_itemname);
(l_ingredients (ingred_index).position) END LOOP;
END show_item;
By using the names as my indices, the net effect of PROCEDURE show_meal (meal_in IN meals_aat)
assigning the record is to perform SELECT DISTINCTs IS
l_mealname all_ingredients.meal_name%TYPE;
on the various levels of data (for example, SELECT BEGIN
DISTINCT day_name) and automatically group my pl ('Number of distinct meals = '
|| meal_in.COUNT, indent_in => 3);
information into the appropriate sub-collection. I also l_mealname := meal_in.FIRST;
no longer need to use an ORDER BY on my query from WHILE (l_mealname IS NOT NULL)
this table; the row values in the collections automatically LOOP
show_item (meal_in (l_mealname));
order the information for me! l_mealname := meal_in.NEXT (l_mealname);
END LOOP;
END show_meal;
Building my menu from collections
PROCEDURE show_menu_data
Once I’ve transferred my flat all_ingredients data to IS
the nested structure, I can very easily write the code l_dayname all_ingredients.day_name%TYPE;
BEGIN
necessary to read through the menu data and display it pl ('Number of distinct day names = '
correctly. At the highest level, the logic to traverse this || g_menu.COUNT);
l_dayname := g_menu.FIRST;
collection is nothing more than:
WHILE (l_dayname IS NOT NULL)
LOOP
For each day name show_meal (g_menu (l_dayname));
Display day information l_dayname := g_menu.NEXT (l_dayname);
For each meal on that day END LOOP;
Display item information END show_menu_data;
For each item in that meal
Display ingredients
End for each item Handling the special rules
End for each meal
End for each day Well, that last sentence wasn’t quite true. One of the
problems with the all_ingredients table was the fact that
The PL/SQL code to support this nested logic is four layers of information were flattened into a single
shown in Listing 8; it’s slightly simplified from the version row of data. The second problem is that the original
you’ll find in menu_pkg.pkb, but it follows the same flow. developer relied heavily on “magic values” and special
All the “heavy lifting” was done when I assigned the rules when putting data into the table. Here’s one example
values to the collections; now I just walk through the of such a special rule:
arrays and display contents. Continues on page 14

www.pinnaclepublishing.com Oracle Professional August 2004 7


Oracle
Professional

Poor Man’s Source Control,


Part 2
Darryl Hurley
At first glance, database triggers that fire when Data again? Luckily I have Poor Man’s Source Control (PMSC)
Definition Language (DDL) statements execute don’t seem installed, so I can quickly see the old code.
to have a lot of uses beyond preventing or logging certain
operations. However, with a little imagination they can SQL> SELECT text
2 FROM pmsc_source_line
provide some handy utilities, such as the simple source 3 WHERE pmsc_id = ( SELECT MAX(pmsc_id)
control system that Darryl Hurley displays in this series of 4 FROM pmsc_source
5 WHERE object_owner = USER
articles. Last month, he explained how to enable automatic 6 AND object_name = 'SIMPLE' )
7 ORDER BY line;
saving of changed PL/SQL source in the database. In this
final installment, he explains how to successfully restore TEXT
--------------------------------------------------
your saved code. PROCEDURE simple ( p_arg NUMBER ) AS
BEGIN

A
DBMS_OUTPUT.PUT_LINE('You entered ' || p_arg);
H, the wonderful sense of security provided by END;
source control. It’s such a nice feeling to know
that order can quickly be restored no matter how How did I manage to screw up such a simple
garbled things get. This freedom promotes exploration procedure? Oh well, the magic of cut and paste makes the
of new and exciting code alternatives because restoration problem go away.
is only a few keystrokes away. Well, at least it can be for
PL/SQL, as I’m demonstrating in this series of articles SQL> CREATE OR REPLACE
2 PROCEDURE simple ( p_arg NUMBER ) AS
using Data Definition Language (DDL) triggers 3 BEGIN
introduced in Oracle9i. Part 1 explained how to save 4 DBMS_OUTPUT.PUT_LINE('You entered ' || p_arg);
5 END;
stored procedures, and that segues nicely into this second 6 /
installment on restoring your code.
Procedure created.
I’ll start with a simple example of something we’ve
all experienced. Starting with a perfectly functional bunch SQL> EXEC simple(99);
You entered 99
of code that requires a change, I’ll begin by verifying the
current behavior. PL/SQL procedure successfully completed.

SQL> EXEC simple(99); Great, everything is back to normal now as if


You entered 99
nothing ever happened. And the best thing is that
PL/SQL procedure successfully completed. there’s no embarrassing log of my mistake.

Now I open up my editor of choice, make my code Automating recovery


change, drop it into the database, recompile everything, Wouldn’t it be nice if the recovery I just detailed was
and I’m all set. A quick test reveals the instant success of automated? Yes, I think it would be, and I’ll enable
my change. it in PMSC using the restore procedure shown in
Listing 1.
SQL> EXEC simple(99);
You entered 98

PL/SQL procedure successfully completed. Listing 1. The restore procedure.

Oh no, what did I do wrong? I don’t have time to /*--------------------------------------------------*/


PROCEDURE restore ( p_owner VARCHAR2,
figure it out right now, as I have to get back to working on p_name VARCHAR2 ) IS
my database upgrade to Oracle10g. So, for now I’ll just /*--------------------------------------------------*/

put the code back the way it was... now, how was that -- cursor to find lines of code for the procedure

8 Oracle Professional August 2004 www.pinnaclepublishing.com


CURSOR curs_get_source ( cp_owner VARCHAR2, demonstrates showing procedure names, timestamps,
cp_name VARCHAR2 ) IS
SELECT text and IDs, not the actual code. That’s relatively simple,
FROM pmsc_source_line so I didn’t bother including it here, but it’s in the
WHERE pmsc_id = ( SELECT MAX(pmsc_id)
FROM pmsc_source accompanying Download file for this article.
WHERE object_owner = cp_owner
AND object_name = cp_name )
ORDER BY line;
Listing 2. Showing saved source.
-- text of C or R line
v_sql VARCHAR2(32767) := NULL; /*--------------------------------------------------*/
PROCEDURE show ( p_owner VARCHAR2,
BEGIN p_name VARCHAR2 ) IS
/*--------------------------------------------------*/
-- for every line of code saved in PMSC...
FOR v_source IN curs_get_source(p_owner,p_name) LOOP -- cursor to get source that matches owner and
-- name - assume wildcard values passed in
-- prepend C or R to the first line CURSOR curs_get_source( cp_owner VARCHAR2,
IF curs_get_source%ROWCOUNT = 1 THEN cp_name VARCHAR2 ) IS
v_sql := 'CREATE OR REPLACE '; SELECT pmsc_id,
END IF; timestamp,
object_owner,
-- append line of code object_name
v_sql := v_sql || v_source.text; FROM pmsc_source pmsc1
WHERE object_owner LIKE cp_owner
END LOOP; -- every line of code AND object_name LIKE cp_name
ORDER BY pmsc_id;
-- if lines of code were found then attempt
-- to recreate the procedure BEGIN
IF v_sql IS NOT NULL THEN
-- a simple header
EXECUTE IMMEDIATE v_sql; DBMS_OUTPUT.PUT_LINE(RPAD('ID',7) ||
RPAD('Timestamp',20) ||
END IF; RPAD('Owner',21) ||
'Name');
END restore;
-- for every source entry...
FOR v_source_rec IN curs_get_source(p_owner,p_name)
Now I’ll demonstrate how easy restoration can be. LOOP

SQL> CREATE OR REPLACE PROCEDURE simple AS -- output basic details


2 BEGIN DBMS_OUTPUT.PUT_LINE(RPAD(v_source_rec.pmsc_id,5)
3 NUKK; || ' ' ||
4 END; TO_CHAR(v_source_rec.timestamp,
5 / 'DD-MON-YYYY HH24:MI:SS')
|| ' ' ||
Warning: Procedure created with compilation errors. RPAD(v_source_rec.object_owner,20)
|| ' ' ||
SQL> EXEC pmsc.restore(USER,'SIMPLE'); RPAD(v_source_rec.object_name,20));

PL/SQL procedure successfully completed. END LOOP; -- very source entry

SQL> EXEC simple(99); END show;


You entered 99

PL/SQL procedure successfully completed. Adding restoration by number is then rather easy
because the ID for each saved version of code is readily
That was almost too easy—one single line of code to available. The restoring code is shown in Listing 3.
recover from a bad case of “Monday Fingers” or the first
day back after a holiday. Listing 3. Restoration by ID.
But what if I made dozens of successful changes
before realizing the error of my ways—how could I /*--------------------------------------------------*/
recover from that? I’m very glad you asked, because that’s PROCEDURE restore_id ( p_id NUMBER ) IS
/*--------------------------------------------------*/
just what the rest of this article is about!
-- cursor to get the owner and name for an ID
CURSOR curs_get_info ( cp_id NUMBER ) IS
Seeing what you’ve got SELECT object_owner,
Before I enable recovery of a specific version of a object_name
FROM pmsc_source
procedure, I want to be able to see what’s been saved. WHERE pmsc_id = cp_id;
This will allow me to make an educated decision v_info_rec curs_get_info%ROWTYPE;

about which code to restore. For this I’ll create a new BEGIN
procedure that uses Oracle’s DBMS_OUTPUT package for -- try to get info
display, as shown in Listing 2. Note that this listing only OPEN curs_get_info(p_id);

www.pinnaclepublishing.com Oracle Professional August 2004 9


FETCH curs_get_info INTO v_info_rec; The call from the restore by ID procedure calls the
-- if id exists then initiate restoration internal one as well but includes an ID:
IF curs_get_info%FOUND THEN

-- call existing restore procedure -- call existing restore procedure


restore(v_info_rec.object_owner, restore_internal(v_info_rec.object_owner,
v_info_rec.object_name); v_info_rec.object_name,
p_id);
END IF; -- id exists

CLOSE curs_get_info; Lastly, the cursor in the internal restore package gains
END restore_id;
some duplicity by querying with or without an ID:

-- cursor to find lines of code for the procedure


It seems easy enough—just a simple query and call to CURSOR curs_get_source ( cp_owner VARCHAR2,
an existing procedure. What could possibly go wrong? cp_name VARCHAR2,
cp_id NUMBER ) IS
Obviously something, you say, or I wouldn’t belabor the SELECT text
point. Of course something did go wrong. Remember the FROM pmsc_source_line
WHERE ( /* If ID was NOT entered */
cursor in Listing 1 that always queries the max ID? It still cp_id IS NULL AND
does. Thus the last version is always recovered regardless pmsc_id = ( SELECT MAX(pmsc_id)
FROM pmsc_source
of the ID passed in. This necessitates some changes to our WHERE object_owner = cp_owner
PMSC package. AND object_name = cp_name ) )
OR ( /* If ID was entered */
I could just change the existing restore procedure cp_id IS NOT NULL AND
from this: pmsc_id = cp_id )
ORDER BY line;

PROCEDURE restore ( p_owner VARCHAR2,


p_name VARCHAR2 ); Once the pmsc_source table starts getting large, it
might be a good idea to split the query in two to avoid
to this: repeatedly selecting maximum values when one may
not even be used. But for now, the demonstrated query
PROCEDURE restore ( p_owner VARCHAR2,
p_name VARCHAR2, will suffice.
p_id NUMBER := NULL );
Not-so-poor man
No existing calls would have to change, because the I realize the code I’ve demonstrated is quite limited
new argument has a default. It would be easy to check because it only recovers procedures. No packages, no
whether an ID was passed in and use that in the query. functions, no triggers, and so forth. It also doesn’t allow
But that would lead to a confusing API. I want to keep recovery of groups of code, focusing instead on single
things simple (there’s that word again) by cleanly procedures. That’s because the intent of this series was to
separating restoration by name and restoration by ID, demonstrate an interesting way to use DDL triggers, not
so I’ll make the following changes instead. to build a full application. If you’re inspired enough to
I’ll rename the restore procedure in the package body augment the code, I genuinely invite you to do so and
and add a parameter: share what you’ve done. ▲

PROCEDURE restore_internal ( p_owner VARCHAR2,


p_name VARCHAR2, 408HURLEY.SQL at www.pinnaclepublishing.com
p_id NUMBER := NULL )

Darryl Hurley has been working with Oracle technology for 16 years
And a new restore procedure simply calls the with a significant focus on database administration and PL/SQL
internal one: development. His days are spent as the database manager for MDSI
Mobile Data Solutions Inc (www.mdsi-advantex.com), and his spare time
/*-----------------------------------------------*/ is spent writing articles and teaching under the moniker of ImpleStrat
PROCEDURE restore ( p_owner VARCHAR2,
p_name VARCHAR2 ) IS Solutions (www.implestrat.com). He has written several articles for the
/*-----------------------------------------------*/ Oracle Development Tools User Group (www.odtug.com) and has
BEGIN
restore_internal(p_owner,p_name); contributed to several Oracle books from O’Reilly (www.ora.com).
END restore; dhurley@mdsi.ca or darryl.hurley@implestrat.com.

Know a clever shortcut? Have an idea for an article for Oracle Professional?
Visit www.pinnaclepublishing.com and click on “Write For Us” to submit your ideas.

10 Oracle Professional August 2004 www.pinnaclepublishing.com


Oracle
Professional

Oracle Enterprise Manager


10g: Not Just for “GUI DBAs”
Anymore!
Bill Burke
In this article, Bill Burke examines Oracle Enterprise Manager from the end-user interface down to the lowest level back-
10g. OEM installs out-of-the-box, configures, monitors, and end resources.
alerts with virtually no hands-on help from an administrator,
and allows DBAs to refine and tailor the tool to their needs. First exposure—the “Dirty Dozen”
In a future article, Bill will explore the new features of 10g’s Last November, I and 11 other hearty souls braved a trip
Configuration Pack and offer a productivity comparison to 9i. to Redwood Shores at the invitation of Oracle and in
partnership with IOUG to spend a week testing Oracle’s

I
started working with Oracle’s Enterprise Manager new flagship product, Oracle Database 10g. During that
suite of database management tools and utilities in the week, not only did we experience significant successes in
mid-1990s. The early releases bundled with Oracle 7 the upgrade of real production databases (more than 50 in
and 8, quite frankly, simply didn’t measure up to their total) we had brought with us, we also got our first look
third-party competition, and I for one couldn’t be at OEM 10g.
bothered with them. At the time of Oracle Enterprise Working on Solaris servers with Wintel clients,
Manager for 9i’s release, I started seeing the industry my first reaction after installing and upgrading my
raising the bar for comprehensive enterprise management production database from 8.1.7.4 directly to Oracle
solutions that touched the applications, databases, and Database 10g (a leapfrog upgrade, skipping 9i completely)
back-end components. While in my opinion the early was, “Okay, what’s next?” I just happened to have my
releases of OEM may not have met my requirements for hands on the OEM 10g CD pack, so about an hour later
an enterprise solution, the release of OEM 9i was a I was staring for the first time at the opening screen
pleasant surprise; the release of OEM 10g represents a for OEM in my browser window. I figured, that was
significant evolution in this product’s life. Over the simple enough!
coming month I’ll exercise the new features of 10g’s
Configuration Pack, and in a future issue of Oracle What’s in the box?
Professional I’ll discuss these enhancements over 9i from When the software bundle arrives, you’ll find three
a productivity perspective. components that ship as a part of OEM 10g:
Working with OEM 9i the past couple of years in • Oracle Grid—Provides for the management of grid
parallel with a competitor’s set of enterprise tools with enabled environments.
which I have extensive familiarity, I really began to see • Oracle Database Control—Management console for
the significant strides Oracle had made in the OEM your Oracle database environment.
product suite at large. But unknown to me, the best was • Oracle Application Server Control—Management
yet to come! console for your Oracle Application Server
So why am I so excited about the new OEM 10g environment.
product? OEM 10g represents what I see as the first truly
major overhaul to Enterprise Manager since its humble Management pack add-ons
beginnings. It doesn’t matter whether you prefer to work Currently, five Management packs are available for
from the command line or your browser, both options are expanding the capabilities of OEM 10g. They include:
available to you through the OEM UI and CLI interfaces. • Tuning Pack
In 9i, you had the old tree navigation system, which, • Diagnostics Pack
while functional, didn’t give the instant high-level view • Change Management Pack
you can get from the new Web-based interface. Once you • Configuration Management Pack
get under the covers with OEM 10g, you’ll find you have • Application Server Diagnostics Pack (available with
views into virtually every aspect of your environment, 10g Application Server)
www.pinnaclepublishing.com Oracle Professional August 2004 11
Simplest install yet Securing the environments through
Simply popping the CD into the CD drive and letting managed privileges
it run isn’t the end of any install with Oracle I’ve ever As has been said many times, one thing a GUI tool is
done, but I have to admit, the post-installation steps to good for is making bad things happen fast. Just as with
have an operational and functional enterprise monitoring any other tool or application with a GUI interface,
system come as close as I’ve seen. OEM 10g’s ability to appropriate protection must be taken to ensure that
automatically discover new resources is a massive time- individuals without adequate experience or training
saver in the initial setup and configuration of the product. aren’t enabled such that they’re put in a situation where
something bad can happen. Similar to what you’ve done
Tiered architecture in the past, a “Super Administrator” can establish a
The basic architecture for OEM 10g really isn’t anything grouping of privileges to be granted via roles to
new. It’s a three-tiered architecture using intelligent individual OEM administrators that covers individual
agents, a management server, and a central console. targets and specific privileges as needed or appropriate.
From the bottom up, a management agent resides on a If carefully thought through, proper implementation of
monitored “target” host. The management agent is roles and privileges will go a long way toward protecting
responsible for monitoring all services on its host, your environments.
relaying that monitoring information to the management
server, and executing any remote operations on its host. A global picture from anywhere (Central Console)
At the next level is the Management Service layer, Once the installation is complete and the necessary
which includes a Management Repository for collection processes are started, available resources must be
of all monitoring and configuration information. “discovered” for the Central Console to begin its display
The final and perhaps most important layer from of global status for your environment. As a part of the
my perspective is the Central Console. This is where the enablement of the Central Console, a default set of
rubber meets the road. This is the console you’ll log into monitoring thresholds and initial e-mail notifications
through your browser from anywhere at any time to can also be put in place, and right out of the box.
ensure that you’re meeting your service level agreements,
or to diagnose and repair issues that may impair your Web-based management
ability to meet them. Tiered views into the OEM architecture via the Personal
Home Page, Target Home Page, and Groups Home
Web-based architecture Page provide never-before-seen visibility into your
Because this is a Web-based architecture, all you need on environments.
your end is a browser and an Internet connection once
OEM is installed and configured and the discovery Personal Home Page
process is complete, and you’re ready for support. You’ll Just as with the tiered architecture that OEM resides on,
log into the Central Console from your browser over views into the OEM architecture are also tiered. When
TCP/IP, and Secure Socket Layer (SSL) will ensure that you first log into the Central Console, you’ll be presented
any messaging between OEM and you is secure. with a Personal Home Page. Each administrator’s home
page presents an overview of the environments they’re
Consistent look and feel responsible for, and includes specific information such as
One of the things you should notice immediately from instance status, pending alerts, critical patch notifications,
your browser is the intuitive, consistent look and feel and more general environment information.
of the OEM interface. Having spent many hours
working my way through menus and trees in previous Target Home Page
releases, I personally found the new browser-based UI One of the most critical items I felt was missing in OEM in
extremely friendly. the past was the ability to get an at-a-glance perspective
on the environment I was responsible for. I didn’t have
Managing the global enterprise the time or the bandwidth to individually cover every
A smooth installation and configuration and a snazzy instance that was in my environment. Because of that,
new interface does not an “Enterprise Class” application significant numbers of scripts were written, tested,
make. What does make an “Enterprise Class” monitoring packaged, and deployed across servers. On some projects,
and management application is secure access, rapid I was fortunate to have an enterprise application that
presentation of critical information, reliability of the could eliminate, or at least minimize, the number of
information presented, enablement of intelligent support, scripts I had to manage and deploy to ensure that my
and the ability to report on what’s been done and the databases and environments were up and safe.
information that’s been collected. The new UI to the Target Home Page is just what

12 Oracle Professional August 2004 www.pinnaclepublishing.com


the doctor ordered (perhaps even Dr. DBA). It gives an (CLI). Blackouts stop all alerting for a given target, a
excellent view of the current health and performance for selected group of targets, or all targets on a selected host.
the particular target, with comprehensive drill-down Blackouts can be enabled immediately, such as when an
functionality for additional information when needed. administrator has responded to and begun working on
What I like most about the Target Home Page is the an alert (STOP PAGING ME), or scheduled maintenance
overview picture that enables me to quickly see anything is being performed and you don’t want the entire team
that needs my attention immediately. It isn’t cluttered up with you all night while you reorganize a table. A
with excess information, statistics, or other data that could blackout can be triggered immediately, or scheduled for a
be rolled up and presented as a “red flag” that I can easily one-time future execution or for execution at regularly
drill down through to find the root cause of the problem. scheduled times.

Groups Home Page Management repository


In the larger environments I’ve worked in, we’ve typically Historical reporting and trending is a key component
divided the instances and their environments among of OEM 10g. The status and monitoring data is
us for core support. OEM supports this approach, or maintained in the management repository as a part of
any logical grouping approach, with a Groups Home the Management Services layer.
Page where you can include any resources that you’re
responsible for or want to include in that group. This is a Watchdogs
very effective way to create a custom group of services A monitoring system is only as good as it is reliable.
whose performance and status you want to monitor and OEM includes a watch-the-watcher system through
report on. Another approach to the use of groupings is by its Watchdogs that operate at both the target and
application system, which enables rapid alerting for Management Services layers. In the event a monitoring
administrators, timely notifications of system problems component or layer terminates for some reason, the
to help desks and affected end users, and improved appropriate Watchdog restarts the process. At an even
accuracy in estimating recovery times through extensive higher level, the system administrators are notified if
drill-downs to the underlying problem. any critical component shows signs of problems.
Another use for the grouping features is the
enablement of summary statistics for the group, which Tattletales (management agents)
can be dynamically loaded into status dashboards, system I like to think of management agents as “tattletales.” They
availability reports, or other business-specific needs, or sit on monitored targets and provide information back to
simply shown on the Groups Home Page. the Management Service layer for dissemination to the
repository for history and trending, or for alerting at the
Real-time monitoring Central Console and notification layers. Management
The Personal Home Page, Target Home Page, and agents serve other roles as well in managing launching
Groups Home Page provide visibility into your of specific jobs on the monitored target.
environments never before available in OEM. By now The addition of new resources to the environment
you should have accepted the out-of-the-box monitoring will normally require the installation and configuration
defaults, configured basic e-mail alerts, and more of new management agents on the new resource. OEM
than likely spent time tuning them to meet your provides silent installation capability to enable the
environments’ requirements. deployment of management agents for global scalability.
One of my areas of expertise and something I really
enjoy doing is real-time monitoring and performance Keeping things running
assessments, during both stress testing and day-to-day Needless to say, monitoring was a key part of the
production operations. This is where you get to see how OEM architecture and covers all components and
the users are going to abuse the best you had to give in layers from the user experience on the front end to the
performance configurations based on your knowledge, back-end components in the server and OS. The OEM
experience, and test results. core focuses on problem identification and resolution
To enable real-time monitoring in OEM, turn on auto- structure to enable a return to service as rapidly as
refreshing. When it’s enabled, you can set the interval rate possible when a problem does occur. On the other hand,
for automatic refreshing of performance data for the equally important, if not more so, is proactive monitoring
target or group being monitored. of the environment.
Starting at the application system or end-user
A solution for my No. 1 pet peeve experience, performance degradation seen at this level
Blackouts can be enabled at nearly any level from either can be indicative of an active issue at a deeper component
the Central Console or the Command Line Interface level, or predictive of trouble just waiting for a spike in

www.pinnaclepublishing.com Oracle Professional August 2004 13


load to effectively take the application down due to slow against the database. Under OEM 10g, this is taken to a
performance. The first level of performance that must be new level by dynamically monitoring a transaction
maintained is the user experience. through the tracing function. This utility allows you to
As noted previously, one area I enjoy is real-time watch the transaction performance all the way through
monitoring and performance tuning. Unfortunately, the various applications and database and server tiers,
that’s not always the best use of a DBA’s time. OEM 10g identifying bottlenecks or degradation that can then be
has made tremendous strides in this area in its proactive addressed promptly.
monitoring capabilities at the system and database
levels. Today, unattended monitoring is mandatory Summary
in any enterprise class tool, and OEM 10g has met After so many years of seeing OEM lag significantly
every requirement I’ve thrown at it. Being able to set behind the third-party providers of enterprise monitoring
monitoring thresholds at any level of the technology software, it’s great to see Oracle provide a solution that
stack has given me tremendous flexibility in developing literally installs out-of-the-box and configures, monitors,
initial “packages” of thresholds, which can be applied and alerts with virtually no hands-on help from an
to particular targets, groups of targets, or other administrator. Its intuitive interfaces, Web accessibility,
monitored resources. virtually unlimited configurability, and trend
No one wants excessive or even unnecessary alerts thresholding provide the most experienced DBAs with
being sent to their e-mail box or pager. That’s where the options to refine and tailor the tool to their needs.
trending capabilities of OEM through the Management There’s so much that’s new and powerful in Oracle
Services layer repository come into play. Metric baselines Enterprise Manager 10g, covering it all is beyond the
are snapshots of a given target’s performance at a point scope of one article. Oracle Corporation has significant
in time. Using these baselines to modify your monitoring information, white papers, and documentation, not to
thresholds enables you to “fine-tune” your thresholds mention the software for download, on its Web site. ▲
to more appropriate levels for your environments. You
Bill Burke, BCSI, is an independent consultant specializing in the design,
can also create comparison reports between different
configuration, and optimization of Oracle infrastructures. He has more
environments to assess or validate thresholds in one
than 15 years of experience as a designer, developer, and DBA for OLTP
environment against your settings for another.
and data warehouse environments. Bill is currently a member of the
It’s well known that, as hard as administrators board of directors for the International Oracle Users Group, a former
may try, much of the performance degradation (or member of the board of directors for the Oracle Development Tools User
improvement, as the case may be) is introduced at the Group, and a regular speaker at local, regional, and national training days
application level, in particular the SQL that’s executed and conferences. bill@OracleGuru.com.

Multi-level Collections... EXISTS operator (which normally helps you avoid the
possibility of raising NO_DATA_FOUND). The reason
Continued from page 7 is that EXISTS will only check for the existence of the
A position of 0 means that the item is free, a special row at the lowest level in the hierarchy. If the specified
promotional deal for which ENEL is justifiably famous. meal name doesn’t have any rows at the second level
of nesting, this RETURN statement will in fact raise a
Again, assuming that I can’t monkey around with NO_DATA_FOUND exception.
the data, I’ll have to live with these irritating “nuances.” You’ll find two other functions that encapsulate
The best way to live with them, however, is to hide them complicated, special case logic for the contents of our
behind functions. That way, you can avoid repeating the all_ingredients table in the menu_pkg.pkb file. These
awkward code you have to write to handle the rule. Even functions are then used in the show programs to fine-tune
better, when you’re finally given the opportunity to clean the display of menu information. This allows you to see
things up, you can change (hopefully simplify) the code in how they might be used.
that one function and know that your entire application is
upgraded to use the new formulation.
Listing 9. The function to determine whether an item is a
Listing 9 shows an example of such a function, to
promotional (free) tasty morsel.
handle the rule I just repeated. Basically, I need to find out
whether a row exists at position 0. That’s the indicator of FUNCTION is_promo (
a promotional item. The function itself isn’t terribly day_name_in IN all_ingredients.day_name%TYPE
,meal_name_in IN all_ingredients.meal_name%TYPE
complicated, but I certainly don’t want to hard-code that ,item_name_in IN all_ingredients.item_name%TYPE
“0” value all over my application. I include a NO_DATA_ )
RETURN BOOLEAN
FOUND exception section, even though I’m calling the IS

14 Oracle Professional August 2004 www.pinnaclepublishing.com


BEGIN the collections.
RETURN g_menu (day_name_in) (meal_name_in)
(item_name_in).EXISTS (0); I hope that the example I provide in this article
EXCEPTION
WHEN NO_DATA_FOUND
makes clear the advantages of multi-level, string-indexed
THEN collections. I certainly plan to use them wherever possible
RETURN FALSE;
END is_promo; in my future development efforts. ▲

Complicated structures, simpler code 408FEUER.ZIP at www.pinnaclepublishing.com


Nested collections and collections that use string indexes
Steven Feuerstein is considered one of the world’s leading experts
are very powerful, but also potentially produce very
on the Oracle PL/SQL language, having written nine books on PL/SQL,
bewildering structures. It’s taken me quite some time, and
including Oracle PL/SQL Programming and Oracle PL/SQL Best Practices
lots of writing and rewriting of code, to fully understand
(all from O’Reilly Media). Steven has been developing software since
the implications of these data structures. 1980 and serves as a Senior Technology Advisor to Quest Software.
I felt that my efforts paid off handsomely, however, His current projects include Qnxo (www.qnxo.com), a new active
when I discovered how the right choice of structures mentoring software product, and the Refuser Solidarity Network
could actually greatly simplify the code that I’d then (www.refusersolidarity.net), which supports the Israeli military refuser
need to write in order to manipulate the contents of movement. steven@stevenfeuerstein.com.

Tip: Deferred Constraints


Daniel Clamage

A fellow PL/SQL Pipeliner (http://pipetalk.quest-pipelines.com/ First, you’ll have to drop the constraint and re-create it
~plsql) asked how he could defer a unique key constraint until with the DEFERRABLE clause. You can also specify whether
the end of his transaction, so that he could temporarily have this deferrable state is initially on or off (INITIALLY IMMEDIATE/
two key values the same, which he would then correct before DEFERRED). Then, you can use the SET CONSTRAINT command
concluding the transaction. I suggested making the key to control this feature. For example, you might want only
constraint deferrable. He then asked how to redefine the one particular application module to have the ability to
constraint as deferred in an UPDATE statement. The correct perform a deferred transaction, and leave the constraint active
method is a bit more involved, so I responded with an example, otherwise. Here’s an example:
which I’ll demonstrate here. Continues

Don’t miss another issue! Subscribe now and save!


Subscribe to Oracle Professional today and receive a special one-year introductory rate:
Just $179* for 12 issues (that’s $20 off the regular rate)

NAME ❑ Check enclosed (payable to Pinnacle Publishing)


❑ Purchase order (in U.S. and Canada only); mail or fax copy
COMPANY
❑ Bill me later
❑ Credit card: __ VISA __MasterCard __American Express
ADDRESS
CARD NUMBER EXP. DATE

CITY STATE/PROVINCE ZIP/POSTAL CODE


SIGNATURE (REQUIRED FOR CARD ORDERS)

COUNTRY IF OTHER THAN U.S.


Detach and return to:
Pinnacle Publishing ▲ 316 N. Michigan Ave. ▲ Chicago, IL 60601
E-MAIL Or fax to 312-960-4106

* Outside the U.S. add $30. Orders payable in


408INS
PHONE (IN CASE WE HAVE A QUESTION ABOUT YOUR ORDER) U.S. funds drawn on a U.S. or Canadian bank.

Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106

www.pinnaclepublishing.com Oracle Professional August 2004 15


CREATE TABLE blick (num NUMBER, str VARCHAR2(1)); The DDL to change between deferred and immediate will
Table created.
have to be done using Native Dynamic SQL (NDS).
ALTER TABLE blick
ADD CONSTRAINT pk_blick PRIMARY KEY (num)
BEGIN
DEFERRABLE INITIALLY IMMEDIATE;
EXECUTE IMMEDIATE 'SET CONSTRAINT pk_blick DEFERRED';
Table altered.
INSERT INTO blick (num, str)
INSERT INTO blick (num, str) VALUES (3, 'C');
VALUES (1, 'A');
1 row created. INSERT INTO blick (num, str)
VALUES (3, 'D');
INSERT INTO blick (num, str)
VALUES (1, 'B'); UPDATE blick
ORA-00001: unique constraint (PPDEV.PK_BLICK) violated SET num=4
WHERE str='D';
SET CONSTRAINT pk_blick DEFERRED;
Constraint set. EXECUTE IMMEDIATE 'SET CONSTRAINT pk_blick IMMEDIATE';
COMMIT;
INSERT INTO blick (num, str) END;
/
VALUES (1, 'B');
1 row created.
Notice that I resume constraint checking before issuing the
UPDATE blick
SET num=2 COMMIT. If the constraint will be violated by my transaction, I
WHERE str='B'; want to know it before committing the changes. ▲
1 row updated.

COMMIT; Dan Clamage has been working with Oracle and especially PL/SQL
Commit complete.
SET CONSTRAINT pk_blick IMMEDIATE; since 1995. He lives in Pittsburgh, PA, with his wife, Robin, and their two
Constraint set. daughters, Patricia and Daniele. danielj@clamage.com.

August 2004 Downloads


• 408FEUER.ZIP—Source code to accompany Steven • 408HURLEY.SQL—Source code to accompany
Feuerstein’s article, “Cutting Through Nasty Design Darryl Hurley’s article, “Poor Man’s Source Control,
with Multi-level Collections.” Part 2.”

For access to current and archive content and source code, log in at www.pinnaclepublishing.com.

Editor: Garry Chan (gchan@procaseconsulting.com) Oracle Professional (ISSN 1525-1756)


Contributing Editor: Bryan Boulton is published monthly (12 times per year) by:
CEO & Publisher: Mark Ragan
Pinnacle Publishing
Group Publisher: Michael King A Division of Lawrence Ragan Communications, Inc.
Executive Editor: Farion Grove 316 N. Michigan Ave., Suite 300
Chicago, IL 60601
Questions?
POSTMASTER: Send address changes to Lawrence Ragan Communications, Inc., 316
Customer Service: N. Michigan Ave., Suite 300, Chicago, IL 60601.

Phone: 800-493-4867 x.4209 or 312-960-4100 Copyright © 2004 by Lawrence Ragan Communications, Inc. All rights reserved. No part
Fax: 312-960-4106 of this periodical may be used or reproduced in any fashion whatsoever (except in the
Email: PinPub@Ragan.com case of brief quotations embodied in critical articles and reviews) without the prior
written consent of Lawrence Ragan Communications, Inc. Printed in the United States
of America.
Advertising: RogerS@Ragan.com
Oracle, Oracle 8i, Oracle 9i, PL/SQL, and SQL*Plus are trademarks or registered trademarks of
Editorial: FarionG@Ragan.com Oracle Corporation. Other brand and product names are trademarks or registered trademarks
of their respective holders. Oracle Professional is an independent publication not affiliated
Pinnacle Web Site: www.pinnaclepublishing.com with Oracle Corporation. Oracle Corporation is not responsible in any way for the editorial
policy or other contents of the publication.

Subscription rates This publication is intended as a general guide. It covers a highly technical and complex
subject and should not be used for making decisions concerning specific products or
applications. This publication is sold as is, without warranty of any kind, either express or
United States: One year (12 issues): $199; two years (24 issues): $348 implied, respecting the contents of this publication, including but not limited to implied
Other:* One year: $229; two years: $408 warranties for the publication, performance, quality, merchantability, or fitness for any particular
purpose. Lawrence Ragan Communications, Inc., shall not be liable to the purchaser or any
Single issue rate: other person or entity with respect to any liability, loss, or damage caused or alleged to be
caused directly or indirectly by this publication. Articles published in Oracle Professional
$27.50 ($32.50 outside United States)* reflect the views of their authors; they may or may not reflect the view of Lawrence Ragan
Communications, Inc. Inclusion of advertising inserts does not constitute an endorsement by
* Funds must be in U.S. currency. Lawrence Ragan Communications, Inc., or Oracle Professional.

16 Oracle Professional August 2004 www.pinnaclepublishing.com

You might also like