PL/SQL   «Prev  Next»

Lesson 2 What is flattening?
Objective Define the concept of flattening a Nested Table in Oracle 23ai

Flattening Nested Tables in Oracle (TABLE Function and THE Deprecation)

Lesson 1 introduced the inner/outer table model and showed — in Figure m4-1.1 — how SALE_HEADER (the inner table) contains a DETAIL_NEST column (the outer table) whose elements are stored out-of-line in a separate segment. This lesson defines the operation that bridges those two layers: flattening. Flattening collapses the hierarchical relationship between an inner table and its nested table column into a flat, row-oriented result set that SQL can process with standard SELECT, WHERE, and ORDER BY operations. The TABLE() function is the Oracle 23ai mechanism for performing that collapse.

What Is Flattening?

The Conceptual Definition

A flattened table is a combination of two tables that appear as a single table to the querying SQL statement. When you flatten a nested table, you instruct Oracle to navigate the out-of-line storage segment where the nested table elements live, retrieve those elements, and present them as rows in a virtual table that the outer query can join, filter, and project against — exactly as if those elements were rows in a conventional relational table.

Without flattening, the nested table column is opaque to SQL. Oracle treats the column value as a single cell containing a collection reference — not as a set of rows. A SELECT against SALE_HEADER that references DETAIL_NEST without flattening returns the collection as an atomic object, not as individual queryable rows. Flattening is the step that makes the collection's contents visible to standard SQL operators.

Why Flattening Is Necessary

Oracle stores nested table data out-of-line. When you define a table with a nested table column and specify NESTED TABLE detail_nest STORE AS detail_table, Oracle creates a separate storage segment (detail_table) for the nested table elements. Each element in that segment carries a hidden NESTED_TABLE_ID column that links it to its parent row in SALE_HEADER. The physical storage boundary between SALE_HEADER and its DETAIL_TABLE segment is what makes flattening necessary: SQL cannot cross that boundary automatically. The TABLE() function provides the explicit bridge — it tells Oracle to traverse the link from the parent row to the out-of-line segment and surface the elements as queryable rows.

Connection to the Inner/Outer Table Model

In lesson 1, SALE_HEADER was identified as the inner table (the enclosing relational table) and DETAIL_NEST as the outer table (the nested table column). Flattening operates on this relationship directly. The flattening query joins the inner table to the result of TABLE(outer column), producing a combined result set where each inner table row is paired with each of its corresponding outer table elements — a one-to-many expansion. The result looks like a single flat table even though the underlying data lives in two separate storage structures.


Oracle Collection Functions — THE, CAST, MULTISET, TABLE

Oracle 8i introduced four collection functions that make database tables act like collections and vice versa, enabling DML and query operations against nested table columns. To query, update, insert, or delete records in a nested table column, the column must be accessed as if it were a standalone table. Oracle 8i provided four mechanisms for this: THE, CAST, MULTISET, and TABLE.

THE — Deprecated Since Oracle 8.1.6

THE was the original Oracle 8 mechanism for querying a nested table column. It wrapped a subquery that returned a single nested table value and mapped that value to a virtual database table. THE retrieves the value of each element of the nested table within the outer table — allowing INSERT, UPDATE, DELETE, and SELECT operations against nested table elements using a subquery inside the THE clause.
THE was deprecated effective Oracle 8.1.6 and is not supported in Oracle 23ai. Its syntax required a correlated subquery wrapped in THE(), which was verbose and error-prone. All new Oracle development should use TABLE() instead. THE appears in this lesson because it is documented in legacy Oracle 8 and Oracle 8i textbooks and codebases — recognizing it on sight is necessary; writing it in new code is not.

TABLE — The Current Standard

TABLE() is the Oracle 9i replacement for THE and remains the current standard through Oracle 23ai. TABLE() maps a collection to a virtual database table that SQL can query directly. Unlike THE, TABLE() accepts an alias-qualified collection column as its argument — enabling the compact correlated join form that is the preferred syntax in modern Oracle development. The TABLE() function returns the contents of the collection as a virtual table, with each collection element presented as one row.

CAST — Converting Collections to SQL Types

