5 PL/SQL Collections and Records: What Is A Collection?
5 PL/SQL Collections and Records: What Is A Collection?
Many programming techniques use collection types such as arrays, bags, lists, nested
tables, sets, and trees. To support these techniques in database applications, PL/SQL
provides the datatypes TABLE and VARRAY, which allow you to declare index-by tables,
nested tables and variable-size arrays. In this chapter, you learn how those types let
you reference and manipulate collections of data as whole objects. You also learn how
the datatype RECORD lets you treat related but dissimilar data as a logical unit.
"What Is a Collection?"
"Choosing Which PL/SQL Collection Types to Use"
"Defining Collection Types"
"Declaring PL/SQL Collection Variables"
"Initializing and Referencing Collections"
"Assigning Collections"
"Using PL/SQL Collections with SQL Statements"
"Using Collection Methods"
"Avoiding Collection Exceptions"
"Reducing Loop Overhead for Collections with Bulk Binds"
"What Is a Record?"
"Defining and Declaring Records"
"Initializing Records"
"Assigning Records"
"Manipulating Records"
What Is a Collection?
A collection is an ordered group of elements, all of the same type. It is a general
concept that encompasses lists, arrays, and other familiar datatypes. Each element has
a unique subscript that determines its position in the collection.
Although collections can have only one dimension, you can model multi-dimensional
arrays by creating collections whose elements are also collections.
To use collections in an application, you define one or more PL/SQL types, then
define variables of those types. You can define collection types in a procedure,
function, or package. You can pass collection variables as parameters, to move data
between client-side applications and stored subprograms.
To look up data that is more complex than single values, you can store PL/SQL
records or SQL object types in collections. Nested tables and varrays can also be
attributes of object types.
Within the database, nested tables can be considered one-column database tables.
Oracle stores the rows of a nested table in no particular order. But, when you retrieve
the nested table into a PL/SQL variable, the rows are given consecutive subscripts
starting at 1. That gives you array-like access to individual rows.
PL/SQL nested tables are like one-dimensional arrays. You can model multi-
dimensional arrays by creating nested tables whose elements are also nested tables.
1. Arrays have a fixed upper bound, but nested tables are unbounded
(see Figure 5-1). So, the size of a nested table can increase dynamically.
2. Arrays must be dense (have consecutive subscripts). So, you cannot delete
individual elements from an array. Initially, nested tables are dense, but they
can become sparse (have nonconsecutive subscripts). So, you can delete
elements from a nested table using the built-in procedure DELETE. That might
leave gaps in the index, but the built-in function NEXT lets you iterate over any
series of subscripts.
Understanding Varrays
Items of type VARRAY are called varrays. They allow you to associate a single
identifier with an entire collection. This association lets you manipulate the
collection as a whole and reference individual elements easily. To reference an
element, you use standard subscripting syntax (see Figure 5-2). For
example,Grade(3) references the third element in varray Grades.
A varray has a maximum size, which you must specify in its type definition. Its
index has a fixed lower bound of 1 and an extensible upper bound. For
example, the current upper bound for varray Grades is 7, but you can extend it
to 8, 9, 10, and so on. Thus, a varray can contain a varying number of elements,
from zero (when empty) to the maximum specified in its type definition.
Associative arrays are sets of key-value pairs, where each key is unique and is
used to locate a corresponding value in the array. The key can be an integer or a
string.
Assigning a value using a key for the first time adds that key to the associative
array. Subsequent assignments using the same key update the same entry. It is
important to choose a key that is unique, either by using the primary key from a
SQL table, or by concatenating strings together to form a unique value.
For example, here is the declaration of an associative array type, and two arrays
of that type, using keys that are strings:
DECLARE
TYPE population_type IS TABLE OF NUMBER INDEX BY VARCHAR2(64);
country_population population_type;
continent_population population_type;
howmany NUMBER;
which VARCHAR2(64)
BEGIN
country_population('Greenland') := 100000;
country_population('Iceland') := 750000;
howmany := country_population('Greenland');
continent_population('Australia') := 30000000;
continent_population('Antarctica') := 1000; -- Creates new entry
continent_population('Antarctica') := 1001; -- Replaces previous
value
which := continent_population.FIRST; -- Returns 'Antarctica'
-- as that comes first alphabetically.
which := continent_population.LAST; -- Returns 'Australia'
howmany := continent_population(continent_population.LAST);
-- Returns the value corresponding to the last key, in this
-- case the population of Australia.
END;
/
Associative arrays help you represent data sets of arbitrary size, with fast
lookup for an individual element without knowing its position within the array
and without having to loop through all the array elements. It is like a simple
version of a SQL table where you can retrieve values based on the primary key.
For simple temporary storage of lookup data, associative arrays let you avoid
using the disk space and network operations required for SQL tables.
Because associative arrays are intended for temporary data rather than storing
persistent data, you cannot use them with SQL statements such
as INSERT andSELECT INTO. You can make them persistent for the life of a
database session by declaring the type in a package and assigning the values in
a package body.
How Globalization Settings Affect VARCHAR2 Keys for Associative
Arrays
When you declare an associative array using a string as the key, the declaration
must use a VARCHAR2, STRING, or LONG type. You can use a different type, such
as NCHAR or NVARCHAR2, as the key value to reference an associative array. You
can even use a type such as DATE, as long as it can be converted toVARCHAR2 by
the TO_CHAR function.
However, you must be careful when using other types that the values used as
keys are consistent and unique. For example, the string value of SYSDATE might
change if the NLS_DATE_FORMAT initialization parameter changes, so
that array_element(SYSDATE) does not produce the same result as before. Two
different NVARCHAR2 values might turn into the same VARCHAR2 value (containing
question marks instead of certain national characters). In that
case,array_element(national_string1) and array_element(national_string2)
might refer to the same element.
When you are writing original code or designing the business logic from the
start, you should consider the strengths of each collection type to decide which
is appropriate for each situation.
Both nested tables and associative arrays (formerly known as index-by tables)
use similar subscript notation, but they have different characteristics when it
comes to persistence and ease of parameter passing.
Nested tables can be stored in a database column, but associative arrays cannot.
Nested tables are appropriate for important data relationships that must be
stored persistently.
Associative arrays are appropriate for relatively small lookup tables where the
collection can be constructed in memory each time a procedure is called or a
package is initialized. They are good for collecting information whose volume
is unknown beforehand, because there is no fixed limit on their size. Their
index values are more flexible, because associative array subscripts can be
negative, can be nonsequential, and can use string values instead of numbers
when appropriate.
PL/SQL automatically converts between host arrays and associative arrays that
use numeric key values. The most efficient way to pass collections to and from
the database server is to use anonymous PL/SQL blocks to bulk-bind input and
output host arrays to associative arrays.
Varrays are a good choice when the number of elements is known in advance,
and when the elements are usually all accessed in sequence. When stored in the
database, varrays retain their ordering and subscripts.
Each varray is stored as a single object, either inside the table of which it is a
column (if the varray is less than 4KB) or outside the table but still in the same
tablespace (if the varray is greater than 4KB). You must update or retrieve all
elements of the varray at the same time, which is most appropriate when
performing some operation on all the elements at once. But you might find it
impractical to store and retrieve large numbers of elements this way.
Nested tables can be sparse: you can delete arbitrary elements, rather than just
removing an item from the end. Nested table data is stored out-of-line in astore
table, a system-generated database table associated with the nested table. This
makes nested tables suitable for queries and updates that only affect some
elements of the collection. You cannot rely on the order and subscripts of a
nested table remaining stable as the table is stored and retrieved, because the
order and subscripts are not preserved when a nested table is stored in the
database.
Collections follow the same scoping and instantiation rules as other types and
variables. In a block or subprogram, collections are instantiated when you enter
the block or subprogram and cease to exist when you exit. In a package,
collections are instantiated when you first reference the package and cease to
exist when you end the database session.
Nested Tables
type_name is a type specifier used later to declare collections. For nested tables
declared within PL/SQL, element_type is any PL/SQL datatype except:
REF CURSOR
BINARY_INTEGER, PLS_INTEGER
BOOLEAN
LONG, LONG RAW
NATURAL, NATURALN
POSITIVE, POSITIVEN
REF CURSOR
SIGNTYPE
STRING
Varrays
The meanings of type_name and element_type are the same as for nested tables.
Associative Arrays
For associative arrays (also known as index-by tables), use the syntax:
TYPE type_name IS TABLE OF element_type [NOT NULL]
INDEX BY [BINARY_INTEGER | PLS_INTEGER | VARCHAR2(size_limit)];
INDEX BY key_type;
To store nested tables and varrays inside database tables, you must also declare
SQL types using the CREATE TYPE statement. The SQL types can be used as
columns or as attributes of SQL object types.
You can declare equivalent types within PL/SQL, or use the SQL type name in
a PL/SQL variable declaration.
The following SQL*Plus script shows how you might declare a nested table in
SQL, and use it as an attribute of an object type:
CREATE TYPE CourseList AS TABLE OF VARCHAR2(10) -- define type
/
CREATE TYPE Student AS OBJECT ( -- create object
id_num INTEGER(4),
name VARCHAR2(25),
address VARCHAR2(35),
status CHAR(2),
courses CourseList) -- declare nested table as attribute
/
Varray Example
The script below creates a database column that stores varrays. Each varray
element contains a VARCHAR2.
-- Each project has a 16-character code name.
-- We will store up to 50 projects at a time in a database column.
CREATE TYPE ProjectList AS VARRAY(50) OF VARCHAR2(16);
/
CREATE TABLE department ( -- create database table
dept_id NUMBER(2),
name VARCHAR2(15),
budget NUMBER(11,2),
-- Each department can have up to 50 projects.
projects ProjectList)
/
DECLARE
TYPE nested_type IS TABLE OF VARCHAR2(20);
TYPE varray_type IS VARRAY(50) OF INTEGER;
TYPE associative_array_type IS TABLE OF NUMBER
INDEXED BY BINARY_INTEGER;
v1 nested_type;
v2 varray_type;
v3 associative_array_type;
%TYPE Example
You can use %TYPE to specify the datatype of a previously declared collection,
so that changing the definition of the collection automatically updates other
variables that depend on the number of elements or the element type:
DECLARE
TYPE Platoon IS VARRAY(20) OF Soldier;
p1 Platoon;
-- If we change the number of soldiers in a platoon, p2 will
-- reflect that change when this block is recompiled.
p2 p1%TYPE;
You can also specify a collection type in the RETURN clause of a function
specification:
DECLARE
TYPE SalesForce IS VARRAY(25) OF Salesperson;
FUNCTION top_performers (n INTEGER) RETURN SalesForce IS ...
To specify the element type, you can use %TYPE, which provides the datatype of
a variable or database column. Also, you can use %ROWTYPE, which provides the
rowtype of a cursor or database table. Two examples follow:
DECLARE
TYPE EmpList IS TABLE OF emp.ename%TYPE; -- based on column
CURSOR c1 IS SELECT * FROM dept;
TYPE DeptFile IS VARRAY(20) OF c1%ROWTYPE; -- based on cursor
In the next example, you use a RECORD type to specify the element type:
DECLARE
TYPE AnEntry IS RECORD (
term VARCHAR2(20),
meaning VARCHAR2(200));
TYPE Glossary IS VARRAY(250) OF AnEntry;
You can also impose a NOT NULL constraint on the element type:
DECLARE
TYPE EmpList IS TABLE OF emp.empno%TYPE NOT NULL;
You must explicitly call a constructor for each varray and nested table variable.
(Associative arrays, the third kind of collection, do not use constructors.)
Constructor calls are allowed wherever function calls are allowed.
Because a nested table does not have a declared maximum size, you can put as
many elements in the constructor as necessary.
In the next example, you pass three objects to constructor ProjectList(), which
returns a varray containing those objects:
DECLARE
TYPE ProjectList IS VARRAY(50) OF VARCHAR2(16);
accounting_projects ProjectList;
BEGIN
accounting_projects :=
ProjectList('Expense Report', 'Outsourcing', 'Auditing');
END;
You need not initialize the whole varray. For example, if a varray has a
maximum size of 50, you can pass fewer than 50 elements to its constructor.
Unless you impose the NOT NULL constraint, you can pass null elements to a
constructor. An example follows:
BEGIN
my_courses := CourseList('Math 3010', NULL, 'Stat 3202');
If you call a constructor without arguments, you get an empty but non-null
collection:
DECLARE
TYPE Clientele IS VARRAY(100) OF Customer;
vips Clientele := Clientele(); -- initialize empty varray
BEGIN
IF vips IS NOT NULL THEN -- condition yields TRUE
...
END IF;
END;
In this case, you can call the collection's EXTEND method to add elements later.
In this example, you insert several scalar values and a CourseList nested table
into the SOPHOMORES table.
BEGIN
INSERT INTO sophomores
VALUES (5035, 'Janet Alvarez', '122 Broad St', 'FT',
CourseList('Econ 2010', 'Acct 3401', 'Mgmt 3100'));
Example: Varray Constructor Within a SQL Statement
In this example, you insert a row into database table DEPARTMENT. The varray
constructor ProjectList() provides a value for column PROJECTS.
BEGIN
INSERT INTO department
VALUES(60, 'Security', 750400,
ProjectList('New Badges', 'Track Computers', 'Check Exits'));
This example shows how to reference an element in the nested table NAMES:
DECLARE
TYPE Roster IS TABLE OF VARCHAR2(15);
names Roster := Roster('J Hamil', 'D Caruso', 'R Singh');
BEGIN
FOR i IN names.FIRST .. names.LAST
LOOP
IF names(i) = 'J Hamil' THEN
NULL;
END IF;
END LOOP;
END;
This example shows that you can reference the elements of a collection in
subprogram calls:
DECLARE
TYPE Roster IS TABLE OF VARCHAR2(15);
names Roster := Roster('J Hamil', 'D Piro', 'R Singh');
i BINARY_INTEGER := 2;
BEGIN
verify_name(names(i)); -- call procedure
END;
Assigning Collections
One collection can be assigned to another by an INSERT, UPDATE, FETCH,
or SELECT statement, an assignment statement, or a subprogram call.
where expression yields a value of the type specified for elements in the
collection type definition.
This example shows that collections must have the same datatype for an
assignment to work. Having the same element type is not enough.
DECLARE
TYPE Clientele IS VARRAY(100) OF Customer;
TYPE Vips IS VARRAY(100) OF Customer;
-- These first two variables have the same datatype.
group1 Clientele := Clientele(...);
group2 Clientele := Clientele(...);
-- This third variable has a similar declaration,
-- but is not the same type.
group3 Vips := Vips(...);
BEGIN
-- Allowed because they have the same datatype
group2 := group1;
-- Not allowed because they have different datatypes
group3 := group2;
END;
You assign an atomically null nested table or varray to a second nested table or
varray. In this case, the second collection must be reinitialized:
DECLARE
TYPE Clientele IS TABLE OF VARCHAR2(64);
-- This nested table has some values.
group1 Clientele := Clientele('Customer 1','Customer 2');
-- This nested table is not initialized ("atomically null").
group2 Clientele;
BEGIN
-- At first, the test IF group1 IS NULL yields FALSE.
-- Then we assign a null nested table to group1.
group1 := group2;
-- Now the test IF group1 IS NULL yields TRUE.
-- We must use another constructor to give it some values.
END;
In the same way, assigning the value NULL to a collection makes it atomically
null.
Comparing Collections
You can check whether a collection is null, but not test whether two collections
are the same. Conditions such as greater than, less than, and so on are also not
allowed.
Nested tables and varrays can be atomically null, so they can be tested for
nullity:
DECLARE
TYPE Staff IS TABLE OF Employee;
members Staff;
BEGIN
-- Condition yields TRUE because we haven't used a constructor.
IF members IS NULL THEN ...
END;
If you want to do such comparison operations, you must define your own
notion of what it means for collections to be equal or greater than, less than,
and so on, and write one or more functions to examine the collections and their
elements and return a true or false value.
Each item in column COURSES is a nested table that will store the courses offered
by a given department. The NESTED TABLE clause is required whenever a
database table has a nested table column. The clause identifies the nested table
and names a system-generated store table, in which Oracle stores the nested
table data.
You can retrieve all the courses offered by the English department into a
PL/SQL nested table:
DECLARE
english_courses CourseList;
BEGIN
SELECT courses INTO english_courses FROM department
WHERE name = 'English';
END;
Within PL/SQL, you can manipulate the nested table by looping through its
elements, using methods such as TRIM or EXTEND, and updating some or all of the
elements. Afterwards, you can store the updated table in the database again.
You can revise the list of courses offered by the English Department:
DECLARE
new_courses CourseList :=
CourseList('Expository Writing',
'Film and Literature',
'Discursive Writing',
'Modern English Grammar',
'Realism and Naturalism',
'Introduction to Shakespeare',
'Modern Drama',
'The Short Story',
'The American Novel',
'20th-Century Poetry',
'Advanced Workshop in Poetry');
BEGIN
UPDATE department
SET courses = new_courses WHERE name = 'English';
END;
Next, you define VARRAY type ProjectList, which stores Project objects:
SQL> CREATE TYPE ProjectList AS VARRAY(50) OF Project;
Each item in column projects is a varray that will store the projects scheduled
for a given department.
Now, you are ready to populate relational table department. In the following
example, notice how varray constructor ProjectList() provides values for
column projects:
BEGIN
INSERT INTO department
VALUES(30, 'Accounting', 1205700,
ProjectList(Project(1, 'Design New Expense Report', 3250),
Project(2, 'Outsource Payroll', 12350),
Project(3, 'Evaluate Merger Proposal', 2750),
Project(4, 'Audit Accounts Payable', 1425)));
INSERT INTO department
VALUES(50, 'Maintenance', 925300,
ProjectList(Project(1, 'Repair Leak in Roof', 2850),
Project(2, 'Install New Door Locks', 1700),
Project(3, 'Wash Front Windows', 975),
Project(4, 'Repair Faulty Wiring', 1350),
Project(5, 'Winterize Cooling System', 1125)));
INSERT INTO department
VALUES(60, 'Security', 750400,
ProjectList(Project(1, 'Issue New Employee Badges', 13500),
Project(2, 'Find Missing IC Chips', 2750),
Project(3, 'Upgrade Alarm System', 3350),
Project(4, 'Inspect Emergency Exits', 1900)));
END;
In the following example, you update the list of projects assigned to the
Security Department:
DECLARE
new_projects ProjectList :=
ProjectList(Project(1, 'Issue New Employee Badges', 13500),
Project(2, 'Develop New Patrol Plan', 1250),
Project(3, 'Inspect Emergency Exits', 1900),
Project(4, 'Upgrade Alarm System', 3350),
Project(5, 'Analyze Local Crime Stats', 825));
BEGIN
UPDATE department
SET projects = new_projects WHERE dept_id = 60;
END;
In the next example, you retrieve all the projects for the Accounting
Department into a local varray:
DECLARE
my_projects ProjectList;
BEGIN
SELECT projects INTO my_projects FROM department
WHERE dept_id = 30;
END;
In the final example, you delete the Accounting Department and its project list
from table department:
BEGIN
DELETE FROM department WHERE dept_id = 30;
END;
By default, SQL operations store and retrieve whole collections rather than
individual elements. To manipulate the individual elements of a collection with
SQL, use the TABLE operator. The TABLE operator uses a subquery to extract the
varray or nested table, so that the INSERT, UPDATE, or DELETE statement applies to
the nested table rather than the top-level table.
In the following example, you add a row to the History Department nested table
stored in column COURSES:
BEGIN
-- The TABLE operator makes the statement apply to the nested
-- table from the 'History' row of the DEPARTMENT table.
INSERT INTO
TABLE(SELECT courses FROM department WHERE name = 'History')
VALUES('Modern China');
END;
In the next example, you abbreviate the names for some courses offered by the
Psychology Department:
BEGIN
UPDATE TABLE(SELECT courses FROM department
WHERE name = 'Psychology')
SET credits = credits + adjustment
WHERE course_no IN (2200, 3540);
END;
In the following example, you retrieve the title of a specific course offered by
the History Department:
DECLARE
my_title VARCHAR2(64);
BEGIN
-- We know that there is one history course with 'Etruscan'
-- in the title. This query retrieves the complete title
-- from the nested table of courses for the History department.
SELECT title INTO my_title
FROM
TABLE(SELECT courses FROM department WHERE name = 'History')
WHERE name LIKE '%Etruscan%';
END;
In the following example, you retrieve the title and cost of the Maintenance
Department's fourth project from the varray column projects:
DECLARE
my_cost NUMBER(7,2);
my_title VARCHAR2(35);
BEGIN
SELECT cost, title INTO my_cost, my_title
FROM TABLE(SELECT projects FROM department
WHERE dept_id = 50)
WHERE project_no = 4;
...
END;
The operands of CAST are PL/SQL collection variable and a SQL collection type
(created by the CREATE TYPE statement). CAST converts the PL/SQL collection to
the SQL type.
When creating a nested table of nested tables as a column in SQL, check the
syntax of the CREATE TABLE statement to see how to define the storage table.
Here are some examples showing the syntax and possibilities for multilevel
collections.
declare
type t1 is varray(10) of integer;
type nt1 is varray(10) of t1; -- multilevel varray type
va t1 := t1(2,3,5);
-- initialize multilevel varray
nva nt1 := nt1(va, t1(55,6,73), t1(2,4), va);
i integer;
va1 t1;
begin
-- multilevel access
i := nva(2)(3); -- i will get value 73
dbms_output.put_line(i);
-- add a new varray element to nva
nva.extend;
declare
type tb1 is table of varchar2(20);
type ntb1 is table of tb1; -- table of table elements
type tv1 is varray(10) of integer;
type ntb2 is table of tv1; -- table of varray elements
declare
type tb1 is table of integer index by binary_integer;
-- the following is index-by table of index-by tables
type ntb1 is table of tb1 index by binary_integer;
type va1 is varray(10) of varchar2(20);
-- the following is index-by table of varray elements
type ntb2 is table of va1 index by binary_integer;
declare
type t2 is table of t1;
v2 t2;
begin
select c1 BULK COLLECT INTO v2 from tab1;
dbms_output.put_line(v2.count); -- prints 2
end;
/
EXISTS
COUNT
LIMIT
FIRST and LAST
PRIOR and NEXT
EXTEND
TRIM
DELETE
Only EXISTS can be applied to atomically null collections. If you apply another
method to such collections, PL/SQL raises COLLECTION_IS_NULL.
COUNT returns the number of elements that a collection currently contains. For
instance, if varray projects contains 25 elements, the following IF condition is
true:
IF projects.COUNT = 25 THEN ...
COUNTis useful because the current size of a collection is not always known. For
example, if you fetch a column of Oracle data into a nested table, how many
elements does the table contain? COUNT gives you the answer.
You can use COUNT wherever an integer expression is allowed. In the next
example, you use COUNT to specify the upper bound of a loop range:
FOR i IN 1..courses.COUNT LOOP ...
For varrays, COUNT always equals LAST. For nested tables, COUNT normally
equals LAST. But, if you delete elements from the middle of a nested
table, COUNTbecomes smaller than LAST.
You can use LIMIT wherever an integer expression is allowed. In the following
example, you use LIMIT to determine if you can add 15 more elements to
varray projects:
IF (projects.COUNT + 15) < projects.LIMIT THEN ...
FIRST and LAST return the first and last (smallest and largest) index numbers in a
collection. For an associative array with VARCHAR2 key values, the lowest and
highest key values are returned; ordering is based on the binary values of the
characters in the string, unless the NLS_COMP initialization parameter is set
toANSI, in which case the ordering is based on the locale-specific sort order
specified by the NLS_SORT initialization parameter.
If the collection contains only one element, FIRST and LAST return the same
index value:
IF courses.FIRST = courses.LAST THEN ... -- only one element
The next example shows that you can use FIRST and LAST to specify the lower
and upper bounds of a loop range provided each element in that range exists:
FOR i IN courses.FIRST..courses.LAST LOOP ...
In fact, you can use FIRST or LAST wherever an integer expression is allowed. In
the following example, you use FIRST to initialize a loop counter:
i := courses.FIRST;
WHILE i IS NOT NULL LOOP ...
For varrays, FIRST always returns 1 and LAST always equals COUNT. For nested
tables, FIRST normally returns 1. But, if you delete elements from the beginning
of a nested table, FIRST returns a number larger than 1. Also for nested
tables, LAST normally equals COUNT. But, if you delete elements from the middle
of a nested table, LAST becomes larger than COUNT.
For associative arrays with VARCHAR2 keys, these methods return the appropriate
key value; ordering is based on the binary values of the characters in the string,
unless the NLS_COMP initialization parameter is set to ANSI, in which case the
ordering is based on the locale-specific sort order specified by
theNLS_SORT initialization parameter.
These methods are more reliable than looping through a fixed set of subscript
values, because elements might be inserted or deleted from the collection
during the loop. This is especially true for associative arrays, where the
subscripts might not be in consecutive order and so the sequence of subscripts
might be (1,2,4,8,16) or ('A','E','I','O','U').
PRIOR and NEXT do not wrap from one end of a collection to the other. For
example, the following statement assigns NULL to n because the first element in
a collection has no predecessor:
n := courses.PRIOR(courses.FIRST); -- assigns NULL to n
PRIOR is the inverse of NEXT. For instance, if element i exists, the following
statement assigns element i to itself:
projects(i) := projects.PRIOR(projects.NEXT(i));
You can use PRIOR or NEXT to traverse collections indexed by any series of
subscripts. In the following example, you use NEXT to traverse a nested table
from which some elements have been deleted:
i := courses.FIRST; -- get subscript of first element
WHILE i IS NOT NULL LOOP
-- do something with courses(i)
i := courses.NEXT(i); -- get subscript of next element
END LOOP;
To increase the size of a nested table or varray, use EXTEND. You cannot
use EXTEND with index-by tables.
You cannot use EXTEND to initialize an atomically null collection. Also, if you
impose the NOT NULL constraint on a TABLE or VARRAY type, you cannot apply the
first two forms of EXTEND to collections of that type.
EXTEND operates on the internal size of a collection, which includes any deleted
elements. So, if EXTEND encounters deleted elements, it includes them in its
tally. PL/SQL keeps placeholders for deleted elements so that you can replace
them if you wish. Consider the following example:
DECLARE
TYPE CourseList IS TABLE OF VARCHAR2(10);
courses CourseList;
BEGIN
courses := CourseList('Biol 4412', 'Psyc 3112', 'Anth 3001');
courses.DELETE(3); -- delete element 3
/* PL/SQL keeps a placeholder for element 3. So, the
next statement appends element 4, not element 3. */
courses.EXTEND; -- append one null element
/* Now element 4 exists, so the next statement does
not raise SUBSCRIPT_BEYOND_COUNT. */
courses(4) := 'Engl 2005';
When it includes deleted elements, the internal size of a nested table differs
from the values returned by COUNT and LAST. For instance, if you initialize a
nested table with five elements, then delete elements 2 and 5, the internal size is
5, COUNT returns 3, and LAST returns 4. All deleted elements (whether leading, in
the middle, or trailing) are treated alike.
For example, this statement removes the last three elements from nested
table courses:
courses.TRIM(3);
PL/SQL does not keep placeholders for trimmed elements. So, you cannot
replace a trimmed element simply by assigning it a new value.
For example:
BEGIN
courses.DELETE(2); -- deletes element 2
courses.DELETE(7,7); -- deletes element 7
courses.DELETE(6,3); -- does nothing
courses.DELETE(3,6); -- deletes elements 3 through 6
If an element to be deleted does not exist, DELETE simply skips it; no exception
is raised. PL/SQL keeps placeholders for deleted elements. So, you can replace
a deleted element simply by assigning it a new value.
DELETE lets you maintain sparse nested tables. In the following example, you
retrieve nested table prospects into a temporary table, prune it, then store it
back in the database:
DECLARE
my_prospects ProspectList;
revenue NUMBER;
BEGIN
SELECT prospects INTO my_prospects FROM customers WHERE ...
FOR i IN my_prospects.FIRST..my_prospects.LAST LOOP
estimate_revenue(my_prospects(i), revenue); -- call procedure
IF revenue < 25000 THEN
my_prospects.DELETE(i);
END IF;
END LOOP;
UPDATE customers SET prospects = my_prospects WHERE ...
Note: For varray parameters, the value of LIMIT is always derived from the
parameter type definition, regardless of the parameter mode.
Avoiding Collection Exceptions
In most cases, if you reference a nonexistent collection element, PL/SQL raises
a predefined exception. Consider the following example:
DECLARE
TYPE NumList IS TABLE OF NUMBER;
nums NumList; -- atomically null
BEGIN
/* Assume execution continues despite the raised exceptions. */
nums(1) := 1; -- raises COLLECTION_IS_NULL (1)
nums := NumList(1,2); -- initialize table
nums(NULL) := 3 -- raises VALUE_ERROR (2)
nums(0) := 3; -- raises SUBSCRIPT_OUTSIDE_LIMIT (3)
nums(3) := 3; -- raises SUBSCRIPT_BEYOND_COUNT (4)
nums.DELETE(1); -- delete element 1
IF nums(1) = 1 THEN ... -- raises NO_DATA_FOUND (5)
In the first case, the nested table is atomically null. In the second case, the
subscript is null. In the third case, the subscript is outside the legal range. In the
fourth case, the subscript exceeds the number of elements in the table. In the
fifth case, the subscript designates a deleted element.
In some cases, you can pass invalid subscripts to a method without raising an
exception. For instance, when you pass a null subscript to procedure DELETE, it
does nothing. Also, you can replace deleted elements without
raising NO_DATA_FOUND, as the following example shows:
DECLARE
TYPE NumList IS TABLE OF NUMBER;
nums NumList := NumList(10,20,30); -- initialize table
BEGIN
nums.DELETE(-1); -- does not raise SUBSCRIPT_OUTSIDE_LIMIT
nums.DELETE(3); -- delete 3rd element
dbms_output.put_line(nums.COUNT); -- prints 2
nums(3) := 30; -- allowed; does not raise NO_DATA_FOUND
dbms_output.put_line(nums.COUNT); -- prints 3
END;
Packaged collection types and local collection types are never compatible. For
example, suppose you want to call the following packaged procedure:
CREATE PACKAGE pkg1 AS
TYPE NumList IS VARRAY(25) OF NUMBER(4);
PROCEDURE delete_emps (emp_list NumList);
END pkg1;
When you run the PL/SQL block below, the second procedure call fails with
a wrong number or types of arguments error. That is because the packaged and
local VARRAY types are incompatible even though their definitions are identical.
DECLARE
TYPE NumList IS VARRAY(25) OF NUMBER(4);
emps pkg1.NumList := pkg1.NumList(7369, 7499);
emps2 NumList := NumList(7521, 7566);
BEGIN
pkg1.delete_emps(emps);
pkg1.delete_emps(emps2); -- causes a compilation error
END;
Too many context switches between the PL/SQL and SQL engines can harm
performance. That can happen when a loop executes a separate SQL statement
for each element of a collection, specifying the collection element as a bind
variable. For example, the following DELETE statement is sent to the SQL engine
with each iteration of the FOR loop:
DECLARE
TYPE NumList IS VARRAY(20) OF NUMBER;
depts NumList := NumList(10, 30, 70); -- department numbers
BEGIN
...
FOR i IN depts.FIRST..depts.LAST LOOP
DELETE FROM emp WHERE deptno = depts(i);
END LOOP;
END;
In such cases, if the SQL statement affects four or more database rows, the use
of bulk binds can improve performance considerably.
To do bulk binds with INSERT, UPDATE, and DELETE statements, you enclose the
SQL statement within a PL/SQL FORALL statement.
To do bulk binds with SELECT statements, you include the BULK COLLECT clause
in the SELECT statement instead of using INTO.
For full details of the syntax and restrictions for these statements,
see "FORALL Statement" and "SELECT INTO Statement".
The following DELETE statement is sent to the SQL engine just once, even
though it performs three DELETE operations:
DECLARE
TYPE NumList IS VARRAY(20) OF NUMBER;
depts NumList := NumList(10, 30, 70); -- department numbers
BEGIN
FORALL i IN depts.FIRST..depts.LAST
DELETE FROM emp WHERE deptno = depts(i);
END;
In the example below, 5000 part numbers and names are loaded into index-by
tables. All table elements are inserted into a database table twice: first using
aFOR loop, then using a FORALL statement. The FORALL version is much faster.
SQL> SET SERVEROUTPUT ON
SQL> CREATE TABLE parts (pnum NUMBER(4), pname CHAR(15));
Table created.
The index can be referenced only within the FORALL statement and only as a
collection subscript. The SQL statement must be an INSERT, UPDATE,
orDELETE statement that references collection elements. And, the bounds must
specify a valid range of consecutive index numbers. The SQL engine executes
the SQL statement once for each index number in the range.
Example: Using FORALL with Part of a Collection
As the following example shows, the bounds of the FORALL loop can apply to
part of a collection, not necessarily all the elements:
DECLARE
TYPE NumList IS VARRAY(10) OF NUMBER;
depts NumList := NumList(20,30,50,55,57,60,70,75,90,92);
BEGIN
FORALL j IN 4..7 -- bulk-bind only part of varray
UPDATE emp SET sal = sal * 1.10 WHERE deptno = depts(j);
END;
The SQL statement can reference more than one collection. However, the
PL/SQL engine bulk-binds only subscripted collections. So, in the following
example, it does not bulk-bind the collection sals, which is passed to the
function median:
FORALL i IN 1..20
INSERT INTO emp2 VALUES (enums(i), names(i), median(sals), ...);
DECLARE
TYPE NumTab IS TABLE OF NUMBER;
nums NumTab := NumTab(1, 2, 3, 4);
TYPE PNumTab IS TABLE OF PNum;
pnums PNumTab := PNumTab(PNum(1), PNum(2), PNum(3), PNum(4));
BEGIN
FORALL i IN pnums.FIRST..pnums.LAST
INSERT INTO partno VALUES(pnums(i));
FORALL i IN nums.FIRST..nums.LAST
DELETE FROM partno WHERE n = 2 * nums(i);
FORALL i IN nums.FIRST..nums.LAST
INSERT INTO partno VALUES(100 + nums(i));
END;
Then, you try to append the 7-character string ' (temp)' to certain job titles
using the following UPDATE statement:
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 30);
BEGIN
FORALL j IN depts.FIRST..depts.LAST
UPDATE emp2 SET job = job || ' (temp)'
WHERE deptno = depts(j);
-- raises a "value too large" exception
EXCEPTION
WHEN OTHERS THEN
COMMIT;
END;
The SQL engine executes the UPDATE statement three times, once for each index
number in the specified range, that is, once for depts(10), once fordepts(20),
and once for depts(30). The first execution succeeds, but the second execution
fails because the string value 'Bookkeeper (temp)' is too large for
the job column. In this case, only the second execution is rolled back.
The SQL cursor has one composite attribute, %BULK_ROWCOUNT, designed for use
with the FORALL statement. This attribute has the semantics of an index-by table.
Its ith element stores the number of rows processed by the ith execution of
an INSERT, UPDATE or DELETE statement. If the ith execution affects no
rows, %BULK_ROWCOUNT(i) returns zero. An example follows:
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 50);
BEGIN
FORALL j IN depts.FIRST..depts.LAST
UPDATE emp SET sal = sal * 1.10 WHERE deptno = depts(j);
-- Did the 3rd UPDATE statement affect any rows?
IF SQL%BULK_ROWCOUNT(3) = 0 THEN ...
END;
The FORALL statement and %BULK_ROWCOUNT attribute use the same subscripts. For
example, if FORALL uses the range 5 .. 10, so does %BULK_ROWCOUNT.
FORALL i IN 1..deptnums.COUNT
INSERT INTO emp_by_dept
SELECT empno, deptno FROM emp WHERE deptno =
deptnums(i);
You can also use the scalar attributes %FOUND, %NOTFOUND, and %ROWCOUNT with
bulk binds. For example, %ROWCOUNT returns the total number of rows processed
by all executions of the SQL statement.
%FOUND and %NOTFOUND refer only to the last execution of the SQL statement.
However, you can use %BULK_ROWCOUNT to infer their values for individual
executions. For example, when %BULK_ROWCOUNT(i) is
zero, %FOUND and %NOTFOUND are FALSE and TRUE, respectively.
All exceptions raised during the execution are saved in the new cursor
attribute %BULK_EXCEPTIONS, which stores a collection of records. Each record
has two fields. The first field, %BULK_EXCEPTIONS(i).ERROR_INDEX, holds the
"iteration" of the FORALL statement during which the exception was raised. The
second field, %BULK_EXCEPTIONS(i).ERROR_CODE, holds the corresponding Oracle
error code.
The SQL engine bulk-binds all collections referenced in the INTO list. The
corresponding columns can store scalar or composite values including objects.
In the following example, the SQL engine loads the
entire empno and ename database columns into nested tables before returning the
tables to the PL/SQL engine:
DECLARE
TYPE NumTab IS TABLE OF emp.empno%TYPE;
TYPE NameTab IS TABLE OF emp.ename%TYPE;
enums NumTab; -- no need to initialize
names NameTab;
BEGIN
SELECT empno, ename BULK COLLECT INTO enums, names FROM emp;
...
END;
In the next example, the SQL engine loads all the values in an object column
into a nested table before returning the table to the PL/SQL engine:
CREATE TYPE Coords AS OBJECT (x NUMBER, y NUMBER);
CREATE TABLE grid (num NUMBER, loc Coords);
INSERT INTO grid VALUES(10, Coords(1,2));
INSERT INTO grid VALUES(20, Coords(3,4));
DECLARE
TYPE CoordsTab IS TABLE OF Coords;
pairs CoordsTab;
BEGIN
SELECT loc BULK COLLECT INTO pairs FROM grid;
-- now pairs contains (1,2) and (3,4)
END;
The SQL engine initializes and extends collections for you. (However, it cannot
extend varrays beyond their maximum size.) Then, starting at index 1, it inserts
elements consecutively and overwrites any pre-existent elements.
The SQL engine bulk-binds entire database columns. So, if a table has 50,000
rows, the engine loads 50,000 column values into the target collection.
However, you can use the pseudocolumn ROWNUM to limit the number of rows
processed. In the following example, you limit the number of rows to 100:
DECLARE
TYPE SalList IS TABLE OF emp.sal%TYPE;
sals SalList;
BEGIN
SELECT sal BULK COLLECT INTO sals FROM emp
WHERE ROWNUM <= 100;
...
END;
Limiting the Rows for a Bulk FETCH Operation with the LIMIT
Clause
The optional LIMIT clause, allowed only in bulk (not scalar) FETCH statements,
lets you limit the number of rows fetched from the database. The syntax is
FETCH ... BULK COLLECT INTO ... [LIMIT rows];
In the example below, with each iteration of the loop, the FETCH statement
fetches ten rows (or less) into index-by table empnos. The previous values are
overwritten.
DECLARE
TYPE NumTab IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
CURSOR c1 IS SELECT empno FROM emp;
empnos NumTab;
rows NATURAL := 10;
BEGIN
OPEN c1;
LOOP
/* The following statement fetches 10 rows (or less). */
FETCH c1 BULK COLLECT INTO empnos LIMIT rows;
EXIT WHEN c1%NOTFOUND;
...
END LOOP;
CLOSE c1;
END;
You can use the BULK COLLECT clause in the RETURNING INTO clause of
an INSERT, UPDATE, or DELETE statement, as the following example shows:
DECLARE
TYPE NumList IS TABLE OF emp.empno%TYPE;
enums NumList;
BEGIN
DELETE FROM emp WHERE deptno = 20
RETURNING empno BULK COLLECT INTO enums;
-- if there were five employees in department 20,
-- then enums contains five employee numbers
END;
You cannot bulk collect into an associative array that has a string type
for the key.
You can use the BULK COLLECT clause only in server-side programs (not in
client-side programs). Otherwise, you get the error this feature is not
supported in client-side programs.
All targets in a BULK COLLECT INTO clause must be collections, as the
following example shows:
DECLARE
TYPE NameList IS TABLE OF emp.ename%TYPE;
names NameList;
salary emp.sal%TYPE;
BEGIN
SELECT ename, sal BULK COLLECT INTO names, salary -- illegal
target
FROM emp WHERE ROWNUM < 50;
...
END;
You can combine the BULK COLLECT clause with a FORALL statement, in which
case, the SQL engine bulk-binds column values incrementally. In the following
example, if collection depts has 3 elements, each of which causes 5 rows to be
deleted, then collection enums has 15 elements when the statement completes:
FORALL j IN depts.FIRST..depts.LAST
DELETE FROM emp WHERE empno = depts(j)
RETURNING empno BULK COLLECT INTO enums;
The column values returned by each execution are added to the values returned
previously. (With a FOR loop, the previous values are overwritten.)
You cannot use the SELECT ... BULK COLLECT statement in a FORALL statement.
Otherwise, you get the error implementation restriction: cannot
useFORALL and BULK COLLECT INTO together in SELECT statements.
What Is a Record?
A record is a group of related data items stored in fields, each with its own
name and datatype. Suppose you have various data about an employee such as
name, salary, and hire date. These items are logically related but dissimilar in
type. A record containing a field for each item lets you treat the data as a
logical unit. Thus, records make it easier to organize and represent information.
The attribute %ROWTYPE lets you declare a record that represents a row in a
database table. However, you cannot specify the datatypes of fields in the
record or declare fields of your own. The datatype RECORD lifts those restrictions
and lets you define your own records.
Note: Unlike VARRAY and (nested) TABLE types, RECORD types cannot be CREATEd
and stored in the database.
You can use %TYPE and %ROWTYPE to specify field types. In the following
example, you define a RECORD type named DeptRec:
DECLARE
TYPE DeptRec IS RECORD (
dept_id dept.deptno%TYPE,
dept_name VARCHAR2(14),
dept_loc VARCHAR2(13));
BEGIN
...
END;
Notice that field declarations are like variable declarations. Each field has a
unique name and specific datatype. So, the value of a record is actually a
collection of values, each of some simpler type.
As the example below shows, PL/SQL lets you define records that contain
objects, collections, and other records (called nested records). However, object
types cannot have attributes of type RECORD.
DECLARE
TYPE TimeRec IS RECORD (
seconds SMALLINT,
minutes SMALLINT,
hours SMALLINT);
TYPE FlightRec IS RECORD (
flight_no INTEGER,
plane_id VARCHAR2(10),
captain Employee, -- declare object
passengers PassengerList, -- declare varray
depart_time TimeRec, -- declare nested record
airport_code VARCHAR2(10));
BEGIN
...
END;
The next example shows that you can specify a RECORD type in the RETURN clause
of a function specification. That allows the function to return a user-defined
record of the same type.
DECLARE
TYPE EmpRec IS RECORD (
emp_id NUMBER(4)
last_name VARCHAR2(10),
dept_num NUMBER(2),
job_title VARCHAR2(9),
salary NUMBER(7,2));
...
FUNCTION nth_highest_salary (n INTEGER) RETURN EmpRec IS ...
BEGIN
...
END;
Declaring Records
Once you define a RECORD type, you can declare records of that type, as the
example below shows. The identifier item_info represents an entire record.
DECLARE
TYPE StockItem IS RECORD (
item_no INTEGER(3),
description VARCHAR2(50),
quantity INTEGER,
price REAL(7,2));
item_info StockItem; -- declare record
BEGIN
...
END;
Initializing Records
The example below shows that you can initialize a record in its type definition.
When you declare a record of type TimeRec, its three fields assume an initial
value of zero.
DECLARE
TYPE TimeRec IS RECORD (
secs SMALLINT := 0,
mins SMALLINT := 0,
hrs SMALLINT := 0);
BEGIN
...
END;
The next example shows that you can impose the NOT NULL constraint on any
field, and so prevent the assigning of nulls to that field. Fields declared
as NOTNULL must be initialized.
DECLARE
TYPE StockItem IS RECORD (
item_no INTEGER(3) NOT NULL := 999,
description VARCHAR2(50),
quantity INTEGER,
price REAL(7,2));
BEGIN
...
END;
Referencing Records
When calling a function that returns a user-defined record, use the following
syntax to reference fields in the record:
function_name(parameter_list).field_name
For instance, the following call to function item references the nested
field minutes in record item_info:
DECLARE
TYPE TimeRec IS RECORD (minutes SMALLINT, hours SMALLINT);
TYPE AgendaItem IS RECORD (
priority INTEGER,
subject VARCHAR2(100),
duration TimeRec);
FUNCTION item (n INTEGER) RETURN AgendaItem IS
item_info AgendaItem;
BEGIN
...
RETURN item_info; -- return record
END;
BEGIN
...
IF item(3).duration.minutes > 30 THEN ... -- call function
END;
Also, use extended dot notation to reference the attributes of an object stored in
a field, as the following example shows:
DECLARE
TYPE FlightRec IS RECORD (
flight_no INTEGER,
plane_id VARCHAR2(10),
captain Employee, -- declare object
passengers PassengerList, -- declare varray
depart_time TimeRec, -- declare nested record
airport_code VARCHAR2(10));
flight FlightRec;
BEGIN
...
IF flight.captain.name = 'H Rawlins' THEN ...
END;
Assigning Records
You can assign the value of an expression to a specific field in a record using
the following syntax:
record_name.field_name := expression;
Instead of assigning values separately to each field in a record, you can assign
values to all fields at once. This can be done in two ways. First, you can assign
one user-defined record to another if they have the same datatype. Having
fields that match exactly is not enough. Consider the following example:
DECLARE
TYPE DeptRec IS RECORD (
dept_num NUMBER(2),
dept_name VARCHAR2(14));
TYPE DeptItem IS RECORD (
dept_num NUMBER(2),
dept_name VARCHAR2(14));
dept1_info DeptRec;
dept2_info DeptItem;
BEGIN
...
dept1_info := dept2_info; -- illegal; different datatypes
END;
As the next example shows, you can assign a %ROWTYPE record to a user-defined
record if their fields match in number and order, and corresponding fields have
compatible datatypes:
DECLARE
TYPE DeptRec IS RECORD (
dept_num NUMBER(2),
dept_name VARCHAR2(14),
location VARCHAR2(13));
dept1_info DeptRec;
dept2_info dept%ROWTYPE;
BEGIN
SELECT * INTO dept2_info FROM dept WHERE deptno = 10;
dept1_info := dept2_info;
...
END;
Second, you can use the SELECT or FETCH statement to fetch column values into
a record, as the example below shows. The columns in the select-list must
appear in the same order as the fields in your record.
DECLARE
TYPE DeptRec IS RECORD (
dept_num NUMBER(2),
dept_name VARCHAR2(14),
location VARCHAR2(13));
dept_info DeptRec;
BEGIN
SELECT * INTO dept_info FROM dept WHERE deptno = 20;
...
END;
The example below shows that you can assign one nested record to another if
they have the same datatype. Such assignments are allowed even if the
enclosing records have different datatypes.
DECLARE
TYPE TimeRec IS RECORD (mins SMALLINT, hrs SMALLINT);
TYPE MeetingRec IS RECORD (
day DATE,
time_of TimeRec, -- nested record
room_no INTEGER(4));
TYPE PartyRec IS RECORD (
day DATE,
time_of TimeRec, -- nested record
place VARCHAR2(25));
seminar MeetingRec;
party PartyRec;
BEGIN
...
party.time_of := seminar.time_of;
END;
Comparing Records
Records cannot be tested for nullity, equality, or inequality. For instance, the
following IF conditions are not allowed:
BEGIN
...
IF emp_info IS NULL THEN ... -- illegal
IF dept2_info > dept1_info THEN ... -- illegal
END;
Manipulating Records
The datatype RECORD lets you collect information about the attributes of
something. The information is easy to manipulate because you can refer to the
collection as a whole. In the following example, you collect accounting figures
from database tables assets and liabilities, then use ratio analysis to
compare the performance of two subsidiary companies:
DECLARE
TYPE FiguresRec IS RECORD (cash REAL, notes REAL, ...);
sub1_figs FiguresRec;
sub2_figs FiguresRec;
FUNCTION acid_test (figs FiguresRec) RETURN REAL IS ...
BEGIN
SELECT cash, notes, ... INTO sub1_figs FROM assets, liabilities
WHERE assets.sub = 1 AND liabilities.sub = 1;
SELECT cash, notes, ... INTO sub2_figs FROM assets, liabilities
WHERE assets.sub = 2 AND liabilities.sub = 2;
IF acid_test(sub1_figs) > acid_test(sub2_figs) THEN ...
...
END;
Notice how easy it is to pass the collected figures to the function acid_test,
which computes a financial ratio.
Next, you define VARRAY type PassengerList, which stores Passenger objects:
SQL> CREATE TYPE PassengerList AS VARRAY(300) OF Passenger;
Each item in column passengers is a varray that will store the passenger list for
a given flight. Now, you can populate database table flights, as follows:
BEGIN
INSERT INTO flights
VALUES(109, '80', 'DFW 6:35PM', 'HOU 7:40PM',
PassengerList(Passenger(109, 'Paula Trusdale', '13C'),
Passenger(109, 'Louis Jemenez', '22F'),
Passenger(109, 'Joseph Braun', '11B'), ...));
INSERT INTO flights
VALUES(114, '12B', 'SFO 9:45AM', 'LAX 12:10PM',
PassengerList(Passenger(114, 'Earl Benton', '23A'),
Passenger(114, 'Alma Breckenridge', '10E'),
Passenger(114, 'Mary Rizutto', '11C'), ...));
INSERT INTO flights
VALUES(27, '34', 'JFK 7:05AM', 'MIA 9:55AM',
PassengerList(Passenger(27, 'Raymond Kiley', '34D'),
Passenger(27, 'Beth Steinberg', '3A'),
Passenger(27, 'Jean Lafevre', '19C'), ...));
END;
In the example below, you fetch rows from database table flights into
record flight_info. That way, you can treat all the information about a flight,
including its passenger list, as a logical unit.
DECLARE
TYPE FlightRec IS RECORD (
flight_no NUMBER(3),
gate CHAR(5),
departure CHAR(15),
arrival CHAR(15),
passengers PassengerList);
flight_info FlightRec;
CURSOR c1 IS SELECT * FROM flights;
seat_not_available EXCEPTION;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO flight_info;
EXIT WHEN c1%NOTFOUND;
FOR i IN 1..flight_info.passengers.LAST LOOP
IF flight_info.passengers(i).seat = 'NA' THEN
dbms_output.put_line(flight_info.passengers(i).name);
RAISE seat_not_available;
END IF;
...
END LOOP;
END LOOP;
CLOSE c1;
EXCEPTION
WHEN seat_not_available THEN
...
END;
The number of fields in the record must equal the number of columns listed in
the INTO clause, and corresponding fields and columns must have compatible
datatypes. To make sure the record is compatible with the table, you might find
it most convenient to declare the variable as the type table_name%ROWTYPE.
This example declares a record variable using a %ROWTYPE qualifier. You can
insert this variable without specifying a column list. The %ROWTYPE declaration
ensures that the record attributes have exactly the same names and types as the
table columns.
DECLARE
dept_info dept%ROWTYPE;
BEGIN
-- deptno, dname, and loc are the table columns.
-- The record picks up these names from the %ROWTYPE.
dept_info.deptno := 70;
dept_info.dname := 'PERSONNEL';
dept_info.loc := 'DALLAS';
-- Using the %ROWTYPE means we can leave out the column list
-- (deptno, dname, loc) from the INSERT statement.
INSERT INTO dept VALUES dept_info;
END;
The number of fields in the record must equal the number of columns listed in
the SET clause, and corresponding fields and columns must have compatible
datatypes.
The keyword ROW is allowed only on the left side of a SET clause.
DECLARE
team_rec teams%ROWTYPE;
BEGIN
team_rec.team_no := 5;
team_rec.team_member := Worker('Paul Ocker', 'Accounting');
UPDATE teams SET ROW = team_rec;
END;
/
The INSERT, UPDATE, and DELETE statements can include a RETURNING clause,
which returns column values from the affected row into a PL/SQL record
variable. This eliminates the need to SELECT the row after an insert or update, or
before a delete. You can use this clause only when operating on exactly one
row.
In the following example, you update the salary of an employee and, at the
same time, retrieve the employee's name, job title, and new salary into a record
variable:
DECLARE
TYPE EmpRec IS RECORD (
emp_name VARCHAR2(10),
job_title VARCHAR2(9),
salary NUMBER(7,2));
emp_info EmpRec;
emp_id NUMBER(4);
BEGIN
emp_id := 7782;
UPDATE emp SET sal = sal * 1.1
WHERE empno = emp_id
RETURNING ename, job, sal INTO emp_info;
END;
• The keyword ROW is allowed only on the left side of a SET clause. Also,
you cannot use ROW with a subquery.
• In an UPDATE statement, only one SET clause is allowed if ROW is used.
• If the VALUES clause of an INSERT statement contains a record variable, no
other variable or value is allowed in the clause.
• If the INTO subclause of a RETURNING clause contains a record variable, no
other variable or value is allowed in the subclause.
• The following are not supported:
Nested record types
Functions that return a record
Record inserts/updates using the EXECUTE IMMEDIATE statement.
FETCH { cursor_name
| cursor_variable_name
| :host_cursor_variable_name}
BULK COLLECT INTO record_variable_name
[LIMIT numeric_expression];
In each statement and clause above, the record variable stores a collection of
records. The number of fields in the record must equal the number of items in
theSELECT list, the number of columns in the INSERT INTO clause, the number of
columns in the UPDATE ... SET clause, or the number of row expressions in
theRETURNING clause, respectively. Corresponding fields and columns must have
compatible datatypes. Here are several examples:
CREATE TABLE tab1 (col1 NUMBER, col2 VARCHAR2(20));
/
CREATE TABLE tab2 (col1 NUMBER, col2 VARCHAR2(20));
/
DECLARE
TYPE RecTabTyp IS TABLE OF tab1%ROWTYPE
INDEX BY BINARY_INTEGER;
TYPE NumTabTyp IS TABLE OF NUMBER
INDEX BY BINARY_INTEGER;
TYPE CharTabTyp IS TABLE OF VARCHAR2(20)
INDEX BY BINARY_INTEGER;
CURSOR c1 IS SELECT col1, col2 FROM tab2;
rec_tab RecTabTyp;
num_tab NumTabTyp := NumTabTyp(2,5,8,9);
char_tab CharTabTyp := CharTabTyp('Tim', 'Jon', 'Beth', 'Jenny');
BEGIN
FORALL i IN 1..4
INSERT INTO tab1 VALUES(num_tab(i), char_tab(i));
FORALL i IN rec_tab.FIRST..rec_tab.LAST
INSERT INTO tab2 VALUES rec_tab(i);
FORALL i IN rec_tab.FIRST..rec_tab.LAST
UPDATE tab1 SET (col1, col2) = rec_tab(i) WHERE col1 < 8;
OPEN c1;
FETCH c1 BULK COLLECT INTO rec_tab;
CLOSE c1;
END;