| Lesson 3 | How to flatten a nested table |
| Objective | Write a query that flattens a nested table |
The correlated join form is the standard Oracle 23ai pattern for flattening a nested table. The inner table (SALE_HEADER) is aliased; the alias is used inside TABLE() to correlate the nested table column to the current parent row:
-- Flatten DETAIL_NEST for sale_id = 101
-- Correlated TABLE() form — Oracle 23ai standard
SELECT oc.product_id,
oc.item_qty,
oc.detail_amount
FROM sale_header sh,
TABLE(sh.detail_nest) oc
WHERE sh.sale_id = 101;
The SELECT clause requests three columns — product_id, item_qty, and detail_amount — from the flattened nested table, referenced through the oc alias. These are attributes of the DETAIL_TABLE_TYPE element type. If columns from the parent row are also needed (sale_id, customer), they are added to the SELECT list prefixed with sh.
The FROM clause specifies two sources: SALE_HEADER aliased sh, and TABLE(sh.detail_nest) aliased oc. For each row of SALE_HEADER that Oracle processes, TABLE(sh.detail_nest) navigates to that row's nested table data in the out-of-line DETAIL_TABLE segment and presents the elements as rows in the oc virtual table. The implicit join between sh and oc produces one result row per detail element.
The WHERE clause (sh.sale_id = 101) filters SALE_HEADER to the single parent row for sale 101. All detail elements belonging to sale 101 are returned — there is no additional filter on the outer table elements in this query. The result is a flat, row-oriented result set showing every product, quantity, and amount from the nested detail collection of sale 101.
To add a filter on the nested table elements — returning only detail rows where detail_amount exceeds a threshold, for example — add an AND condition referencing the oc alias:
-- Flatten DETAIL_NEST and filter on element attributes
SELECT oc.product_id,
oc.item_qty,
oc.detail_amount
FROM sale_header sh,
TABLE(sh.detail_nest) oc
WHERE sh.sale_id = 101
AND oc.detail_amount > 50;
The legacy SALE_HEADER schema in this module's source material defined product_id as a REF column — meaning it points to a row in an object table rather than storing a product ID number directly. The flattening query for this REF-based schema uses DEREF() to retrieve the referenced product object alongside the scalar columns:
-- Flatten DETAIL_NEST where product_id is a REF column
-- DEREF() retrieves the referenced product object
SELECT DEREF(oc.product_id) AS product,
oc.item_qty,
oc.detail_amount
FROM sale_header sh,
TABLE(sh.detail_nest) oc
WHERE sh.sale_id = 34;
This query is the Oracle 23ai modernization of the legacy THE-based query from the original lesson source. The structure is identical to the scalar flattening query — the only addition is DEREF() wrapped around oc.product_id in the SELECT clause. The FROM clause uses the correlated TABLE() form; the deprecated THE keyword is not present.
Use a plain column reference (oc.product_id) when the nested table element type stores scalar values — NUMBER, VARCHAR2, DATE — directly in its attributes. The value returned is the stored scalar itself.
Use DEREF(oc.product_id) when the attribute is defined as a REF to an object type. Without DEREF(), the query returns the raw REF locator — an internal Oracle pointer that is not directly useful in application output. With DEREF(), the query returns the full object instance, whose individual attributes can be accessed using dot notation in the SELECT clause: DEREF(oc.product_id).product_name, DEREF(oc.product_id).unit_price.
In schemas designed for Oracle 23ai from scratch, REF columns are uncommon — direct foreign key relationships and JSON Relational Duality Views are the modern alternatives for cross-table references. DEREF() appears in legacy object-relational schemas built on Oracle 8i and 9i object table patterns and remains supported in Oracle 23ai for backward compatibility.
CREATE OR REPLACE TYPE type_name
AS TABLE OF element_type [ NOT NULL ];
The NOT NULL constraint is optional and, when specified, prevents the nested table from containing null elements. The type_name follows Oracle naming conventions — most schemas use a suffix to signal the type's role. Common conventions are described in the naming standards section below.
The SALE_HEADER schema requires two type definitions: first, an object type for the individual detail element; second, a nested table type whose elements are instances of that object type:
-- Step 1: Define the element object type
CREATE OR REPLACE TYPE detail_item_ty AS OBJECT (
product_id NUMBER,
item_qty NUMBER,
detail_amount NUMBER(10,2)
);
/
-- Step 2: Define the nested table collection type
CREATE OR REPLACE TYPE detail_table_nt AS TABLE OF detail_item_ty;
/
-- Step 3: Create the parent table using the nested table type
CREATE TABLE sale_header (
sale_id NUMBER PRIMARY KEY,
customer VARCHAR2(100),
detail_nest detail_table_nt
) NESTED TABLE detail_nest STORE AS detail_table;
The slash (/) after each CREATE OR REPLACE TYPE statement submits the DDL to Oracle. The NESTED TABLE ... STORE AS clause in CREATE TABLE names the out-of-line storage segment (detail_table) where Oracle physically stores the nested table elements.
Consistent naming conventions for collection-related objects make schemas self-documenting. The most widely used convention applies three suffixes that encode an object's role:
The TY suffix identifies an object type — the element type of a collection. DETAIL_ITEM_TY is an object type; its name signals that it defines the structure of one detail item.
The NT suffix identifies a nested table collection type — a TABLE OF some element type. DETAIL_TABLE_NT is a nested table type; its name signals that it is a collection (specifically a nested table) of DETAIL_ITEM_TY elements.
The NT_TAB suffix identifies the out-of-line storage segment for a nested table — the segment named in the STORE AS clause. DETAIL_TABLE is the storage segment for DETAIL_TABLE_NT; some schemas name this DETAIL_TABLE_NT_TAB to make the relationship to the collection type explicit.
When these conventions are applied consistently, the name of any object reveals its role and its relationship to related objects. If you encounter a table named DETAIL_TABLE_NT_TAB, you immediately know it is the out-of-line storage for a nested table type named DETAIL_TABLE_NT, whose element type is DETAIL_ITEM_TY. Naming discipline at schema creation time eliminates ambiguity during querying, debugging, and maintenance.
DECLARE
-- Declare a nested table variable of the schema-level type
v_details detail_table_nt;
v_idx PLS_INTEGER;
BEGIN
-- Initialize with a constructor — two detail elements
v_details := detail_table_nt(
detail_item_ty(1001, 2, 29.99),
detail_item_ty(1042, 1, 15.50)
);
-- Iterate using FIRST and NEXT
v_idx := v_details.FIRST;
WHILE v_idx IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE(
'Item ' || v_idx ||
' | product_id: ' || v_details(v_idx).product_id ||
' | qty: ' || v_details(v_idx).item_qty ||
' | amount: ' || v_details(v_idx).detail_amount
);
v_idx := v_details.NEXT(v_idx);
END LOOP;
END;
/
CREATE TABLE sale_header (
sale_id NUMBER PRIMARY KEY,
customer VARCHAR2(100),
detail_nest detail_table_nt
) NESTED TABLE detail_nest STORE AS detail_table;
The segment name (detail_table) is the name you use if you need to directly manage storage properties of the nested table — creating indexes on the segment's columns, for example, to speed up TABLE() queries. An index on the NESTED_TABLE_ID column of the out-of-line segment enables Oracle to navigate from a parent row to its nested table elements without scanning the entire segment:
-- Index on the hidden linking column speeds up TABLE() correlated joins
CREATE INDEX idx_detail_table_ntid
ON detail_table (NESTED_TABLE_ID);