CAST converts a collection value from one collection type to another compatible SQL type, or converts a multiset subquery result to a declared collection type. CAST is used when a collection needs to be passed to a function or stored in a column that expects a different but compatible collection type, or when constructing a collection value from a SELECT result for insertion into a nested table column:

-- CAST a subquery result into a nested table type
INSERT INTO sale_header (sale_id, customer, detail_nest)
VALUES (
    10,
    'D. Kim',
    CAST(MULTISET(SELECT item FROM staging_items WHERE order_id = 10)
         AS detail_table_type)
);

MULTISET — Converting Subquery Results to Collections

MULTISET converts the result of a subquery into a collection value of the appropriate nested table type. MULTISET is typically used in combination with CAST when inserting or updating nested table columns from relational query results. It is also used in multiset set operations (MULTISET UNION, MULTISET INTERSECT, MULTISET EXCEPT) introduced in Oracle 10g as ANSI SQL 2003 extensions:

-- MULTISET set operation — elements in collection_a but not collection_b
SELECT sale_id,
       (detail_nest MULTISET EXCEPT other_detail) AS unique_items
FROM sale_header
WHERE sale_id = 1;


The TABLE() Function in Detail

How TABLE() Works

TABLE() accepts one argument: a collection value. In practice, that argument is almost always an alias-qualified nested table column from a correlated join — TABLE(sh.detail_nest), where sh is the alias for the row of SALE_HEADER currently being processed. Oracle evaluates TABLE(sh.detail_nest) for each row of SALE_HEADER, retrieves the corresponding nested table elements from the out-of-line storage segment, and presents those elements as rows in a virtual table that participates in the FROM clause join.

The virtual table produced by TABLE() has one column per attribute of the nested table's element type. If the nested table stores scalar values (VARCHAR2, NUMBER), the virtual table has a single column named COLUMN_VALUE. If the nested table stores object type instances, the virtual table has one column per attribute of the object type.

Correlated vs Subquery Form

TABLE() can be used in two syntactic forms. The correlated form — the preferred modern syntax — aliases the inner table in the FROM clause and references the alias inside TABLE():
-- Correlated form (preferred — Oracle 9i through 23ai)
SELECT sh.sale_id, oc.product_id, oc.detail_amount
FROM sale_header sh,
     TABLE(sh.detail_nest) oc
WHERE sh.sale_id = 22;
The subquery form wraps a correlated subquery inside TABLE() — less readable but functionally equivalent:
-- Subquery form (valid but less readable)
SELECT sale_id, detail_total
FROM sale_header sh
WHERE 78 IN (SELECT detail_amount FROM TABLE(sh.detail_nest));
The correlated form is preferred because it makes the join relationship explicit in the FROM clause, allows the alias (oc) to be used in both the SELECT and WHERE clauses for clarity, and aligns with how Oracle's query optimizer most efficiently processes the nested table access.

Flattening Syntax — Oracle 23ai

The following syntax template shows the correlated form for flattening a nested table in Oracle 23ai. This is the current standard form — THE() is not used in new code:
SELECT <expression-list>
FROM <inner table> t,
     TABLE(t.<outer column name>) oc
WHERE <condition on inner table>
AND   <condition on outer table elements>;

Line-by-Line Explanation

The SELECT clause names the columns to return from the combined result. Columns from the inner table are referenced with the inner table alias (t.sale_id); columns from the flattened nested table are referenced with the TABLE() alias (oc.product_id, oc.detail_amount).

The FROM clause lists two sources separated by a comma: the inner table with its alias (t), and TABLE(t.outer column name) with its alias (oc). This implicit join is equivalent to a CROSS JOIN followed by a filter — for each row of the inner table, TABLE() produces the rows of that row's nested table collection, and the WHERE clause then filters the combined result.

The first WHERE condition filters on the inner table — identifying which parent row or rows to retrieve (t.sale_id = 22 selects the sale with ID 22). The AND condition filters on the outer table elements — selecting only those nested table rows that meet the specified criterion (oc.detail_amount = 78 returns only detail rows with a detail amount of 78). Both conditions operate in the same WHERE clause, but they target different layers of the data: the inner table condition narrows the parent rows; the outer table condition narrows the collection elements within those parent rows.

