| Lesson 5 | When to use PL/SQL for querying object tables |
| Objective | Determine when SQL alone is sufficient and when PL/SQL is appropriate for querying and processing object data. |
Oracle object tables, object references, nested tables, and varrays do not automatically require PL/SQL. SQL can retrieve object attributes, return complete objects, follow references, and expose collection elements as rows. Start by identifying the required result and deciding whether a SQL statement can express it directly.
PL/SQL becomes useful when a database operation needs local variables, a sequence of statements, procedural calls, branching, or explicit exception handling. It works with SQL rather than replacing it: the SQL engine still performs the queries and data changes embedded in a PL/SQL program. An application language can also coordinate database work, so procedural requirements do not always imply that PL/SQL is the only possible implementation.
This lesson builds on Lesson 4's DEREF example. Its department and employee examples assume that you have created l4_dept_t, l4_dept_tab, and l4_emp_tab using that setup. The department type contains an identifier, name, and location; the employee table contains a department REF. The examples are documentation-reviewed illustrations, not recorded database execution results.
| Requirement | Starting approach | Important distinction |
|---|---|---|
| Filter, join, group, or project attributes | SQL | Object storage retains relational query capabilities. |
| Return an object or follow a REF | VALUE, DEREF, or SQL path expressions | The result can be an object value or individual attributes. |
| Read collection elements | A SQL TABLE expression | A procedural loop is optional. |
| Calculate a value with an object method | An eligible SQL-callable function | A procedure is not a scalar query expression. |
| Coordinate several steps with local state | PL/SQL or application code | Choose where the workflow should execute. |
| Process many rows procedurally | Consider bounded bulk fetching | Compare with one set-based statement first. |
For example, a report that lists departments in Austin is a retrieval problem. A routine that retrieves a department, calls a validation procedure, chooses an action, and handles a missing department is a workflow. Both involve object data, but only the latter introduces a reason to consider procedural orchestration.
Conditional logic alone is not proof that a loop is necessary. SQL WHERE predicates and CASE expressions can express many conditions. Likewise, a large number of rows is not proof that PL/SQL is preferable. The important question is whether the required operation can be performed correctly and clearly as a set.
Query an object table's attributes much as you would query columns in a relational table. The following statement returns scalar values for matching department rows:
SELECT d.dept_id, d.dept_name
FROM l4_dept_tab d
WHERE d.location = 'Austin';
If the caller needs each matching row as an object value, use VALUE with the object-table alias:
SELECT VALUE(d) AS department_object
FROM l4_dept_tab d
WHERE d.dept_id = 10;
VALUE returns the department object rather than three separate result columns. A client might display it using constructor-like notation or an expandable object view. That presentation is client-dependent. The underlying result remains a value of the department object type.
For a reference held in an employee row, SQL can either follow a path to an attribute or return the whole referenced object:
SELECT e.emp_name, e.dept_ref.dept_name AS department
FROM l4_emp_tab e
WHERE e.dept_ref IS NOT NULL
AND e.dept_ref IS NOT DANGLING;
SELECT e.emp_name, DEREF(e.dept_ref) AS department_object
FROM l4_emp_tab e;
The first query implicitly dereferences the department REF and selects its name. Its predicates intentionally exclude employees whose references are null or dangling. The second query retains employee rows and projects a complete department object where available. These queries have different result shapes and different missing-data behavior; they are not interchangeable merely because both access departments.
A REF is distinct from an object value and from an ordinary numeric identifier. A PL/SQL variable receiving a department reference would be declared REF l4_dept_t; a variable receiving VALUE or DEREF would be declared l4_dept_t. Matching these types prevents the error of assigning a reference to an ordinary object variable.
Changing a known department's location does not require fetching its reference first:
UPDATE l4_dept_tab d
SET d.location = 'Chicago'
WHERE d.dept_id = 10;
This UPDATE changes the stored row in the current transaction. The application or person running the transaction decides whether to commit or roll back. No PL/SQL loop is needed to locate the row, and an extra REF lookup would add work without changing this example's requirement.
When applying a similar change to many rows, first ask whether an appropriate WHERE clause can select the entire set. Fetching every row into PL/SQL and issuing a separate UPDATE for each one can introduce avoidable communication between the PL/SQL and SQL engines. A single statement also expresses the affected set in one place.
If a trigger is defined for an affected table event, Oracle invokes it according to its definition. The caller does not need to wrap a SQL statement in PL/SQL merely because triggers exist. Keep trigger behavior, caller language, and transaction ownership as separate design questions.
The next block retrieves a department object and chooses a message based on its location. It illustrates variables, embedded SQL, branching, and a defined response when the department is absent:
SET SERVEROUTPUT ON
DECLARE
v_dept l4_dept_t;
BEGIN
SELECT VALUE(d) INTO v_dept
FROM l4_dept_tab d
WHERE d.dept_id = 10;
IF v_dept.location = 'Austin' THEN
DBMS_OUTPUT.PUT_LINE('Route to the Austin review team.');
ELSE
DBMS_OUTPUT.PUT_LINE('Route to the general review team.');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Department 10 was not found.');
END;
/
VALUE supplies the object value for v_dept. Subsequent dot notation accesses attributes of that local object variable. The branch could be expressed as a SQL CASE expression if producing a message were the entire requirement. PL/SQL becomes more useful when each branch must coordinate additional procedural steps.
The sample's output depends on the current data. With the original Lesson 4 setup it selects the Austin message. If you executed the preceding location UPDATE and have not rolled it back, it selects the general message. Examples that modify data affect later queries in the same session.
The primary-key predicate selects at most one department. No matching row raises NO_DATA_FOUND in SELECT INTO. A different, nonunique predicate could return multiple rows and raise TOO_MANY_ROWS. For a multirow requirement, choose a cursor or another multirow retrieval mechanism instead of hiding a cardinality error with a broad exception handler.
PL/SQL procedural assignments cannot directly follow a REF chain in the way a SQL path expression can. You can use embedded SQL to retrieve the referenced object and then work with its attributes:
DECLARE
v_ref REF l4_dept_t;
v_dept l4_dept_t;
BEGIN
SELECT e.dept_ref INTO v_ref
FROM l4_emp_tab e
WHERE e.emp_id = 101;
SELECT DEREF(v_ref) INTO v_dept FROM DUAL;
IF v_dept IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Department unavailable.');
ELSE
DBMS_OUTPUT.PUT_LINE(v_dept.dept_name);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee 101 was not found.');
END;
/
The second SELECT demonstrates that a supplied REF can be dereferenced without naming its target object table in that statement. DUAL provides a row for evaluating the expression. The first SELECT still needs the employee table because that is where this example obtains the reference.
An absent employee and an unavailable department are different outcomes. The first raises NO_DATA_FOUND; the second can yield a null object from DEREF. SQL dereferencing of a dangling REF returns a null object, rather than invariably raising an exception. Handle the outcome that belongs to the operation being used.
Oracle's UTL_REF package provides procedural operations on objects identified by references. It is useful for generic code that receives a REF and should not hard-code the target table name. Its procedures remain database operations; using the package is not a guarantee of client-side caching or zero database I/O.
UTL_REF.SELECT_OBJECT retrieves an object's value into a compatible PL/SQL variable.UTL_REF.LOCK_OBJECT locks the referenced object; an overload also retrieves it.UTL_REF.UPDATE_OBJECT writes a supplied object value to the referenced object.UTL_REF.DELETE_OBJECT deletes the referenced object.SELECT_OBJECT is not the locking operation. If a workflow needs a protected read-modify-write sequence, design the locking or concurrency check explicitly. Transaction completion releases transaction locks. Do not leave a transaction open while waiting indefinitely for user input.
Consult the package's documented exception behavior before substituting a UTL_REF procedure for a SQL DEREF expression. A missing object, a null REF, insufficient privileges, or a concurrency conflict can produce package-operation errors. Do not assume that every retrieval mechanism handles unavailable objects identically.
Object types can declare member functions, member procedures, and static methods. Eligible functions can be invoked from SQL expressions. Therefore, the existence of object methods does not automatically require PL/SQL. A member procedure, however, is not a scalar expression that returns a value to a SELECT list.
SQL-invoked functions must satisfy the applicable SQL invocation and side-effect restrictions. Declaring a function DETERMINISTIC is not a general requirement for calling it from SQL, and it does not grant permission to modify data in a prohibited context. Similarly, calling a PL/SQL function from SQL does not guarantee parallel execution or eliminate all engine-transition overhead.
A procedure that changes attributes of a fetched object variable changes that local value. Persisting the result requires an appropriate database write. Fetching, modifying, and writing an object can also overwrite intervening changes unless the routine uses a suitable concurrency strategy. The Lesson 4 department type has no methods, so these examples do not call invented methods on it.
Subtype-aware data does not automatically require procedural code either. SQL offers type inspection and conversion facilities, while PL/SQL may be convenient when different subtypes lead to different sequences of operations. Choose based on the workflow rather than the presence of an inheritance hierarchy alone.
A SQL collection value can be exposed as rows with TABLE. For a small independent demonstration, create the following type in a practice schema where its name is unused:
CREATE TYPE l5_name_list_t AS TABLE OF VARCHAR2(50);
/
SELECT n.COLUMN_VALUE AS department_name
FROM TABLE(l5_name_list_t('Cloud Architecture', 'Research')) n
ORDER BY n.COLUMN_VALUE;
The constructor creates a nested-table value containing two strings. TABLE exposes its elements as rows, and COLUMN_VALUE names the scalar element column. The ORDER BY clause determines the display order. COLUMN_VALUE is appropriate for this scalar collection; it is not a universal substitute for VALUE on an object-table alias.
CREATE TYPE is DDL and commits; a later ROLLBACK does not remove the type. Keep this setup separate from unfinished transaction experiments. SQL can also query supported stored nested-table and varray collections. Use procedural iteration when the processing requirement needs it, not simply because the data is a collection.
When processing must occur in PL/SQL, BULK COLLECT retrieves rows in batches and FORALL batches DML bindings. These features can reduce repeated communication between PL/SQL and SQL compared with issuing individual statements for each row. They do not establish that procedural processing is faster than one suitable SQL statement.
An unrestricted BULK COLLECT can load a large result into a collection. For large workloads, consider bounded fetching with FETCH BULK COLLECT LIMIT, process each nonempty batch, and stop after a fetch returns no elements. Choose and measure a batch size appropriate to the data and session memory budget.
FORALL is a DML batching construct, not a general loop for arbitrary method calls or procedural branches. Collection bounds, sparse indexes, and error-handling requirements need deliberate treatment. Do not copy a bulk example until its declarations, element types, target columns, and empty-collection behavior fit the actual operation.
Retrieving an object once can avoid repeating the same retrieval during a routine, but the variable holds a fetched value. It is not a continuously synchronized view of the stored object. Avoid claims that materializing objects always uses a particular memory region or removes all future database access.
An anonymous PL/SQL block has an executable section enclosed by BEGIN and END. Its declaration and exception sections are optional. This small example shows all three sections:
DECLARE
v_name VARCHAR2(50);
BEGIN
SELECT d.dept_name INTO v_name
FROM l4_dept_tab d
WHERE d.dept_id = 10;
DBMS_OUTPUT.PUT_LINE(v_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No matching department.');
END;
/
Declarations introduce local variables and cursors. The executable section runs SQL and procedural statements. An exception handler defines the response to a named failure. Unexpected failures should normally propagate unless the routine can handle them meaningfully; printing SQLERRM in WHEN OTHERS and silently continuing can make a failed operation appear successful.
In SQL*Plus or SQLcl, SET SERVEROUTPUT ON enables display of DBMS_OUTPUT messages, and the slash submits a buffered block. These are client script conventions. Other tools may require their script mode or a separate output panel. Neither a message nor an exception handler automatically commits the transaction.
Begin with the result's shape and cardinality: one object, several rows, a scalar calculation, or a data change. Identify the SQL that produces it. Add PL/SQL when a database-side routine needs to coordinate that work with procedural state, calls, or recovery decisions. Keep transaction ownership explicit, especially for routines called by larger business transactions.
Continue to creating a PL/SQL block to develop the procedural structure further.
Related reading: Oracle PL/SQL Programming.