Professional Documents
Culture Documents
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.
);
Figure 2.
Producing a set
of encapsulation
packages.
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;
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.
put the code back the way it was... now, how was that -- cursor to find lines of code for the procedure
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);
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:
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.
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
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
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
Pinnacle, A Division of Lawrence Ragan Communications, Inc. ▲ 800-493-4867 x.4209 or 312-960-4100 ▲ Fax 312-960-4106
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.
For access to current and archive content and source code, log in at www.pinnaclepublishing.com.
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.