Flattening Example — SALE_HEADER / DETAIL_NEST

The following example applies the correlated syntax template to the SALE_HEADER / DETAIL_NEST schema from lesson 1:
-- Query nested table detail rows using TABLE()
-- Replaces deprecated THE syntax
SELECT oc.product_id,
       oc.item_qty,
       oc.detail_amount
FROM sale_header sh,
     TABLE(sh.detail_nest) oc
WHERE sh.sale_id      = 22
AND   oc.detail_amount = 78;

Query Walkthrough

The SELECT clause requests three columns — product_id, item_qty, and detail_amount — all from the flattened nested table, referenced through the oc alias. No columns from SALE_HEADER itself (sale_id, customer) are included in the output, though they could be added by prefixing them with sh.

The FROM clause joins SALE_HEADER (aliased sh) to TABLE(sh.detail_nest) (aliased oc). For each row of SALE_HEADER, Oracle evaluates TABLE(sh.detail_nest) to retrieve the nested table elements stored in the DETAIL_TABLE segment for that specific sale. The result of TABLE(sh.detail_nest) is a virtual table with the columns of the nested table element type — in this case, product_id, item_qty, and detail_amount.

The first WHERE condition (sh.sale_id = 22) filters SALE_HEADER to the single row for sale 22. Without this condition, the query would flatten and search the detail_nest collections of every row in SALE_HEADER. The second condition (oc.detail_amount = 78) filters the flattened detail rows to only those where detail_amount equals 78. The combined result is the set of detail line items within sale 22 that have a detail amount of exactly 78.

WHERE Conditions — Inner Table vs Outer Table Elements

The distinction between WHERE conditions on the inner table and WHERE conditions on the outer table elements is fundamental to writing correct flattening queries. Inner table conditions (sh.sale_id = 22) filter which parent rows participate in the join — they operate on SALE_HEADER's conventional relational columns. Outer table conditions (oc.detail_amount = 78) filter which nested table elements are returned — they operate on the virtual rows produced by TABLE().

Both types of conditions use the same WHERE clause syntax. The difference is entirely in the alias prefix: sh. references the inner table; oc. references the flattened outer table. A query with only an inner table condition retrieves all nested table elements for the matching parent rows. A query with only an outer table condition searches every parent row's collection for matching elements. A query with both narrows both dimensions simultaneously.

The WHERE IN Subquery Form

The subquery form of TABLE() is useful when the goal is to filter the inner table based on whether any element in its nested table collection meets a criterion — without needing to project individual element columns in the result:
-- Return inner table rows whose nested collection contains at least one
-- element where detail_amount = 78
SELECT sale_id, detail_total, tax_amount, shipping_amount, sale_total
FROM sale_header sh
WHERE 78 IN (SELECT detail_amount FROM TABLE(sh.detail_nest));
This query goes through SALE_HEADER and returns all rows whose DETAIL_NEST collection contains at least one element with a detail_amount of 78. The TABLE() function inside the subquery flattens the nested table for each SALE_HEADER row being evaluated, and the IN condition tests whether 78 appears in the resulting detail_amount values. The outer SELECT returns columns from SALE_HEADER — not from the nested table elements — making this the correct form when you want to identify parent rows based on their collection contents, rather than project individual element attributes.

When to Use Each Form

Use the correlated join form (FROM sale_header sh, TABLE(sh.detail_nest) oc) when you need to project columns from the nested table elements in the SELECT list, apply multiple conditions to element attributes, or treat the flattened collection as a first-class join participant with its own alias.

Use the WHERE IN subquery form when you need to filter the inner table based on collection membership — returning inner table columns only, with the collection used as a filter criterion rather than a data source. The WHERE IN form is also useful when the existence of a matching element is the condition, and you do not need to know which specific element matched.

In the next lesson, you will write a query to flatten a nested table.
[1] Flattening: A relational table which is associated with a nested table consists of two tables, the main relational table and the nested table.

SEMrush Software 2 SEMrush Banner 2