Oracle Collections


Oracle Collections

Collections are powerful structures because they enable you to develop programs that manage large sets of data in memory. You can build collections of any SQL and PL/SQL data type. Some collections only work in PL/SQL where others work in both SQL and PL/SQL.

There are three types of collections that Oracle uses

Varray
They are densely populated arrays (no gaps in the index sequence) and behave like traditional programming arrays. They can be accessed by either SQL or PL/SQL, at creation they have a fixed size which cannot be changed.
Varrays use sequential integers for their subscripts (index), however the subscript starts from 1 not 0.
You would use a varray when the physical size of the collection is static and the collection may be used in tables. These are like the traditional arrays in other programming languages.
Nested Tables
They are initially defined as densely populated arrays but become sparsely populated when records are deleted. They can be accessed by either SQL or PL/SQL; they can also be dynamically extended.
Nested Tables use sequential integers for their subscripts (index), however the subscript starts from 1 not 0.
You would use nested tables when the physical size is unknown due to run-time variations and when the type may be used in tables. These are like the traditional lists or bags in other programming languages.
Associative Array
They are sparsely populated arrays which mean the numbering does not have to be sequential, only unique. The subscript supports both unique integers and strings, they can grow dynamically but they can only be accessed via PL/SQL.
Associative arrays use sequential or non-sequential integers or strings for their subscript.
You would use associative arrays when the physical size is unknown due to run-time variations and when the type will not be used in tables. These are like the traditional sets or maps in other programming languages.



With the release of Oracle 7, Oracle introduced the PL/SQL Table. By using PL/SQL Tables, it was possible to create a collection of items, all of the same type, indexed by an integer.

TYPE book_title_tab IS TABLE OF book.title%TYPE INDEX BY BINARY_INTEGER;

The only way to access the elements of a PL/SQL Table was through its numeric index. Still, it was the first construct that gave PL/SQL developers array-like access to data.
PL/SQL tables were often combined with PL/SQL Records. By creating a PL/SQL Record, developers could define a composite type that allowed you to group items of varying type together. Combining PL/SQL Tables and Records together was often referred to as a 'PL/SQL Table of Records'.

--Define a PL/SQL record type representing a book:

TYPE book_rec IS RECORD
   (title                   book.title%TYPE,
    author                  book.author_last_name%TYPE,
    year_published published_date.%TYPE));

--define a PL/SQL table containing entries of type book_rec:

Type book_rec_tab IS TABLE OF book_rec%TYPE INDEX BY BINARY_INTEGER;

my_book_rec  book_rec%TYPE;
my_book_rec_tab book_rec_tab%TYPE;
...
...
my_book_rec := my_book_rec_tab(5);
find_authors_books(my_book_rec.author);
...
...
In version 8, Oracle introduced two collection types, Nested Tables and Varrays. At this time, the PL/SQL Table was renamed to 'index-by table'. As of Oracle 9i, PL/SQL Tables (index-by tables) have again been renamed to Associative Arrays. The Associative Array functions much the same way the PL/SQL Table of old did. However, the Associative Array does contain some enhanced functionality, as we will see.

Varrays

The Varray is short for Variable Array. A Varray stores elements of the same type in the order in which they are added. The number of elements in a Varray must be known at the time of its declaration. In other words, a Varray has a fixed lower and upper bounds, making it most similar to collection types from other programming languages. Once it is created and populated, each element can be accessed by a numeric index.

The following statements declare, and then populate, a Varray that will contain 4 elements of the same type as the column genre_name in table book_genre:

DECLARE
    TYPE genres IS VARRAY(4) OF book_genre.genre_name%TYPE;
    Fiction_genres genres;
BEGIN
    fiction_genres := genres('MYSTERY','SUSPENSE', 'ROMANCE','HORROR');
END;

We could have declared genres to be of type VARCHAR2(30) because all values here are text. However, in keeping with good Oracle programming practices, you should always prefer to declare variables that are based on table columns with the %TYPE attribute. This allows your code to grow with the database schema. If we were to populate genres with a variable like v_genre (versus a text literal), it would be easy for the column type to change in the database without modifying our code.

All PL/SQL collections contain a number of built-in methods that prove useful when working with them. Table 1 lists these Collection methods.

Method
Action It Performs
COUNT
Returns number of elements in the Collection
EXISTS
Returns Boolean true if element at specified index exists; otherwise, false
EXTEND
Increases size of Collection by 1 or number specified, ie. EXTEND(n)
**Cannot use with Associative Array
FIRST
Navigates to the first element in the Collection
LAST
Navigates to the last element
PRIOR
Navigates to the previous element
NEXT
Navigates to the next element
TRIM
Removes the last element, or the last n elements if a number is specified, ie. TRIM(n)
**Cannot use with Associative Array
DELETE
Removes all elements of a Collection, or the nth element, if a parameter is specified

