PLSQL Chapter
PLSQL Chapter
OBJECT-RELATIONAL
ORACLE8 AND PL/SQL8
“Civilization advances by extending the number of important operations
which we can perform without thinking about them.”
Alfred North Whitehead
◆ Collections
◆ Summary
319
320 Chapter 11
Chapters 8 and 9 presented an overview of the traditional SQL and PL/SQL lan-
guages as they have existed and been used with the Oracle Server database before
the release of version 8. This release pushed the Oracle Server technology in three
major directions:
This chapter will focus on the major concepts introduced by Oracle8 and its
versions of SQL and PL/SQL to support the object-relational technology.
As discussed in Chapter 9, the relational model was defined in the 1970s and
commercial applications of it started being implemented in the 1980s. Through-
out that decade and in the early to mid-1990s, relational databases grew up and
hosted a large percentage of the data corporations cared about. The simplicity of
the relational world (expressed in a short list of steps known as the normaliza-
tion rules), the straightforward implementations of SQL by all the database ven-
dors, and a number of data modeling and database design tools helped IT pro-
fessionals put order in their data universe and build robust relational database
applications.
The relational model, as defined and described in its latest standard (SQL2),
considers the data to be organized in fields and records. The data stored in each
of these fields may be described by a data type from a (short) list of predefined
data types. By the mid-1990s, an increasing number of IT professionals began
realizing that what was considered a virtue of the relational model—its simplic-
ity—was quickly becoming a liability. The fast-paced improvements in hard-
ware, software, and communication technologies enabled corporations to
address much more complex structures and relations in their data. The relation-
al model by itself was no longer optimal for storing and handling this complex-
Object-Relational Oracle8 and PL/SQL8 321
ity. There were two alternatives from which system designers and application
developers could choose in order to address the shortcomings of the relational
databases:
Clearly, none of these approaches was very satisfying. By the mid-1990s, all the
stakeholders in the relational world, including database vendors, standards’ com-
mittees, designers, and developers, agreed that the relational model and SQL had to
be extended with object-oriented features. In Oracle8, the Oracle Corporation intro-
duced a set of features that enabled designers and developers to natively implement
and support objects in the Oracle Server. These features are collectively known as
the Objects Option of Oracle8 and are grouped in the following categories:
❏ Object types. They are also known as user-defined or abstract data types.
Object types allow you to represent the structure (attributes) and the behav-
ior (methods) of entities as they appear in the real world.
❏ References. They are handles to instances of object types, which allow you to
represent the relations among objects in a way that optimizes the storage of
these objects.
❏ Collections. They allow you to group data in an ordered or unordered way
for efficient and optimized access. These are two categories of collections in
Oracle8 Objects Option: variable-length arrays, often abbreviated as
VARRAYs, and nested tables.
❏ Object storage. Oracle8 with Objects Option allows you not only to define
objects from the previous three categories, but also to store them in the Oracle
Server. Extensions to SQL allow you to manipulate these objects just like the
predefined data types. Furthermore, object views allow you to build object
definitions upon purely relational structures and are very important when
migrating applications into the object-relational world.
In the remainder of this chapter, each of these categories of features will be dis-
cussed in detail. From now on, the expression Oracle8 means Oracle8 with Objects
Option, unless otherwise noted.
322 Chapter 11
The world around us is made up of objects which we see, touch, and interact with
constantly. These objects are materialized in the form of a number of attributes
and operations you perform with them. A simple object like a door has attributes
such as weight, material, color, dimensions, and price. Some operations you can
perform on a door are open, close, mount, lock, and break. Depending on who
you are and your intentions, you may be interested in different attributes of
doors and perform different operations on them. If you are a homeowner consid-
ering purchasing a door for your bedroom, you may want to know its dimensions
and price, as well as how to open, close, or lock it. But, if you are a contractor
hired to install a door, you need to know its weight and material, as well as how
to mount it.
One of the major advantages that object-oriented languages brought to soft-
ware engineering is the ability to model, design, and build applications using the
familiar object paradigm with which we all are familiar. These languages allow
you to distinguish between the actual objects and the templates that describe the
structure and methods of a group of objects. In a number of object-oriented lan-
guages, like C++ and Java, these templates are called object classes, or simply class-
es. Class instances created and manipulated during the life of the application are
the actual objects with which users interact.
In Oracle8, the templates that describe the attributes and methods of a catego-
ry of objects are called object types. They are defined using DDL commands and
considered part of the schema objects defined in the Oracle Server data dictionary.
The actual objects are called instances of the object type and can be stored tempo-
rarily in PL/SQL program units or persistently in the Oracle Server database struc-
tures. Object instances can be created and maintained using SQL DML commands
and PL/SQL syntactic structures.
In a sense, PL/SQL and Oracle have offered similar constructs even before
Oracle8. In fact any language offers a number of “object types” called data types.
For example, the PL/SQL data types VARCHAR2 or NATURAL define the
attributes of their instance variables, such as alphanumeric strings no longer than
2000 characters or positive integers. They also define the allowed operations on
each of these variables—NATURAL numbers can be added and multiplied,
whereas VARCHAR2 strings cannot. Complex data types in PL/SQL, such as
records and index-by tables discussed in the previous chapter, allow you to extend
the predefined scalar data types with complex data types that are associated with
a number of predefined methods, like NEXT and PRIOR in the case of index-by
tables. Finally, packages in PL/SQL have for a long time allowed the association
Object-Relational Oracle8 and PL/SQL8 323
❏ Support for abstract data types (ADT). The object types created in Oracle8
can be of any level of complexity. They may contain a large number of
attributes (up to 1000) and methods. Each of these attributes may be of a pre-
defined or another abstract data type.
❏ Centralized storage of ADT definitions. By storing the definitions and prop-
erties of the object types as part of the schema, Oracle8 enables you to access
and use them at any level in your application architecture. You can use them
at the database level, or through PL/SQL at the application layer. Further-
more, mechanisms provided by other Oracle Corporation’s tools, such as
Designer/2000, allow you to derive class definitions in languages such as
C++ from object type definitions stored in the Repository.
The statement shown in Figure 11.2 creates the corresponding body of the
object type point_t.
On the other hand, an attribute may be assigned any user-defined object type
stored in the Oracle Server schema. This mechanism allows you to create complex
objects with attributes defined as other nested objects. The statements shown in
Figure 11.3 contain the specification and body for the object type circle_t. As
you can see from this example, the center of the circle is an object of type point_t
defined at the end of the previous section.
The object type definitions shown so far contain only one simple method.
However, methods are not required to define an object type. In this case, the body
of the type definition is not required and the object type resembles a structure in
languages like C. Obviously, the benefits of encapsulating data with operations are
realized when the attributes are followed by a number of interesting methods in
the object type definition. The statement contained in Figure 11.4 extends the def-
inition of the object type point_t with a number of methods that implement oper-
ations with point on the Real plane.
Object-Relational Oracle8 and PL/SQL8 325
FIGURE 11.3 A simple example of a nested object type definition and its implementation.
The corresponding body of this object type is shown in Figure 11.5. From the
contents of this figure, you can see that the methods of an object type are defined
and implemented just like regular functions and procedures in a PL/SQL pack-
age. In particular, the specification of the method resides within the object type
specification, whereas its implementation resides in the object type body. Fur-
thermore, methods can be overloaded as in the example of the methods GetDis-
tance. Based on the arguments passed to these methods, a client application is
able to compute the distance of a point from the origin of the coordinate system
or from another point on the plane. Similarly, the first method GetMidPoint
allows you to compute the coordinates of the point in the middle of the segment
that joins the given point with the origin of coordinates. The second method com-
putes the midpoint of the segment that connects the point with any other point
on the plane.
326 Chapter 11
The implementations of the methods for the object type point_t shown in
Figure 11.5 highlight a few special characteristics of them, which are described in
the following list.
By default, PL/SQL allows you to compare two instances of the same object
type in the sense that it can tell you whether these instances are equal or not. In
addition, you can also add the ability to sort these objects just as you sort objects of
basic data types, such as DATE, NUMBER, or VARCHAR2. In order for Oracle to
be able to sort object instances, the object type must contain either one of the fol-
lowing two methods:
328 Chapter 11
❏ MAP method. This method is a function that maps the object instance to a
hash value, which is then used to sort the object. The data type of the return
value of the map function can be DATE, NUMBER, or VARCHAR2. The key-
word MAP should precede the mapping function in the object type definition
and body. Figure 11.6 shows the object type point_t discussed earlier in
which the function GetDistance is defined as a MAP function. Given the
implementation of this function, you can easily see that point objects will be
ordered based on their distance from the origin of the coordinate system.
❏ ORDER method. An alternative way to sort objects is to define a method that
compares the object with another object. This method is preceded by the key-
word ORDER and returns a negative number, zero, or a positive number if
the object whose method is invoked is less than, equal, or greater than the
input object. Figure 11.7 shows the definition of the object type point_t
with the ORDER method Compare. Figure 11.8 shows the simple, one-line
implementation of this method. When sorting a set of objects using the
ORDER method, Oracle needs to compare them individually. For large sets,
this may degrade the performance of the operation; therefore you should
consider using the MAP function.
FIGURE 11.8 The implementation of the ORDER method Compare of the object type point_t.
The discussion so far could apply to any language that supports objects. How-
ever, what is special about the Object Option in Oracle8 is that object instances
may be closely associated with objects stored in the database. Therefore, the meth-
ods defined in the object type could be part of SQL statements like SELECT,
INSERT, or UPDATE. In order for these statements to be executed successfully,
the member methods have to follow a small number of rules aimed at limiting
their references to and dependency from other schema objects, like tables and
stored packages. For example, a method cannot be invoked from a SQL statement
if it inserts, updates, or deletes data from one or more tables. You can enlist the
PL/SQL compiler to help you define and implement such methods. The pragma
RESTRICT_REFERENCES indicates to the compiler a method and the level of
restrictions placed on it. When the compiler compiles this method, it uses the prag-
ma definition to verify that the method is not violating any of the rules defined by
the pragma. Figure 11.9 shows how you can inform the compiler that the function
GetArea should Read No Database State (RNDS), Write No Database State
(WNDS), Read No Package State (RNPS), and Write No Package State (RNPS).
Let us revisit now the object type circle_t defined in Figure 11.3 earlier in
the chapter. This object contains an object of type point_t that describes the cen-
ter of the circle. Suppose now that you are developing an application that deals
with a large number of concentric circles, that is, circles that have a common point
on the plane as their center. There are two disadvantages with the current defini-
tion of circle_t. First, for each circle, you will create a point_t object instance
to hold the coordinates of the center. Obviously, you will consume memory loca-
tions to store identical object instances for these circles. Second, suppose your users
want to move the center of these circles to a new point on the plane. You applica-
tion will have to assign the new coordinates to the center objects of each circle.
It is clear that what you need here is a way to create only one instance for the
center point and refer to it from all the circles that need it. This way you will not
store multiple instances of the same object and you will have only one object to
maintain. In PL/SQL, references or pointers to objects are called refs. Figure 11.11
shows the revised definition of the object type circle_t in which the attribute
circle is a reference to an object of data type point_t.
Object-Relational Oracle8 and PL/SQL8 331
For each object instance created, Oracle generates a universally unique identifi-
er, called the Object Identifier, or simply OID. Attributes or variables that reference
another object are assigned the OID of an object instance. The PL/SQL block shown
in Figure 11.12 declares one point_t object A, a reference to it A_ref, and two
circle_t objects C1 and C2. The first statement of the block stores the reference to
the object A in the variable A_ref. Then the point is initialized to the origin of coor-
dinates. After that, two circles are initialized. These circles share the same origin
pointed at by the OID of the center point. Their radii are 10 and 20 units, respectively.
DECLARE
A point_t := point_t(NULL, NULL);
A_ref REF point_t;
C1 circle_t := circle_t(NULL, NULL);
C2 circle_t := circle_t(NULL, NULL);
BEGIN
SELECT DEREF(A_ref) INTO A FROM DUAL;
A := point_t(0, 0);
C1 := circle_t(A_ref, 10);
C2 := circle_t(A_ref, 20);
END;
As the example shown in Figure 11.12 hints, the OID of an object is not directly
accessible. Similarly, the reverse operation, that is getting the actual object refer-
enced by the OID, is not yet available in PL/SQL. A workaround to both these
problems is to use the SQL operator DEREF. This operator must be used in a
SELECT statement and returns the value of the object in a predefined variable.
Typically, the SELECT statement is issued against the dummy table DUAL as in
Figure 11.12.
Figure 11.13 expands the PL/SQL block presented in Figure 11.12 in the form
of a procedure that takes as arguments the coordinates of two points on the plane
(X1, Y1, X2, and Y2). First, the procedure initializes the circles C1 and C2 to the
point (X1, Y1). Then, the center of the objects is moved to the point (X2, Y2). Finally,
the centers of the circles are dereferenced and their coordinates are printed in the
message console of the Developer/2000 Forms application.
332 Chapter 11
The examples used here have been very simple to allow you to focus on the
concepts rather than on implementation details. They are provided in the file
CH11_SQL of the companion CD-ROM. However, the real power of concepts such
as referencing and dereferencing is realized only when large and complex objects
are stored persistently in database structures. The following section covers details
on this topic and shows you how to insert and manipulate objects instances using
SQL statements.
In the examples shown in the previous section, the object types you defined were
used as data types for attributes of other objects or as data types for PL/SQL vari-
ables. Oracle8 allows you to use these object types as data types for columns in a
table object. It also allows you to create tables that will store instances of a defined
data type. Figure 11.14 shows the definition of two object types, movie_t and
address_t, from the MRD application. Figure 11.15 shows three tables that use
these definitions. The table MOVIES_OBJ is an object table that stores instances of
movie_t. The table CUSTOMERS_OBJ is like the other tables you have seen so
far, except that one of its columns, ADDRESS, is of data type address_t. And
Object-Relational Oracle8 and PL/SQL8 333
finally, the table RENTALS_OBJ contains the column MOVIE_OID that references
a movie stored in the object table MOVIES. The statements that create these
objects and populate them with data are provided in file CH11_2.SQL on the com-
panion CD-ROM.
FIGURE 11.14 Two object type definitions from the MRD application.
FIGURE 11.15 Database table objects that use object type definitions.
334 Chapter 11
There are two operators that are closely related to the use of objects in SELECT
statements. They are VALUE and REF. The first operator returns the value of an
object. The second operator returns the reference to a particular object instance.
Recall from the discussion about refs in Section 11.2.3 that the SQL operator
DEREF is used to return the object pointed to by a reference value. The statements
shown in Figure 11.16 use each of these operators. The first one uses VALUE to
select an object from the table MOVIES_OBJ with a given ID. The second one uses
REF to return the OID of the movie object retrieved by the first statement. And the
last statement returns the same object but this time by dereferencing the OID
returned by the second statement.
DECLARE
m_obj movie_t;
m_ref REF movie_t;
BEGIN
SELECT VALUE(m)
INTO m_obj
FROM movies_obj m
WHERE ID = '121';
SELECT REF(m)
INTO m_ref
FROM movies_obj m
WHERE ID = '121';
SELECT DEREF(m_ref)
INTO m_obj
FROM DUAL;
END;
As you can see from the second statement, the default constructor method
provided by Oracle must be used in order to insert data in a columns that contains
object instances. If the table is a table of objects, the constructor is not required, as
the first example shows.
When inserting object references, you may find the RETURNING clause of
the INSERT statement very handy. This clause returns the reference to the newly
inserted object to a PL/SQL variable. This reference can then be used for further
processing without the need to query the database for it. Figure 11.17 shows a
PL/SQL anonymous block that inserts an object in the table MOVIES and then
records a rental transaction for the new object in the table RENTALS.
DECLARE
movie_ref REF movie_t;
BEGIN
INSERT INTO movies_obj m
VALUES (movie_t(123, 'Speed', NULL, NULL, 'Sandra Bullock',
'PG-13'))
RETURNING REF(m) INTO movie_ref;
You can also insert objects in an object table that are returned by a query.
Assuming that a table called R_MOVIES has been created, the following statement
inserts in this table all the movies rated “R” from the object table MOVIES_OBJ.
336 Chapter 11
Notice in this statement the operator VALUE which returns the value of the objects
retrieved by the subquery. Alternatively, you could have used the familiar
SELECT * form of the subquery.
UPDATE customers_obj c
SET c.address.zip = '22153'
WHERE c.ID = 21;
UPDATE customers_obj
SET address = address_t('123 Commerce Street',
'Springfield', 'VA', '22153')
WHERE ID = 21;
Notice however that the table RENTALS_OBJ may contain records which ref-
erence the movie you just deleted in the second statement above. The record insert-
ed in the statements of Figure 11.17 is one example. For object references, Oracle
does not enforce the same referential integrity mechanism similar to the foreign
key constraints defined in the relational model. Instead, it leaves the reference dan-
gling. Since, dangling references always cause errors and unexpected exceptions
at runtime, you should be careful to nullify all the references to an object that you
Object-Relational Oracle8 and PL/SQL8 337
delete. Thus, the following UPDATE statement should append the DELETE state-
ment shown above:
UPDATE rentals_obj
SET movie_OID = NULL
WHERE movie_OID IS DANGLING;
11.4 Collections
Chapter 10 discussed index-by tables as a data type that allows you to introduce
array-like structures in you PL/SQL program units. Index-by tables were intro-
duced in version 2 of PL/SQL that came along with Oracle7. Oracle8 expands your
ability to define customized tabular structures by introducing nested tables and
variable-size arrays (VARRAYs). The features of these collection types will be the
subject of this section. In the remainder of this section the term varray will denote
an object of type VARRAY.
❏ You need to define a collection (nested table or varray) type before initializ-
ing any instances of these collections. However, while object types can be
defined only using DDL statements and are stored as part of the schema, col-
lection types may be defined as part of the schema as well as in any PL/SQL
program unit. Figure 11.18 shows the DDL statements to create a varray and
nested table type in the Oracle8 schema. Figure 11.19 shows how you can
define the same types in the DECLARE section of a PL/SQL block.
❏ Oracle8 allows you to initialize a collection using a constructor method sim-
ilar to the one used to initialize object instances. Figure 11.19 shows examples
of how you declare two collection variables and initialize them.
❏ Collection types may be data types of PL/SQL variables as well as object
attributes and table columns. Figure 11.18 shows a revised definition of the
object movie_t introduced in Figure 11.14. In the new object type movies_t
338 Chapter 11
the attributes DIRECTOR, ACTOR, and ACTRESS are of the varray data type
people_t. This allows you to store up to ten names in each of these
attributes rather than just one as in the previous definition.
FIGURE 11.18 Defining nested table and varray types with DDL scripts.
DECLARE
TYPE people_t IS VARRAY(10) OF VARCHAR2(30);
TYPE rental_tab_t IS TABLE OF rental_t;
As you will see in the following section, the similarities between nested tables
and varrays extend beyond the ones listed above. They include the way you work
with them and the methods defined for each collection type. However, there are
also important differences between nested tables and varrays. You need to be
aware of them in order to select the appropriate collection in a given situation. The
following is a list of these differences.
❏ For nested tables, the upper bound of the subscript is practically unlimited
(232 - 1) whereas the upper bound of varrays is fixed at the time of the type
declaration.
❏ Varrays are always compact, with no gaps between their elements, whereas
nested tables, like index-by tables, allow you to delete elements from them
without compacting the entire structure.
❏ When stored in database structures, varray data are located in the same data-
base block as the data from the other fields of the table record. The nested
table data on the other hand are stored in a separate table (hence the term
nested table) whose records reside in separate data blocks and may be locat-
ed in a different tablespace.
❏ The varray elements are maintained as a block at the varray level. This
means that in order to select, insert, update, or delete an element in the
varray, you have to perform the operation on the entire structure, carrying
with you all the other elements as well. Nested tables on the other hand offer
more flexibility and granularity. They allow you to access and manipulate
individual records within the table just as you would do with records in a
regular Oracle table.
Finally, the following are a few differences between nested tables and index-
by tables:
❏ The subscript of nested tables is a positive number ranging from 1 to (232 - 1),
whereas the subscript of index-by tables may be anywhere in the range -(232 - 1)
to (232 - 1), including negative integers.
❏ Index-by tables are intended to be used in PL/SQL program units and cannot
be manipulated with SQL statements. In nested tables, on the other hand,
you can use the SQL commands, like SELECT, INSERT, UPDATE, and
DELETE, as you would do with a standard Oracle table.
❏ There is a list of data types that elements of nested tables cannot have but are
allowed for index-by tables. These include BOOLEAN, LONG, LONG RAW,
and BINARY_INTEGER.
❏ Index-by tables are unbounded in the sense that you can store elements in
them anywhere in the range of their subscript. Nested tables have a lower
340 Chapter 11
bound because their subscript cannot be smaller than 1. Although they have
no upper bound, you need to extend them until they include or surpass the
desired value of the subscript before storing an element in that position. For
example, to store the first element in a nested table, you need to extend it at
least by one and then store the element in the first position in the table.
❏ Typically, nested tables are compact initially and may become sparse as you
delete elements from them. Index-by tables on the other hand are sparse to
begin with and may be filled in certain regions with elements.
Two situations may arise when you work with collections stored in the data-
base. The first one is when you consider them as “black-boxes” and manipulate
them like any other data fields in the table record. Figure 11.21 shows a PL/SQL
block with operations from this scenario. In the statements shown in this figure,
you insert a movie in the database, update its ACTRESS column, and then select
the new record from the database.
DECLARE
actorspeople_t := people_t('Tom Cruise', 'Ed Harris');
actressespeople_t := people_t('Holly Hunter',
'Jeanne Tripplehorn');
rental_trx rental_tab_t := rental_tab_t(
rental_t(212, '02-SEP-98', NULL, 0.99),
rental_t(213, '03-SEP-98', NULL, 0.99),
rental_t(541, '06-SEP-98', NULL, 1.49));
movie_idNUMBER;
BEGIN
INSERT INTO movies_table (id, title, actor, rentals)
VALUES(189, 'The Firm', actors, rental_trx);
The most interesting situation is the one in which you want to access the col-
lection at a more granular level and work with its individual elements. For exam-
ple, each time a new rental transaction occurs, you will want to insert a new record
in the nested table stored in the column RENTALS of the movie in question. When
the movie is returned, you want to update the transaction record with the return
date; you may also want to see the number of all the transactions generated for a
movie on a given date; and finally you may want to delete a transaction from the
nested table. Figure 11.22 shows a PL/SQL block with statements that manipulate
the records in the nested table along these lines.
Notice that these statements differ from the regular statements you issue
against an Oracle table only by the way you define the table name. Normally in
clauses like INSERT INTO <table>, UPDATE <table>, SELECT … FROM
<table>, or DELETE <table>, you specify the name of a given table from the
schema. When the statements are issued against a nested table, you need to use the
342 Chapter 11
operand THE to specify the table object. This operand works with a subquery
which must return exactly one nested table object. In the examples shown in Figure
11.22, this subquery returns the nested table stored in the column RENTALS of the
movie record with ID 189.
DECLARE
trx_countNUMBER;
BEGIN
INSERT INTO THE ( SELECT rentals FROM movies_table WHERE
id=189)
VALUES ( rental_t(543, '07-SEP-98', NULL, 1.49));
SELECT COUNT(*)
INTO trx_count
FROM THE ( SELECT rentals FROM movies_table WHERE id=189)
WHERE rent_dt = '07-SEP-98';
When the differences between nested tables and varrays were listed in the pre-
vious section, it was mentioned that nested tables allow you to manipulate their
elements individually but varrays do not. In the examples shown so far in this sec-
tion, you have seen how easy it is to select, insert, update, or delete a single element
from nested tables. The programming is more involved if you want to perform
similar tasks with varray columns. The following is a list of steps you need to fol-
low to accomplish these tasks:
1. Retrieve the varray object into a PL/SQL variable of the same type. If you
intend to modify the value of any elements within the object, place a database
lock on the record.
2. If you are planning to insert a new element, extend the collection.
3. Loop through all the varray elements until you find the element you want to
select, update, or delete, or until you find the place where you want to insert
the new element.
4. Perform the desired operation with the selected element.
5. Update the table with the new value of the varray object.
Object-Relational Oracle8 and PL/SQL8 343
The PL/SQL block shown in Figure 11.23 shows how you could follow these
steps in order to add an actor to the list of actors in the movie “The Firm” created
in Figure 11.21.
DECLARE
actors people_t;
j INTEGER;
BEGIN
SELECT actor INTO actors FROM movies_table WHERE id = 158
FOR UPDATE OF actor;
actors.EXTEND(2);
j := actors.LAST - 1;
actors(j) := 'Gene Hackman';
actors(j + 1) := 'Hal Holbrook';
The Objects Option of Oracle8 allows you to define abstract data types that encap-
sulate data attributes with operations on those attributes. It also extends the previ-
ous support for collections by introducing nested tables and variable-size arrays.
This chapter highlights the major features of objects and collections. Its main topics
included:
❏ Collections
❏ Overview of Collections
❏ Working with Collections
344 Chapter 11
The following table describes the software assets provided in the companion
CD-ROM that were discussed in this chapter. From the main page of the CD-ROM
follow the links Software and Chapter 11 to access these assets: