Collections (Arrays, VARRAYs, Nested Tables)
PL/SQL collections are ordered groups of elements of the same type — PL/SQL's version of arrays and lists. Three collection types exist, each with distinct rules about size, sparseness, and storage.
The Three Collection Types
| Feature | Associative Array | VARRAY | Nested Table |
|---|---|---|---|
| Also called | Index-by table | Bounded array | Unbounded table |
| Size limit | Unlimited | Fixed at declaration | Unlimited |
| Indexes | PLS_INTEGER or VARCHAR2 | 1..N (sequential) | 1..N (can be sparse) |
| Stored in DB? | No (PL/SQL only) | Yes (inline in row) | Yes (out-of-line) |
| Can be sparse? | Yes | No | Yes (after DELETE) |
| BULK COLLECT support | Yes (integer index only) | Yes | Yes |
Associative Arrays (Index-By Tables)
The most flexible and commonly used collection. Indexed by PLS_INTEGER or VARCHAR2:
DECLARE
-- Integer-indexed: like a zero-based or arbitrary-index array
TYPE t_salary_list IS TABLE OF employees.salary%TYPE
INDEX BY PLS_INTEGER;
-- String-indexed: like a hash map
TYPE t_dept_map IS TABLE OF VARCHAR2(100)
INDEX BY VARCHAR2(30);
v_salaries t_salary_list;
v_depts t_dept_map;
v_idx PLS_INTEGER;
BEGIN
-- Populate integer-indexed collection
v_salaries(1) := 24000;
v_salaries(2) := 17000;
v_salaries(3) := 13500;
v_salaries(100) := 99999; -- sparse — gaps are fine
-- Populate string-indexed collection
v_depts('IT') := 'Information Technology';
v_depts('HR') := 'Human Resources';
v_depts('FINANCE') := 'Finance';
-- Iterate integer-indexed
v_idx := v_salaries.FIRST;
WHILE v_idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE('Index ' || v_idx || ': $' || v_salaries(v_idx));
v_idx := v_salaries.NEXT(v_idx);
END LOOP;
-- Lookup string-indexed
DBMS_OUTPUT.PUT_LINE('IT dept: ' || v_depts('IT'));
END;
/
VARRAY (Variable-Size Array)
Fixed maximum size, always dense, sequential indexing starting at 1:
DECLARE
-- Maximum 5 elements
TYPE t_skills IS VARRAY(5) OF VARCHAR2(50);
v_emp_skills t_skills := t_skills('SQL', 'PL/SQL', 'Java');
BEGIN
DBMS_OUTPUT.PUT_LINE('Skills count: ' || v_emp_skills.COUNT);
DBMS_OUTPUT.PUT_LINE('Max capacity: ' || v_emp_skills.LIMIT);
-- Extend before adding (VARRAY needs EXTEND)
v_emp_skills.EXTEND;
v_emp_skills(4) := 'Python';
-- Iterate
FOR i IN 1..v_emp_skills.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(i || '. ' || v_emp_skills(i));
END LOOP;
END;
/
Nested Tables
Like a VARRAY but unlimited size and can become sparse after DELETE:
DECLARE
TYPE t_phone_list IS TABLE OF VARCHAR2(20);
v_phones t_phone_list := t_phone_list(
'515.123.4567', '515.123.4568', '515.123.4569'
);
BEGIN
DBMS_OUTPUT.PUT_LINE('Count: ' || v_phones.COUNT);
v_phones.EXTEND(2);
v_phones(4) := '515.999.0001';
v_phones(5) := '515.999.0002';
-- DELETE creates a sparse collection
v_phones.DELETE(3); -- remove element at index 3
-- Must use FIRST/NEXT because it may be sparse
DECLARE v_i PLS_INTEGER := v_phones.FIRST;
BEGIN
WHILE v_i IS NOT NULL LOOP
IF v_phones.EXISTS(v_i) THEN
DBMS_OUTPUT.PUT_LINE(v_i || ': ' || v_phones(v_i));
END IF;
v_i := v_phones.NEXT(v_i);
END LOOP;
END;
END;
/
Collection Methods
DECLARE
TYPE t_list IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
v_l t_list;
BEGIN
v_l(1) := 10; v_l(2) := 20; v_l(5) := 50;
DBMS_OUTPUT.PUT_LINE('COUNT: ' || v_l.COUNT); -- 3 (populated elements)
DBMS_OUTPUT.PUT_LINE('FIRST: ' || v_l.FIRST); -- 1
DBMS_OUTPUT.PUT_LINE('LAST: ' || v_l.LAST); -- 5
DBMS_OUTPUT.PUT_LINE('NEXT(2): ' || v_l.NEXT(2)); -- 5 (skips gap at 3,4)
DBMS_OUTPUT.PUT_LINE('PRIOR(5): ' || v_l.PRIOR(5)); -- 2
DBMS_OUTPUT.PUT_LINE('EXISTS(3): ' || CASE WHEN v_l.EXISTS(3) THEN 'Y' ELSE 'N' END); -- N
v_l.DELETE(2); -- delete element at index 2
DBMS_OUTPUT.PUT_LINE('After DELETE(2), COUNT: ' || v_l.COUNT); -- 2
v_l.DELETE; -- delete ALL elements
DBMS_OUTPUT.PUT_LINE('After DELETE, COUNT: ' || v_l.COUNT); -- 0
END;
/
Method Reference
| Method | Applies to | Description |
|---|---|---|
COUNT |
All | Number of populated elements |
FIRST |
All | Index of first element (NULL if empty) |
LAST |
All | Index of last element |
NEXT(i) |
All | Next index after i (NULL if none) |
PRIOR(i) |
All | Previous index before i (NULL if none) |
EXISTS(i) |
All | TRUE if element i is populated |
EXTEND |
VARRAY, Nested Table | Add one null element at end |
EXTEND(n) |
VARRAY, Nested Table | Add n null elements |
EXTEND(n,i) |
VARRAY, Nested Table | Add n copies of element i |
TRIM |
VARRAY, Nested Table | Remove last element |
TRIM(n) |
VARRAY, Nested Table | Remove last n elements |
DELETE |
All | Remove all elements |
DELETE(i) |
Associative, Nested Table | Remove element at index i |
DELETE(i,j) |
Associative, Nested Table | Remove elements from i to j |
LIMIT |
VARRAY | Maximum size (NULL for others) |
For in-memory lookup tables (department names by ID, job codes by abbreviation, etc.), use an associative array with a
VARCHAR2 index — it acts as a fast hash map with no SQL required once populated.
Multilevel Collections
Collections of collections:
DECLARE
TYPE t_row IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
TYPE t_matrix IS TABLE OF t_row INDEX BY PLS_INTEGER;
v_matrix t_matrix;
BEGIN
-- 3x3 matrix
FOR r IN 1..3 LOOP
FOR c IN 1..3 LOOP
v_matrix(r)(c) := r * 10 + c;
END LOOP;
END LOOP;
-- Print
FOR r IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(
v_matrix(r)(1) || ' ' ||
v_matrix(r)(2) || ' ' ||
v_matrix(r)(3)
);
END LOOP;
END;
/
Choosing the right collection type
Associative Array:
- Working set is in PL/SQL only (not stored in a table column).
- Need sparse storage, string indexes, or flexible sizes.
- Lookup by key (department code → department name).
VARRAY:
- Known maximum size that will not change.
- Storing in a table column where you want the data inline with the row (e.g., a list of up to 10 phone numbers).
Nested Table:
- Unlimited size or size unknown at design time.
- Storing in a table column where the list may be long.
- Need to use SQL set operators (
MULTISET UNION,MULTISET INTERSECT) on the collection.
Summary
- Associative arrays — flexible, PL/SQL-only, integer or string index.
- VARRAYs — fixed max size, always dense, storable in table columns.
- Nested tables — unlimited size, can be sparse after DELETE, storable in table columns.
- Methods (
COUNT,FIRST,LAST,NEXT,PRIOR,EXISTS,EXTEND,DELETE,TRIM,LIMIT) work on all or specific types. - Use
FIRST/NEXTfor safe iteration over potentially sparse collections. - Multilevel (nested) collections are supported — index with multiple subscripts.