The following code sample demonstrates how to use a few of these methods. We are using a Varray in the example, but the methods function similarly on all collection types. We mentioned that a Varray differs from Nested Tables and Associative Arrays in that you must supply a size during its declaration. This example usese the EXTENDS method to demonstrate that it is possible to modify a Varray's size programmatically.

--Add a new genre.
IF adding_new_genre  THEN

     --Is this genre id already in the collection?
     IF NOT fiction_genres.EXISTS(v_genre_id)     THEN
       --**Add** another element to the varray.
       fiction_genres.EXTENDS(1);
       fiction_genres(v_genre_id) := v_genre;
   END IF;
    --Display the total # of elements.
    DBMS_OUTPUT.PUT_LINE('Total # of entries in fiction_genres is :
                         '||fiction_genres.COUNT();

END IF;
...
...
--Remove all entries.
IF deleting_all_genres THEN
     Fiction_genres.DELETE();
END IF;

The advantage that Varrays (and Nested Tables) have over Associative Arrays is their ability to be added to the database. For example, you could add the genres type, a Varray, to a DML statement on the library table.

CREATE TYPE genres IS VARRAY(4) OF book_genre.genre_name%TYPE;
/
CREATE TABLE book_library (
    library_id    NUMBER,
    name          VARCHAR2(30),
    book_genres   genres);
/

When a new library record is added, we can supply values to our genres type, book_genres, by using its constructor:

--Insert a new collection into the column on our book_library table.

INSERT INTO book_library (library_id, name, book_genres)
  VALUES (book_library_seq.NEXTVAL,'Brand New Library',
          Genres('FICTION','NON-FICTION', 'HISTORY',
                 'BUSINESS AND FINANCE'));

The query SELECT name, book_genres from book_library returns us:
NAME                  BOOK_GENRES
--------------------  ---------------------------------------------
Brand New Library     GENRES('FICTION', 'NON-FICTION', 'HISTORY',
                      'BUSINESS AND FINANCE')

Note how the insertion order of elements in book_genres is retained. When a table contains a Varray type, its data is included in-line, with the rest of the table's data. When a Varray datatype is selected from a database table, all elements are retrieved. The Varray is ideal for storing fixed values that will be processed collectively. It is not possible to perform inserts, updates, and deletes on the individual elements in a Varray. If you require your collection to be stored in the database but would like the flexibility to manipulate elements individually, Nested Tables are a better solution.

Nested Table

Nested Tables, like the Varray, can be stored in a relational table as well as function as a PL/SQL program variable. The syntax for declaring a Nested Table is similar to the syntax for declaring the traditional PL/SQL Table. Let's rework our earlier example using a Nested Table. First, you declare your type:
CREATE TYPE genres_tab IS TABLE OF book_genre.genre_name%TYPE;
/
The 'IS TABLE OF' syntax was also used when declaring a PL/SQL Table. However, this declaration omits the 'INDEX BY BINARY_INTEGER' clause required by the former type. Note that we have not specified the size of the collection. This is because Nested Tables, unlike the Varray, require no size specification. In other words, they are unbound. Here is a definition for the book_library database table, which now contains a Nested Table column:
CREATE TABLE book_library (
    library_id     NUMBER,
    name           VARCHAR2(30),
    book_genres_tab genres_tab)
    NESTED TABLE book_genres_tab STORE AS genres_table;
/

As stated earlier, a Varray's contents are stored in the same table as the other columns' data (unless the collection is exceedingly large, then Oracle stores it in a BLOB, but still within the same tablespace). With Nested Tables, a separate database table will store the data. This table is specified following the 'STORE AS' clause. If a database table has more than one Nested Table type, the same storage table will store data for all the Nested Tables on that parent table. These storage tables contain a column called NESTED_TABLE_ID that allows the parent table to reference a row's nested table data.
--Insert a record into book_library, with a Nested Table of book genres.
INSERT INTO book_library (library_id, name, book_genres_tab)
  VALUES (book_library_seq.NEXTVAL,'Brand New Library',
          genres_tab('FICTION','NON-FICTION', 'HISTORY', 'BUSINESS AND FINANCE'));
/

--Declare a nested table type
DECLARE
  updated_genres_tab genres_tab;
BEGIN
  updated_genres_tab :=
    genres_tab('FICTION','NON-FICTION','HISTORY','BUSINESS AND FINANCE',
               'SCIENCE','PERIODICALS','MULTIMEDIA');

  --Update the existing record with a new genres Nested Table.
  UPDATE book_library
    SET book_genres_tab = updated_genres_tab;

END;
/

These examples show an insert and an update to the book_table and are similar to what you might see if you were working with a Varray. Both Nested Tables and Varrays allow you to use SQL to select individual elements from a collection. However, Nested Tables have an advantage over Varrays in that they allow for inserts, updates, and deletes on individual elements. The Varray type does not because Varray data is stored as one single, delimited piece of data within the database.

To operate on collection elements, use the TABLE command. The TABLE command operator informs Oracle that you want your operations to be directed at the collection, instead of its parent table.

--1.)Select all genres from library 'Brand New Library' that are like
     '%FICTION%'.
