19.4 Using CollectionsThere are three main programming tasks you must understand when you are working with collections in PL/SQL:
In addition, to fully exploit the programming utility of collections, you will want to learn how to retrieve and store sets of data with them. This leads into our section on pseudo-functions, which allow you to perform magic tricks with collections. (Okay, maybe it's not real magic, but you're almost guaranteed to say "How did they do that?" the first time you try to program this stuff and find yourself bewildered.) 19.4.1 Initializing Collection VariablesWith an index-by table datatype, initialization is a non-issue. Simply declaring an index-by table variable also initializes it, in an "empty" state. Then you can just assign values to subscripted table elements as you desire. Index values (subscripts) can be almost any positive or negative integer. A program can even assign subscripts to index-by tables arbitrarily, skipping huge ranges of subscripts without paying a memory or performance penalty.[ 2 ]
The allocation scheme for nested tables and VARRAYs is different from that of index-by tables. First off, if you don't initialize one of these collections, it will be "atomically null," and any attempt to read or write an element of an atomically null collection will generate a runtime error. For example:
You must initialize the collection before using it. There are three ways you can initialize a collection:
There is no requirement that you initialize any particular number of elements in a collection. Zero, one, or more are fine, and you can always add more values later. In particular, don't be confused by VARRAYs. Just because you specify a limit on the number of elements it can hold does not imply that you have to put that many elements in when you initialize it. 19.4.1.1 Initializing with a constructorEarlier, we saw declarations that looked like this:
Color_tab_t( ) is the constructor function supplied by Oracle when we created the Color_tab_t collection type. This function accepts an arbitrary number of arguments, as long as each argument is of the "proper" datatype -- which in this case is VARCHAR2(30), since our original type definition statement was the following: CREATE TYPE Color_tab_t AS TABLE OF VARCHAR2(30) ; At initialization, Oracle allocates to the variable an amount of memory necessary to hold the values you supply as arguments. Initialization both creates the "slots" for the elements and populates them. So, if I want to "fix" the earlier invalid example, I can simply initialize the variable:
What do you suppose Oracle does with the following initialization? working_colors Color_tab_t := Color_tab_t(); This is a way of creating an "empty" collection. Empty is a sort of enigmatic state in which the collection is not atomically null but still has no data. Whenever you create such an empty collection, you'll need to "extend" the collection variable later when you want to put elements into it. (The EXTEND built-in is explored later in this chapter.) 19.4.1.2 Initializing implicitly during direct assignmentYou can copy the entire contents of one collection to another as long as both are built from the exact same datatype. When you do so, initialization comes along "for free." Here's an example illustrating implicit initialization that occurs when we assign wedding_colors to be the value of earth_colors.
This code initializes wedding_colors and creates three elements that match those in earth_colors. These are independent variables rather than pointers to identical values; changing the third element of wedding_colors to 'CANVAS' does not have any effect on the third element of earth_colors. Note that assignment is not possible when datatypes are merely "type-compatible." Even if you have created two different types with the exact same definition, the fact that they have different names makes them different types. A collection variable cannot be assigned to another variable of a different datatype:
This code will fail with the compile-time error "PLS-00382: expression is of wrong type," because r_color and l_color are of different types. 19.4.1.3 Initializing implicitly via fetchIf you use a collection as a type in a database table, Oracle provides some very elegant ways of moving the collection between PL/SQL and the table. As with direct assignment, when you use FETCH or SELECT INTO to retrieve a collection and drop it into a collection variable, you get automatic initialization of the variable. Collections can turn out to be incredibly useful! Although we mentioned this briefly in an earlier example, let's take a closer look at how you can read an entire collection in a single fetch. First, we want to create a table containing a collection and populate it with a couple of values:
Now we can show off the neat integration features. With one trip to the database we can retrieve all of the values of the "colors" column for a given row, and deposit them into a local variable:
With SERVEROUTPUT turned on, SQL*Plus prints the following when this code fragment executes: 1 RED 2 GREEN 3 BLUE Pretty neat, huh? A few important points to notice:
You can also make changes to the contents of the nested table and just as easily move the data back into a database table. Just to be mischievous, let's create a Fuschia-Green-Blue color model:
19.4.1.4 VARRAY integrationDoes this database-to-PL/SQL integration work for VARRAYs too? You bet, although there are a couple of differences. First of all, realize that when you store and retrieve the contents of a nested table in the database, Oracle makes no promises about preserving the order of the elements. This makes sense, because the server is just putting the nested data into a store table behind the scenes, and we all know that relational databases don't give two hoots about row order. By contrast, storing and retrieving the contents of a VARRAY does preserve the order of the elements. Preserving the order of VARRAY elements is actually a fairly useful capability. It makes possible something you cannot do in a pure relational database: embedding meaning in the order of the data. For example, if you want to store someone's favorite colors in rank order, you can do it with a single VARRAY column. Every time you retrieve the column collection, its elements will be in the same order as when you last stored it. By contrast, abiding by a pure relational model, you would need two columns, one for an integer corresponding to the rank, and one for the color. Thinking about this order-preservation of VARRAYs brings to mind some interesting utility functions. For example, you could fairly easily code a tool that would allow the insertion of a new "favorite" at the low end of the list by "shifting up" all the other elements. A second difference between integration of nested tables and integration of VARRAYs with the database is that some SELECT statements you could use to fetch the contents of a nested table will have to be modified if you want to fetch a VARRAY. (See Section 19.5 later for some examples.) 19.4.2 Assigning Values to Elements: Index (Subscript) ConsiderationsIn contrast to index-by tables, you can't assign values to arbitrarily numbered subscripts of nested tables and VARRAYs; instead, the indexes, at least initially, are monotonically increasing integers, assigned by the PL/SQL engine. That is, if you initialize n elements, they will have subscripts 1 through n. And, as implied above, you cannot rely on the assignment of particular subscripts to particular element values in nested tables. Yes, any element can be null, but null is different from nonexistent (sparse). Nested tables are initially dense, with no skipped subscripts. Once a nested table exists, however, it is possible to remove any element from it, even one in the "middle." This will result in a sparse array. VARRAYs, on the other hand, are always dense. Elements of VARRAYs can only be removed from the "end" of the array, so VARRAYs cannot be coerced into being sparse. However, if what you want is a sparse array in PL/SQL, you would be much better off using an index-by table. The real strength of nested tables and VARRAYs is their ability to move gracefully in and out of the database. 19.4.3 Adding and Removing ElementsWith an old-style index-by table, Oracle automatically allocates memory for new elements. When you want to add an element, you simply pick a value for the subscript and assign a value to that element. To remove an element, you could use the DELETE method. To illustrate:
What would happen if you tried this assignment with a nested table? Aha, you say, knowing that subscripts start with 1 and are monotonically increasing, I'll just try: DECLARE /* colors starts life initialized by empty */ colors Color_tab_t := Color_tab_t(); BEGIN colors(1) = 'SEAFOAM GREEN'; -- invalid But this code produces an "ORA-06533, Subscript beyond count" error. This is why you need EXTEND. 19.4.3.1 Adding elements using EXTENDAdding an element to a collection requires a separate allocation step. Making a "slot" in memory for a collection element is independent from assigning a value to it. If you haven't initialized the collection with a sufficient number of elements (null or otherwise), you must first use the EXTEND procedure on the variable. (For the formal syntax and usage of this procedure, refer to Section 19.6, "Collection Built-Ins" .) DECLARE /* The colors variable begins life initialized but with || no elements allocated */ colors Color_tab_t := Color_tab_t(); BEGIN colors.EXTEND; -- allocate space for a single element colors(1) := 'GRANITE'; -- this works colors(2) := 'HUNTER GREEN'; -- invalid; we only extended by 1 END; 19.4.3.2 Removing elements using DELETEWhen you DELETE an element, PL/SQL removes that element from the collection. Interestingly, it doesn't actually remove all traces of the element; in fact, a placeholder gets left behind. That means you can reassign the element later without having to re-allocate the space. DELETE has three different forms depending on how much you want to remove: one, several (contiguous), or all of the elements. Section 19.6 describes all the forms of this procedure. In physical terms, PL/SQL actually releases the memory only when your program deletes a sufficient number of elements to free an entire page of memory (unless you DELETE all of the elements, which frees all of the memory immediately). This de-allocation happens automatically and requires no accommodations or devices in your code. 19.4.3.3 ...And what about TRIM?TRIM is another built-in which lets you remove elements from a collection; it's equally applicable to nested tables and VARRAYs. (Again, refer to Section 19.6 for details.) As its name implies, TRIM drops elements off the end of a collection. Unlike DELETE, TRIM leaves no placeholder behind when it does its work. Although my programming exercise above didn't need TRIM, this built-in, combined with EXTEND, can be very useful if you want to program a "stack" abstraction. In general, the syntax is simply the following: collection_name .TRIM( n ); where n is the number of elements to remove. If you don't supply n , it defaults to 1. Unfortunately, if you use TRIM and DELETE on the same collection, you can get some very surprising results. Consider this scenario: if you DELETE an element at the end of a nested table variable and then do a TRIM on the same variable, how many elements have you removed? You would think that you have removed two elements, but, in fact, you have removed only one. The placeholder that is left by DELETE is what TRIM acts upon.
19.4.4 Comparing CollectionsUnfortunately, there is no built-in capability to compare collections and determine whether one is "equal to" or "greater than" the other. Attempts to do so will produce compile-time errors. The only comparison that is legal for collections is a test for nullness, as we saw previously: DECLARE cool_colors Color_tab_t; BEGIN IF cool_colors IS NULL THEN -- valid; will be TRUE If comparing collections is important to your application, you could put an object "container" around the collection, and use objects instead of collections as the structure that your applications manipulate. Doing so allows you to define your own object comparison semantics. Although Chapter 18 provides a detailed discussion of defining your own object comparisons using the MAP and ORDER methods, we'll divert momentarily to illustrate how this technique will help us compare collections. Without repeating a lot of descriptive detail that you'll find in Chapter 18 , your object type specification might look quite simple: CREATE TYPE Color_object_t AS OBJECT ( crayons Color_array_t, ORDER MEMBER FUNCTION compare(c_obj Color_object_t) RETURN INTEGER); This creates an object with a single attribute, crayons, and a special method that Oracle will use when it needs to compare instances of type Color_object_t. The object type body could be implemented as follows:
In PL/SQL, you can now compare objects of type Color_object_t to your heart's content, achieving a kind of de facto collection comparison:
And if you needed this structure as a column in a table, it could go something like this: CREATE TABLE kids_coloring_kits ( NAME VARCHAR2(30), crayon_colors Color_object_t ); Once the table is populated, you can then use SQL sorting features such as ORDER BY, GROUP BY, and DISTINCT on the crayon_colors column, since your ORDER member function tells Oracle how to compare values in the column.
Copyright (c) 2000 O'Reilly & Associates. All rights reserved. |
|
|