Collections in Oracle PL/SQL: o o o o o o o o o o o
Collections in Oracle PL/SQL: o o o o o o o o o o o
Collections in Oracle PL/SQL: o o o o o o o o o o o
Oracle uses collections in PL/SQL the same way other languages use arrays. Oracle
provides three basic collections, each with an assortment of methods.
o CARDINALITY Function
o POWERMULTISET Function
o POWERMULTISET_BY_CARDINALITY Function
o SET Function
Multidimensional Collections
Related articles.
v_tab table_type;
v_idx NUMBER;
BEGIN
-- Initialise the collection.
<< load_loop >>
FOR i IN 1 .. 5 LOOP
v_tab(i) := i;
END LOOP load_loop;
The number 1
The number 2
The number 4
The number 5
SQL>
The number 1
The number 2
The number 4
The number 5
SQL>
Varray Collections
A VARRAY is similar to a nested table except you must specifiy an upper bound in the
declaration. Like nested tables they can be stored in the database, but unlike nested
tables individual elements cannot be deleted so they remain dense.
SET SERVEROUTPUT ON SIZE 1000000
DECLARE
TYPE table_type IS VARRAY(5) OF NUMBER(10);
v_tab table_type;
v_idx NUMBER;
BEGIN
-- Initialise the collection with two values.
v_tab := table_type(1, 2);
-- Traverse collection
v_idx := v_tab.FIRST;
<< display_loop >>
WHILE v_idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE('The number ' || v_tab(v_idx));
v_idx := v_tab.NEXT(v_idx);
END LOOP display_loop;
END;
/
The number 1
The number 2
The number 3
The number 4
The number 5
SQL>
Extending the load_loop to 3..6 attempts to extend the VARRAY beyond it's limit of 5
elements resulting in the following error.
DECLARE
*
ERROR at line 1:
ORA-06532: Subscript outside of limit
ORA-06512: at line 12
The following example shows a successful assignment between two collections of the
same type.
DECLARE
TYPE table_type IS TABLE OF NUMBER(10);
v_tab_1 table_type;
v_tab_2 table_type;
BEGIN
-- Initialise the collection with two values.
v_tab_1 := table_type(1, 2);
-- Assignment works.
v_tab_2 := v_tab_1;
END;
/
SQL>
If we repeat that, but this time use two separate types with similar definitions, we can
see the code fails to compile due to the illegal assignment.
DECLARE
TYPE table_type_1 IS TABLE OF NUMBER(10);
TYPE table_type_2 IS TABLE OF NUMBER(10);
v_tab_1 table_type_1;
v_tab_2 table_type_2;
BEGIN
-- Initialise the collection with two values.
v_tab_1 := table_type_1(1, 2);
v_tab_2 := v_tab_1;
*
ERROR at line 11:
ORA-06550: line 11, column 14:
PLS-00382: expression is of wrong type
ORA-06550: line 11, column 3:
PL/SQL: Statement ignored
SQL>
Collections of the same type can be tested for equality, as shown in the example below.
SET SERVEROUTPUT ON
DECLARE
TYPE table_type IS TABLE OF NUMBER(10);
v_tab_1 table_type;
v_tab_2 table_type;
BEGIN
-- Initialise the collection with two values.
v_tab_1 := table_type(1, 2);
v_tab_2 := v_tab_1;
SQL>
Collection Methods
A variety of methods exist for collections, but not all are relevant for every collection
type.
MULTISET Operations
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5,6);
l_tab2 t_tab := t_tab(5,6,7,8,9,10);
BEGIN
l_tab1 := l_tab1 MULTISET UNION l_tab2;
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5,6);
l_tab2 t_tab := t_tab(5,6,7,8,9,10);
BEGIN
l_tab1 := l_tab1 MULTISET UNION DISTINCT l_tab2;
SQL>
The NOT keyword can be included to get the inverse. For example NOT MULTISET UNION.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5,6,7,8,9,10);
l_tab2 t_tab := t_tab(6,7,8,9,10);
BEGIN
l_tab1 := l_tab1 MULTISET EXCEPT l_tab2;
SQL>
The NOT keyword can be included to get the inverse. For example NOT MULTISET EXCEPT.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5,6,7,8,9,10);
l_tab2 t_tab := t_tab(6,7,8,9,10);
BEGIN
l_tab1 := l_tab1 MULTISET INTERSECT l_tab2;
MULTISET Conditions
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_null_tab t_tab := NULL;
l_empty_tab t_tab := t_tab();
l_set_tab t_tab := t_tab(1,2,3,4);
l_not_set_tab t_tab := t_tab(1,2,3,4,4,4);
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_null_tab t_tab := NULL;
l_empty_tab t_tab := t_tab();
l_not_empty_tab t_tab := t_tab(1,2,3,4,4,4);
SQL>
MEMBER Condition
The MEMBER condition allows you to test if an element is member of a collection.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5);
BEGIN
DBMS_OUTPUT.put('Is 3 MEMBER OF l_tab1? ');
IF 3 MEMBER OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is 3 MEMBER OF l_tab1? TRUE
SQL>
SUBMULTISET Condition
The SUBMULTISET condition returns true if the first collection is a subset of the second.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5);
l_tab2 t_tab := t_tab(1,2,3);
l_tab3 t_tab := t_tab(1,2,3,7);
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,3,4,5);
l_tab2 t_tab := t_tab(1,2,3);
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is l_tab2 SUBMULTISET OF l_tab1? TRUE
SQL>
Having duplicate values in the subset results in false, if those duplicates are not present
in the main set.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5);
l_tab2 t_tab := t_tab(1,2,3,3);
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is l_tab2 SUBMULTISET OF l_tab1? FALSE
SQL>
If we add the duplicates into both the main set and the subset, it returns true.
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5,3);
l_tab2 t_tab := t_tab(1,2,3,3);
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is l_tab2 SUBMULTISET OF l_tab1? TRUE
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5);
l_tab2 t_tab := t_tab();
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is l_tab2 SUBMULTISET OF l_tab1? TRUE
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab();
l_tab2 t_tab := t_tab();
BEGIN
DBMS_OUTPUT.put('Is l_tab2 SUBMULTISET OF l_tab1? ');
IF l_tab2 SUBMULTISET OF l_tab1 THEN
DBMS_OUTPUT.put_line('TRUE');
ELSE
DBMS_OUTPUT.put_line('FALSE');
END IF;
END;
/
Is l_tab2 SUBMULTISET OF l_tab1? TRUE
SQL>
MULTISET Functions
CARDINALITY Function
The CARDINALITY function returns the number of elements in the collection, similar to
the COUNT method, but it is available from SQL.
SELECT CARDINALITY(tab1)
FROM (SELECT t_number_tab (1, 2, 3, 4) AS tab1
FROM dual);
CARDINALITY(TAB1)
-----------------
4
SQL>
SELECT tab1
FROM (SELECT t_number_tab(1, 2, 3, 4) AS tab1
FROM dual)
WHERE CARDINALITY(tab1) = 4;
TAB1
--------------------------------------------------------------------------------
T_NUMBER_TAB(1, 2, 3, 4)
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,5);
BEGIN
DBMS_OUTPUT.put_line('COUNT = ' || l_tab1.COUNT);
DBMS_OUTPUT.put_line('CARDINALITY = ' || CARDINALITY(l_tab1));
END;
/
COUNT = 5
CARDINALITY = 5
SQL>
POWERMULTISET Function
The POWERMULTISET function accepts a nested table and returns a "nested table of nested
tables" containing all the possible subsets from the original nested table.
SELECT *
FROM TABLE(POWERMULTISET(t_number_tab (1, 2, 3, 4)));
COLUMN_VALUE
----------------------------------------
T_NUMBER_TAB(1)
T_NUMBER_TAB(2)
T_NUMBER_TAB(1, 2)
T_NUMBER_TAB(3)
T_NUMBER_TAB(1, 3)
T_NUMBER_TAB(2, 3)
T_NUMBER_TAB(1, 2, 3)
T_NUMBER_TAB(4)
T_NUMBER_TAB(1, 4)
T_NUMBER_TAB(2, 4)
T_NUMBER_TAB(1, 2, 4)
T_NUMBER_TAB(3, 4)
T_NUMBER_TAB(1, 3, 4)
T_NUMBER_TAB(2, 3, 4)
T_NUMBER_TAB(1, 2, 3, 4)
SQL>
POWERMULTISET_BY_CARDINALITY Function
The POWERMULTISET_BY_CARDINALITY function is similar to the POWERMULTISET function, but
it allows us to limit the output to just those subsets that have the specified cardinality. In
the following example we return only those subsets that have a cardinality of 2.
SELECT *
FROM TABLE(POWERMULTISET_BY_CARDINALITY(t_number_tab (1, 2, 3, 4), 2));
COLUMN_VALUE
----------------------------------------
T_NUMBER_TAB(1, 2)
T_NUMBER_TAB(1, 3)
T_NUMBER_TAB(1, 4)
T_NUMBER_TAB(2, 3)
T_NUMBER_TAB(2, 4)
T_NUMBER_TAB(3, 4)
SQL>
SET Function
The SET function returns a collection containing the distinct values from a collection.
BASIC_OUT SET_OUT
CARD_OUT CARD_SET
----------------------------------- -----------------------------------
---------- ----------
T_NUMBER_TAB(1, 2, 3, 4, 4, 4) T_NUMBER_TAB(1, 2, 3, 4)
6 4
SQL>
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF NUMBER;
l_tab1 t_tab := t_tab(1,2,3,4,4,4);
BEGIN
DBMS_OUTPUT.put_line('CARDINALITY = ' || CARDINALITY(l_tab1));
DBMS_OUTPUT.put_line('CARDINALITY SET = ' || CARDINALITY(SET(l_tab1)));
END;
/
CARDINALITY = 6
CARDINALITY SET = 4
SQL>
Multidimensional Collections
In addition to regular data types, collections can be based on record types, allowing the
creation of two-dimensional collections.
SET SERVEROUTPUT ON
-- Collection of records.
DECLARE
TYPE t_row IS RECORD (
id NUMBER,
description VARCHAR2(50)
);
SET SERVEROUTPUT ON
DECLARE
TYPE t_tab IS TABLE OF t1%ROWTYPE;
l_tab t_tab := t_tab();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab.extend();
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
END;
/
DECLARE
TYPE t_tab1 IS TABLE OF NUMBER;
TYPE t_tab2 IS TABLE OF t_tab1;
l_tab1 t_tab1 := t_tab1(1,2,3,4,5);
l_tab2 t_tab2 := t_tab2();
BEGIN
FOR i IN 1 .. 10 LOOP
l_tab2.extend();
l_tab2(l_tab2.last) := l_tab1;
END LOOP;
END;
/