Collections and Records

PLSQL offers you an option how to store variables within an array; collections and records. They can hold values that can be treated as standalone values.

Collections‘ elements must always be of the same data type. They are accessible by its unique subscript.

Records, on the other hand, can have different data types and you can access those fields by their name. We usually store there a whole row from the table or selected columns (depends on the need).

Types of collections

  • Nested tables
    • can have from 1 up to 2147483647 number of elements
    • can be created in a PL/SQL block or at a schema level (meaning you can use it outside of your block as well if defined outside of the block)
  • Varrays (or variable-size arrays)
    • can have from 1 up to 2147483647 number of elements (you have to define it in a declaration)
    • can be created in a PL/SQL block or at a schema level (meaning you can use it outside of your block as well if defined outside of the block)
  • Index-by tables (or associative arrays)
    • can have from -2147483648 up to 2147483647 number of elements
    • in case of the string, the limit is the same as it is for VARCHAR2
    • can be created only in a PL/SQL block
  • Multidimensional arrays

Even though collections have only one dimension, you can easily create a multidimensional collection with elements of a collection type. In other words, a collection that will store multiple other collections.

Nested tables

Nested tables are very similar to arrays but there are some differences you have to keep in mind. First of all, you don’t have to define the number of elements while defining a nested table unlike with array, where you have to know how many elements are you going to store. With nested tables, you can dynamically change the number of stored elements.

Another difference is, that arrays are always dense. That means all values are in consecutive order and one value is followed by another until the end of the array. With nested tables, you can delete an element in the middle and there will be an empty space left – that means they are sparse.

plsql_collections_nested_table

See the example of a nested table.

-- create a collection
CREATE OR REPLACE TYPE members AS TABLE OF VARCHAR2(30);

-- create a nested table with that collection
CREATE TABLE nested_table (id_member NUMBER, member_list members)
       NESTED TABLE member_list STORE AS member_list_tab;
       
-- add some values       
INSERT INTO nested_table VALUES (1, members('Admin'));
INSERT INTO nested_table VALUES (2, members('Editor', 'CEO'));
INSERT INTO nested_table VALUES (3, members('CIO', 'Secretary', 'HelpDesk'));
INSERT INTO nested_table VALUES (4, members('Assistant'));
COMMIT;