SELECT column_value FROM TABLE(SELECT book_genres_tab
                               FROM book_library
                               WHERE name = 'Brand New Library')
   WHERE column_value LIKE '%FICTION%';
/

COLUMN_VALUE
------------------------------
FICTION
NON-FICTION

--2.)Update entry 'MULTIMEDIA' to a new value.  Only possible with
     a nested table!!
UPDATE TABLE(SELECT book_genres_tab
             FROM book_library
             WHERE name = 'Brand New Library')
  SET column_value   = 'MUSIC AND FILM'
  WHERE column_value = 'MULTIMEDIA';


--3.)Select all book_genre_tab entries for this library.
SELECT column_value FROM TABLE(SELECT book_genres_tab
                               FROM book_library
                               WHERE name = 'Brand New Library');

COLUMN_VALUE
------------------------------
FICTION
NON-FICTION
HISTORY
BUSINESS AND FINANCE
SCIENCE
PERIODICALS
MULTIMEDIA

The first of the preceding three statements simply selects an individual element from the Nested Table, book_genres_tab, in our book_library database table. The second statement performs an update on an individual element, something possible only with Nested Tables. The last query shown selects all Nested Table elements from the parent table. This demonstrates an important feature of the TABLE operator. An ealier query we performed on a database column of type Varray returned a single comma-delimited list of values ('FICTION', 'NON-FICTION', 'HISTORY', 'BUSINESS AND FINANCE'). Using TABLE allows you to 'unnest' a collection and display its elements as you would a database table's results, top down.

Associative Arrays

Associated Arrays are single dimesional structures which can be used in programming structures. They can only be accessed in PL/SQL, they have a different approach to Varrays and Nested tables
Do not require initialization and have no constructor syntax, they do not need to allocate space before assign values (no extend method).

Can be indexed numerically or use unique variable-length strings.

Can use any integer as index value which means any negative, positive or zero whole number
Are implicty converted from equivalent %rowtype, record type and object type return values to associative array structures.

Are the key to using the forall statement or bulk collect clause, which enables bulk tranfers of records from a database table to a programming unit.

You cannot store an associative array directly in the database, so you need logic to store and retrieve the values in this type of collection with the database, it also cannot be use in SQL.

TYPE book_title_tab IS TABLE OF book.title%TYPE
    INDEX BY BINARY_INTEGER;
book_titles   book_title_tab;

The statement defines a collection of book titles, accessible by a numeric index. Although it is feasible to locate an element by its numeric index, the limitation to this approach is that the value we have to search by is often not an integer.

SELECT title FROM book;

TITLE
------------------------------
A Farewell to Arms
For Whom the Bell Tolls
The Sun Also Rises

Above are values from the title column of the book table. If we needed to remove an entry, given only the book title, we would have to search the entire collection in a somewhat inefficient manner.

The following is code illustrates this:

   FOR cursor_column IN  book_titles.FIRST..book_titles.LAST LOOP
       IF book_titles(cursor_column) = 'A Farewell to Arms' THEN
          book_titles.DELETE(cursor_column);
       END IF;
   END LOOP;

With Associative Arrays, it is now possible to index by the title of the book. In fact, there are numerous different indexing options, including by VARCHAR2, using the %TYPE keyword, and more.

This is a improvement over indexing everything by an integer then having to shuffle through entries to find what you're looking for. Now, if we want to remove the book A Farewell to Arms, we can use an Associative Array:

DECLARE
   TYPE book_title_tab IS TABLE OF book.title%TYPE
       INDEX BY book.title%TYPE;
   book_titles book_title_tab;
BEGIN
      book_titles.DELETE('A Farewell to Arms');
END;

By using an Associative Array of book.title%TYPE, we accomplish our mission in one line, without the need to loop through the set. The main drawback to the Associative Array type is that, like the PL/SQL Table type before it, you are not able to store them in the database. They are strictly for internal use in PL/SQL applications. If this is all you require of a collection, the Associative Array's indexing flexibility make it a good choice.

When to Use What

If you're new to PL/SQL collections, you may have a fair understanding of their mechanics by this point, but are uncertain when to use a particular type. Table 2 summarizes each collection's capabilities.

Has Ability To
Varray
Nested Table
Associative Array
be indexed by non-integer
No
No
Yes
preserve element order
Yes
No
No
be stored in database
Yes
Yes
No
have elements selected individually in database
Yes
Yes
--
have elements updated individually in database
Yes
No
--

In addition, the following bullet points can be referred to when deciding what collection best suits a particular solution.

Varray

Use to preserve ordered list
Use when working with a fixed set, with a known number of entries
Use when you need to store in the database and operate on the Collection as a whole

Nested Table

Use when working with an unbounded list that needs to increase dynamically
Use when you need to store in the database and operate on elements individually

Associative Array

Use when there is no need to store the Collection in the database. Its speed and indexing flexibility make it ideal for internal application use.


Comments