Querying Nested Tables and Varrays (Oracle Collection Types Explained)
Oracle PL/SQL provides three collection types — nested tables, varrays, and associative arrays — for storing and managing sets of same-type data within the database. This module focuses on querying nested tables and varrays using SQL. Before writing the first query, you need to understand what collections are, how they are stored, and why the TABLE() keyword exists as the bridge between a collection column and a queryable result set.
Module Objectives
When you have completed this module, you will be able to:
Explain the concept of flattening a nested table
Describe the SQL enhancements for nested tables such as THE and TABLE
Query nested tables and varrays
Create a PL/SQL block to query nested tables and varrays
What Is a Collection?
Ordered Groups of Same-Type Elements
A collection is an ordered group of elements that are all of the same type. Where a record manages a single row of related attributes, a collection manages multiple rows — or more precisely, multiple elements of the same declared type. Collections can store instances of an object type, serve as attributes of an object type, and be passed as parameters between subprograms. You can use collections to move data in and out of the database, to transfer data between client-side applications and stored subprograms, and to define in-memory data structures within PL/SQL packages.
Collections are lists, which may be ordered or unordered depending on the collection type. Ordered lists are indexed by unique integer subscripts; unordered lists are indexed by unique identifiers, which may be integers, hash values, or string names. The collection type determines the indexing mechanism, the storage model, and the SQL syntax required to query the collection's contents.
Collections vs Records — When to Use Each
Records and collections solve different problems. A record — defined with TYPE rec IS RECORD or anchored to a table row with %ROWTYPE — stores one complete set of related attributes: one customer, one order, one product. A collection stores multiple instances of the same type: many phone numbers for one customer, many line items for one order, many measurements for one sensor. When your data is inherently multi-valued for a single parent entity, a collection is the appropriate structure.
Inner Table and Outer Table — The Key Distinction
The most important conceptual distinction for querying Oracle collections is the inner table / outer table relationship. A collection consists of two logical tables that work together.
The inner table is the enclosing relational table — the standard Oracle table in which a nested table or varray appears as a column data type. The inner table has conventional relational columns alongside the collection column.
The outer table is the collection itself — the nested table or varray that is implemented as a column within the inner table. The outer table stores the collection elements. For nested tables, Oracle stores the outer table data out-of-line in a separate storage segment; for varrays, Oracle stores the data inline with the parent row.
Using the schema that appears throughout this module: SALE_HEADER is the inner table — the enclosing relational table with conventional columns (SALE_ID, CUSTOMER) plus the collection column. DETAIL_NEST is the outer table — the nested table column within SALE_HEADER that stores the collection of detail records. DETAIL_TABLE is the storage segment where Oracle physically stores the nested table data out-of-line.
The diagram below (Figure m4-1.1) shows this three-zone relationship: SALE_HEADER (inner table) → DETAIL_NEST column (outer table, expanded) → the TABLE() keyword that flattens the collection into a queryable result set.
Figure m4-1.1: SALE_HEADER is the inner (enclosing) table. DETAIL_NEST is the outer (nested) table stored out-of-line. TABLE() flattens the collection into a queryable result set.
The Three Oracle Collection Types
Oracle 23ai supports three collection types. All three were available before Oracle 23ai, but their capabilities have been refined across releases. Understanding the distinctions between them is essential for choosing the right structure and the right query syntax.
Associative Arrays (Index-By Tables)
Associative arrays — historically called PL/SQL tables and then index-by tables — are purely in-memory PL/SQL structures. They cannot be stored as column types in relational tables, and they cannot be queried directly with SQL. They are indexed by either a unique integer or a unique VARCHAR2 string, making them effective as lookup tables, caches, and temporary data structures within PL/SQL blocks, procedures, and functions.
Associative arrays are declared with the INDEX BY clause:
TYPE phone_lookup_type IS TABLE OF VARCHAR2(20)
INDEX BY VARCHAR2(50); -- string-indexed associative array
phone_lookup phone_lookup_type;
phone_lookup('Gauss') := '555-0192';
phone_lookup('Riemann') := '555-0317';
Nested Tables
Nested tables are the most flexible Oracle collection type for SQL querying. They can be declared as standalone schema-level types, used as column data types in relational tables, and queried with SQL using the TABLE() operator. Nested tables have no declared maximum size — they can grow to hold any number of elements. Oracle stores nested table data out-of-line in a separate storage segment associated with the parent table, which is why the TABLE() flattening operation is required to query their contents with SQL.
A nested table type and its use in a table definition:
-- Create the nested table type at schema level
CREATE OR REPLACE TYPE detail_table_type AS TABLE OF VARCHAR2(100);
/
-- Create the parent table using the nested table type as a column
CREATE TABLE sale_header (
sale_id NUMBER PRIMARY KEY,
customer VARCHAR2(100),
detail_nest detail_table_type
) NESTED TABLE detail_nest STORE AS detail_table;
The NESTED TABLE ... STORE AS clause names the out-of-line storage segment (detail_table) where Oracle physically stores the nested table elements.
Varrays (Variable-Size Arrays)
A varray is an ordered collection with a declared maximum size. Unlike nested tables, varrays maintain element order and cannot exceed their declared maximum element count. Oracle stores varray data inline with the parent row — in the same data block as the enclosing table row — rather than in a separate storage segment. This inline storage makes varrays efficient for small, fixed-maximum collections where element order matters and the collection is always read as a unit.
-- Create a varray type with a maximum of 5 elements
CREATE OR REPLACE TYPE phone_varray AS VARRAY(5) OF VARCHAR2(20);
/
CREATE TABLE contacts (
contact_id NUMBER PRIMARY KEY,
last_name VARCHAR2(100),
phone_numbers phone_varray
);
Choosing the Right Collection Type
The decision framework for selecting a collection type rests on three questions. First, does the collection need to be stored in a relational table column? If no, use an associative array for in-memory PL/SQL work. If yes, choose between nested tables and varrays. Second, is there a known maximum element count that will never be exceeded? If yes and the collection is small and order matters, use a varray. If the size is unbounded or unknown, use a nested table. Third, will individual elements be deleted independently? Nested tables support deletion of individual elements; varrays do not — you replace the entire varray to modify it.
Collection Storage — Inline vs Out-of-Line
How Varrays Are Stored
Oracle stores varray data inline with the parent row when the varray is small enough to fit within a standard data block. The varray elements travel with the parent row — reading the parent row fetches the varray data in the same I/O operation. For larger varrays that exceed the block size, Oracle stores the data in a LOB segment, but the varray is still logically associated with its parent row and behaves as an inline structure from the application's perspective.
How Nested Tables Are Stored
Oracle stores nested table data out-of-line in a separate storage segment — the segment named in the STORE AS clause of CREATE TABLE. This separate segment contains all the nested table elements for all parent rows, with a hidden NESTED_TABLE_ID column linking each element to its parent row. This out-of-line architecture means a SQL query against the parent table does not automatically retrieve nested table elements — the TABLE() operator is required to tell Oracle to navigate the out-of-line storage and present the nested table data as a queryable set of rows.
Why Storage Model Affects Querying
The fundamental reason that querying nested tables requires the TABLE() operator — while querying varrays also uses TABLE() for SQL access — comes down to how Oracle's SQL engine processes data. A relational query scans rows in a table. A nested table column contains a reference to a collection stored elsewhere, not the collection data itself. Without TABLE(), Oracle treats the nested table column as an opaque value — a single cell containing a collection identifier. With TABLE(), Oracle navigates the out-of-line storage, retrieves the collection elements, and presents them as a virtual table that the outer SQL query can treat as any other table.
History of Oracle Collections
Records are necessary structures to manage single rows of data. Collections are necessary structures to manage multiple rows of data. Understanding the evolution of Oracle collections from Oracle 7 through Oracle 23ai provides context for why the current syntax and terminology exist.
Oracle 7 — PL/SQL Tables
The first Oracle collections were PL/SQL tables, introduced in Oracle 7 (1992). These were purely in-memory, integer-indexed structures — the direct ancestor of today's associative arrays. PL/SQL tables could only be used within PL/SQL blocks; they had no SQL representation and could not be stored in relational table columns.
Oracle 8 — Nested Tables and Varrays
Oracle 8 (1997) added two new collection types: nested tables and varrays. PL/SQL tables were renamed index-by tables to distinguish them from the new nested table type. Oracle 8 also introduced the object-relational model, allowing collection types to be declared at the schema level and used as column data types in relational tables — enabling the SALE_HEADER / DETAIL_NEST pattern that this module uses throughout. Oracle 8i (1998) improved collection access and management, adding collection methods and refining the query syntax.
Oracle 9i through 10g — Refinements and ANSI SQL 2003
Oracle 9i (2001) introduced multilevel collections — collections of collections — and deprecated the THE keyword in favor of TABLE() for nested table queries. Oracle 10g (2003) made four significant additions: ANSI SQL 2003 multiset operations (MULTISET INTERSECT, MULTISET UNION, MULTISET EXCEPT), globalization improvements, unique string indexes for associative arrays (allowing VARCHAR2 subscripts instead of only integers), and additional comparison conditions. Index-by tables were renamed a second time — to associative arrays — reflecting the broader subscript support.
Oracle 19c, 21c, and 23ai — Modern Collection Capabilities
Oracle 19c (2019) introduced polymorphic table functions, enabling more flexible SQL-level access to collection-like structures. Oracle 21c (2021) added a native JSON data type, enabling collections that store JSON documents natively alongside traditional Oracle types. Oracle 23ai (2023) introduced JSON Relational Duality Views, allowing relational data to be exposed and updated as JSON documents — a capability that intersects with collection-type data modeling for applications that need both relational and document-oriented access to the same underlying data. The core nested table and varray syntax covered in this module is stable across all modern Oracle releases.
Enhanced Collection Features
VARRAY Size Alteration
Oracle allows the maximum size of a VARRAY type to be increased after creation using ALTER TYPE with the MODIFY LIMIT clause. This enables the declared capacity of a varray type to grow as application requirements change, without requiring the type to be dropped and recreated:
-- Increase the maximum size of phone_varray from 5 to 10
ALTER TYPE phone_varray MODIFY LIMIT 10 CASCADE;
The CASCADE option propagates the type change to all tables and stored objects that depend on the type. The maximum size can only be increased, not decreased — Oracle prevents size reduction to protect existing data that may already contain the maximum number of elements.
Varrays in Temporary Tables
Oracle supports varray columns in global temporary tables, enabling session-scoped or transaction-scoped collection storage without the overhead of persistent table structures. This is useful for intermediate result sets in complex PL/SQL operations that need temporary multi-valued storage within a SQL-accessible structure.
Multiset Operations — INTERSECT, UNION, EXCEPT
Nested table types support ANSI SQL 2003 multiset operations, enabling set algebra between two nested table instances of the same type:
-- Find elements common to both collections
SELECT CAST(MULTISET(
SELECT * FROM TABLE(collection_a)
INTERSECT
SELECT * FROM TABLE(collection_b)
) AS detail_table_type) AS common_elements
FROM dual;
MULTISET UNION combines two collections including duplicates. MULTISET INTERSECT returns elements present in both collections. MULTISET EXCEPT returns elements in the first collection not present in the second.
Comparison Conditions — Member Of, Submultiset Of
Nested table types support comparison predicates that enable element membership and subset testing directly in WHERE clauses:
-- Test whether a value is a member of a nested table column
SELECT sale_id, customer
FROM sale_header
WHERE 'Widget A' MEMBER OF detail_nest;
-- Test whether one collection is a subset of another
SELECT sale_id
FROM sale_header s1, sale_header s2
WHERE s1.detail_nest SUBMULTISET OF s2.detail_nest
AND s1.sale_id != s2.sale_id;
Oracle Collection Methods
All three Oracle collection types support a set of built-in methods that are called using dot notation on a collection variable within PL/SQL. Collection methods are not available in SQL — they are PL/SQL-only constructs used when manipulating collections programmatically within blocks, procedures, and functions.
Navigation — FIRST, LAST, NEXT, PRIOR
FIRST returns the index of the first element; LAST returns the index of the last element. NEXT(n) returns the index of the element after index n; PRIOR(n) returns the index of the element before index n. These methods are essential for iterating over collections in FOR loops and WHILE loops where the index boundaries may not be known in advance:
DECLARE
TYPE num_table IS TABLE OF NUMBER;
nums num_table := num_table(10, 20, 30, 40, 50);
idx PLS_INTEGER;
BEGIN
idx := nums.FIRST;
WHILE idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE('Element ' || idx || ': ' || nums(idx));
idx := nums.NEXT(idx);
END LOOP;
END;
/
Status — COUNT, EXISTS
COUNT returns the number of elements currently in the collection. EXISTS(n) returns TRUE if an element exists at index n, FALSE otherwise. EXISTS is particularly important before accessing an element by index — accessing a nonexistent index raises a SUBSCRIPT_BEYOND_COUNT or NO_DATA_FOUND exception depending on the collection type:
IF detail_collection.EXISTS(3) THEN
DBMS_OUTPUT.PUT_LINE(detail_collection(3));
END IF;
Modification — DELETE, EXTEND, TRIM
EXTEND adds elements to the end of a nested table or varray — either one element, n elements, or n copies of a specific existing element. TRIM removes elements from the end of a collection. DELETE removes specific elements by index from a nested table (nested tables support sparse deletion; varrays do not support DELETE on individual elements):
detail_collection.EXTEND; -- add one null element
detail_collection.EXTEND(3); -- add three null elements
detail_collection.DELETE(2); -- delete element at index 2 (nested table only)
detail_collection.TRIM(1); -- remove last element
The TABLE and THE Keywords
THE — Deprecated Since Oracle 9i
Oracle 8 introduced the THE keyword as the original SQL mechanism for querying nested table columns. THE wrapped a subquery that returned a single nested table value, instructing Oracle to treat that value as a queryable table:
-- Oracle 8 syntax — deprecated, do not use in new code
SELECT value(d)
FROM THE (SELECT detail_nest FROM sale_header WHERE sale_id = 1) d;
Oracle 9i deprecated THE in favor of TABLE(). While THE may still execute in some Oracle environments for backward compatibility, it is not supported in Oracle 23ai and should never be used in new PL/SQL or SQL development. The module objectives reference THE specifically because it appears in legacy code and documentation — recognizing it is important; using it is not.
TABLE — The Current Standard for Flattening Queries
TABLE() is the current Oracle standard for navigating a nested table or varray column and presenting its contents as a queryable row set. TABLE() takes a subquery that returns a single collection value and exposes that collection's elements as rows:
-- Current syntax — Oracle 9i through 23ai
SELECT d.COLUMN_VALUE AS detail_item
FROM sale_header sh,
TABLE(sh.detail_nest) d
WHERE sh.sale_id = 1;
This query joins SALE_HEADER to the result of TABLE(sh.detail_nest), which presents the nested table elements for each SALE_HEADER row as queryable rows. The COLUMN_VALUE pseudocolumn refers to the value of each element when the nested table contains scalar values rather than object types.
The TABLE() operation is called flattening — it converts the hierarchical, multi-valued collection stored in DETAIL_NEST into a flat, row-oriented result set that SQL can process with standard WHERE, ORDER BY, and aggregate operations. Lesson 2 defines this flattening concept in detail; lesson 3 shows the complete query pattern that uses TABLE() to flatten DETAIL_NEST.
In the next lesson, we will define the concept of flattening a nested table and examine what happens at the storage level when TABLE() translates a collection into a queryable result.