-- check the data (all we can see that there is a collection stored but we don't see the actual values)
SELECT *
FROM nested_table


/* output
ID_MEMBER   |   MEMBER_LIST
-----------------------------
1	    |  <Collection>
2	    |  <Collection>
3	    |  <Collection>
4	    |  <Collection>
*/

-- unnesting the table (looping through each value)
SELECT * 
FROM nested_table t1, TABLE(t1.member_list) t2;

/* output
ID_MEMBER	 |	MEMBER_LIST	 |	COLUMN_VALUE
--------------------------------------------------------------
1	 	 |	<Collection>	 |	Admin
2	 	 |	<Collection>	 |	Editor
2	 	 |	<Collection>	 |	CEO
3	 	 |	<Collection>	 |	CIO
3		 |	<Collection>	 |	Secretary
3		 |	<Collection>	 |	HelpDesk
4		 |	<Collection>	 |	Assistant

*/

Index-by tables

Index-by tables (sometimes referred to as associative arrays as well) are tables holding paired values; a key and its value. All values are accessible via its unique key. If you use the key which does not exist, it will create value in the table. However, if you try to define it again with a different value it will overwrite the existing one (update). See the example demonstrating how index-by tables work.

DECLARE 
   -- define the index_by table
   TYPE members IS TABLE OF VARCHAR2(100)
       INDEX BY BINARY_INTEGER;
   
   -- create the instance    
   oracleWorldMembers members;
   
   -- just a helping variable (must be of the same data type as the INDEX BY)
   x BINARY_INTEGER;
   
BEGIN
  
   -- adding values into the table
   OracleWorldMembers(1) := 'Admin';
   OracleWorldMembers(2) := 'Moderator';
   OracleWorldMembers(3) := 'Editor';
   OracleWorldMembers(100) := 'Guest';
   OracleWorldMembers(23) := 'CEO';
   OracleWorldMembers(13) := 'Assistant';
   
   -- updating value with the defined subscript
   OracleWorldMembers(2) := 'Janitor';
   
   -- get the first element
   x := OracleWorldMembers.FIRST;
   
   -- loop through the table
   WHILE x IS NOT NULL LOOP
     
      dbms_output.put_line(OracleWorldMembers(x)||' has id '||x||'.');
      
      -- get next element from the table
      x := OracleWorldMembers.NEXT(x);
     
   END LOOP;
   
   EXCEPTION
     WHEN OTHERS THEN
         dbms_output.put_line('Ooops!');
   
END;
/



------------------------
Admin has id 1.
Janitor has id 2.
Editor has id 3.
Assistant has id 13.
CEO has id 23.
Guest has id 100.

As you can see, the output is sorted, no matter in what order you insert the records 🙂

Varrays

Varrays has a definite number of elements varying from zero (empty) to the maximum size. Access to the values is done via subscripts. Varrays are always dense! See the example below.

DECLARE
  -- varray definition
  TYPE t_varray IS 
                VARRAY(5) -- number of elements
               OF 
                NUMBER(10); -- data type of elements

  -- create the instance   
  v_items  t_varray;
  
  -- just a helping variable
  x   NUMBER;
  
BEGIN
  
  -- init the varray
  v_items := t_varray(1, 2);

  -- add extra values
  FOR i IN 150 .. 152 LOOP
    v_items.extend; -- extend (mandatory first)
    v_items(v_items.last) := i;  -- assign element
  END LOOP; 

  -- get the first value from varray
  x := v_items.FIRST;
  
  -- loop through the varray
  WHILE x IS NOT NULL LOOP
    
    dbms_output.put_line(v_items(x));
    
    -- get next element from the table
    x := v_items.NEXT(x);
    
  END LOOP;
   
   EXCEPTION
     WHEN OTHERS THEN
         dbms_output.put_line('Ooops!');

END;
/

Collections methods

There are multiple methods allowing you access, insert, modify, or delete data in collections.

  • EXISTS
    • returns true if the given element exists
    • usage EXISTS(x)
  • COUNT
    • returns the number of elements existing at the moment in a collection
  • LIMIT
    • in case of nested tables, it returns NULL because there is no limit set
    • in case of varrays, it returns the maximum limit of elements it can be stored
  • FIRST
    • returns the first element in the collection
  • LAST
    • returns the last element in the collection
  • PRIOR
    • returns the previous element in the collection from the given subscript
    • PRIOR(x) will return the element that precedes the element with index x
    • if there is no element, it returns NULL
  • NEXT
    • returns the following element in the collection from the given subscript
    • NEXT(x) will return the element that succeeds the element with index x
    • if there is no element, it returns NULL
  • EXTEND – there are three ways of usage
    • EXTEND – will add a NULL element at the end of the collection
    • EXTEND(x) – will add x-times NULL element at the end of the collection
    • EXTEND(x,y) – will add x-times element with index y at the end of the collection
  • TRIM
    • cannot be used with index-by tables
    • with no parameter, it will remove the last element from the collection
    • with a parameter -> TRIM(x), it will remove X last parameters from the collection
      • if the X is higher than COUNT (number of elements) it will return an error SUBSCRIPT_BEYOND_COUNT
  • DELETE – there are three ways of usage
    • DELETE – removes the last element in the collection
    • DELETE(x) – removes the element with index x in the collection
    • DELETE(x,y) – removes all elements in an index range between x and y. If x is greater than y than nothing happens.

Records

I will show you a few examples of records usage at the end of this section. Records might be pretty handy when you need to use a little more complicated structures.

-- simple record
DECLARE
   TYPE t_recOrders IS RECORD (
      id_order NUMBER(4) NOT NULL := 99, -- if NOT NULL, you always have to define a default value
      name_product    products.name_product%TYPE,
      code_type       action_codes%ROWTYPE );
   orders_rec t_recOrders;
BEGIN
   orders_rec.name_product := 'Laptop';
END;
/
-- record as a parameter
DECLARE
   TYPE t_OraWorldMmbr IS RECORD (
      id            NUMBER(6),
      flag_locked   NUMBER );
   PROCEDURE unlock_user (member t_OraWorldMmbr) IS
   BEGIN

      UPDATE member 
      SET flag_locked = 0
      WHERE id_member = member.id;

   END unlock_user;
BEGIN
    -- call the procedure or/and do something else
END;
/