Sie sind auf Seite 1von 50

| Print | Contents | Close |

Working with Composite Data Types


Learning objective

After completing this topic, you should be able to recognize the steps for creating userdefined PL/SQL records and records with the %ROWTYPE attribute.

1. Creating user-defined PL/SQL records


Disclaimer
Although certain aspects of Oracle Database 11g are case and spacing insensitive, a
common coding convention has been used throughout all aspects of this course. This
convention uses lowercase characters for schema, role, user, and constraint names, and
for permissions, synonyms, and table names (with the exception of the DUAL table).
Lowercase characters are also used for column names and user-defined procedure,
function, and variable names shown in code. Uppercase characters are used for Oracle
keywords and functions, for view, table, schema, and column names shown in text, for
column aliases that are not shown in quotes, for packages, and for data dictionary views.
The spacing convention requires one space after a comma and one space before and
after operators that are not Oracle-specific, such as +, -, /, and <. There should be no
space between an Oracle-specific keyword or operator and an opening bracket, a closing
bracket and a comma, between the last part of a statement and the closing semicolon, or
before a statement. String literals in single quotes are an exception to all convention rules
provided. Please use this convention for all interactive parts of this course.
----------------------------------------------------------------------------------------------------------------------In PL/SQL, variables of scalar data type can hold only one value, whereas a variable of
composite data type can hold multiple values of scalar data type or composite data type.
The two kinds of composite data types are

PL/SQL records

PL/SQL collections
PL/SQL records
PL/SQL records are used to treat related but dissimilar data as a logical unit. A PL/SQL
record can have variables of different types.
For example, you can define a record to hold employee details. This involves storing an
employee number as NUMBER, a first name and a last name as VARCHAR2, and so on. By

creating a record to store employee details, you create a logical collective unit. This makes
data access and manipulation easier.
PL/SQL collections
PL/SQL collections are used to treat data as a single unit. Collections are of three types:

INDEX BY tables or associative arrays

nested table

VARRAY

You should use composite data types when you have all the related data as a single unit
and so you can easily access and modify the data. Composite data types make data
easier to manage, relate, and transport.
An analogy is having a single bag for all laptop components rather than a separate bag
for each component.
If both PL/SQL records and PL/SQL collections are composite types, use PL/SQL records
when you want to store values of different data types that are logically related. If you
create a record to hold employee details, indicate that all the values stored are related
because they provide information about a particular employee.
Use PL/SQL collections when you want to store values of the same data type. This data
type can also be of the composite type, such as records. You can define a collection to
hold the first names of all employees. You may have stored n names in the collection;
however, name 1 is not related to name 2. The relation between these names is only that
they are employee names.
These collections are similar to arrays in programming languages such as C, C++, and
Java.
A record is a group of related data items stored in fields, each with its own name and data
type. Remember:

Each record defined can have as many fields as necessary.


Records can be assigned initial values and can be defined as NOT NULL. Fields without initial
values are initialized to NULL.
The DEFAULT keyword can also be used when defining fields.

You can define RECORD types and declare user-defined records in the declarative part of any
block, subprogram, or package.

You can declare and reference nested records. One record can be the component of another
record.
PL/SQL records are user-defined composite types. To use them:

1. define the record in the declarative section of a PL/SQL block


2. declare (and optionally initialize) the internal components of this record type
To definePL/SQL record, you use this syntax:

type_name

field_name

field_type

expr

NOT NULL
type_name
The type_name is the name of the RECORD type. (This identifier is used to declare
records.)
field_name
The field_name is the name of a field within the record.
field_type
The field_type is the data type of the field. (It represents any PL/SQL data type except
REF CURSOR. You can use the %TYPE and %ROWTYPE attributes.)
expr
The expr is the field_type or an initial value.
NOT NULL
The NOT NULL constraint prevents assigning nulls to those fields. Be sure to initialize the
NOT NULL fields.
You can then declare the record using this syntax.
identifier type_name
Field declarations used in defining a record are like variable declarations. Each field has
a unique name and a specific data type. There are no predefined data types for PL/SQL
records, as there are for scalar variables. Therefore, you must create the record type first
and then declare an identifier using that type.
In this code, a record type, t_rec, is defined to declare variables to store the name, job,
and salary of a new employee. Then a record called v_myrec of the t_rec type is
declared.
DECLARE
TYPE t_rec IS RECORD
(v_sal number(8),

v_minsal number(8) default 1000,


v_hire_date employees.hire_date%type,
v_rec1 employees%rowtype);
v_myrec t_rec;
BEGIN
v_myrec.v_sal := v_myrec.v_minsal + 500;
v_myrec.v_hire_date := sysdate;
SELECT * INTO v_myrec.v_rec1
FROM employees WHERE employee_id = 100;
DBMS_OUTPUT.PUT_LINE(v_myrec.v_rec1.last_name ||' '||
TO_CHAR(v_myrec.v_hire_date) ||' '|| TO_CHAR(v_myrec.v_sal));
END;

Note
You can add the NOT NULL constraint to any field declaration to prevent assigning
nulls to that field. Remember that the fields declared as NOT NULL must be
initialized.
Fields in a record are accessed with the name of the record. To reference or initialize an
individual field, use dot notation:
record_name.field_name
For example, you reference the job_id field in the emp_record record as
emp_record.job_id.
You can then assign a value to the record field:
emp_record.job_id := 'ST_CLERK';
In a block or subprogram, user-defined records are instantiated when you enter the block
or subprogram. They cease to exist when you exit the block or subprogram.

Question
Which statements are true about PL/SQL records?
Options:
1.

DEFAULT can define a field

2.

Fields can not be initialized to NULL

3.

RECORD types can be defined anywhere in a PL/SQL package

4.

You can declare and reference nested records

Answer

In PL/SQL records, DEFAULT can define a field. You can declare and reference
nested records.
Option 1 is correct. The DEFAULT keyword can be used when defining fields as
well as in variable declarations.
Option 2 is incorrect. Records can be assigned initial values and can be defined
as NOT NULL. Fields without initial values are initialized to NULL.
Option 3 is incorrect. You can define RECORD types and declare user-defined
records in the declarative section of any block, subprogram, or package.
Option 4 is correct. You can declare and reference nested records. One record
can be the component of another record.

2. Creating records with %ROWTYPE attribute


The %TYPE attribute is used to declare a variable of a column type. The variable has the
same data type and size as the table column. The benefit of %TYPE is that you do not
have to change the variable if the column is altered. Also, if the variable is used in any
calculations, you need not worry about its precision.
The %ROWTYPE attribute is used to declare a record that can hold an entire row of a table
or view. The fields in the record take their names and data types from the columns of the
table or view. The record can also store an entire row of data fetched from a cursor or
cursor variable.
When declaring a record, you

declare a variable according to a collection of columns in a database table or view

prefix %ROWTYPE with the database table or view

fields in the record take their names and data types from the columns of the table or view
DECLARE
identifier reference%ROWTYPE;
The syntax for declaring a record consist of these components:

identifier

reference
identifier
The identifier is the name chosen for the record as a whole.
reference

The reference is the name of the table, view, cursor, or cursor variable on which the
record is to be based. (The table or view must exist for this reference to be valid.)
In this sample code, a record is declared using %ROWTYPE as a data type specifier.
The emp_record record has a structure consisting of fields, where each field represents
a column in the EMPLOYEES table.
DECLARE
emp_record employees%ROWTYPE;
...
This is not code but simply the structure of the composite variable.
To reference an individual field, use dot notation: record_name.field_name
For example, you reference the commission_pct field in the emp_record record as
emp_record.commission_pct
You can then assign a value to the record field:
emp_record.commission_pct:= .35;
You can assign a list of common values to a record using the SELECT or FETCH
statement. Make sure that the column names appear in the same order as the fields in
your record. You can also assign one record to another if both have the same
corresponding data types.
A record of type employees%ROWTYPE and a user-defined record type having analogous
fields of the EMPLOYEES table will have the same data type. Therefore, if a user defined
record contains fields similar to the fields of a %ROWTYPE record, you can assign that user
defined record to the %ROWTYPE record.
You use the %ROWTYPE attribute when you are not sure about the structure of the
underlying database table.
The main advantage of using %ROWTYPE is that it simplifies maintenance. Using
%ROWTYPE ensures that the data types of the variables declared with this attribute change
dynamically when the underlying table is altered.
If a DDL statement changes the columns in a table, then the PL/SQL program unit is
invalidated. When the program is recompiled, it will automatically reflect the new table
format.
The %ROWTYPE attribute is particularly useful when you want to retrieve an entire row

from a table. In the absence of this attribute, you would be forced to declare a variable for
each of the columns retrieved by the SELECT statement.
In this sample code, if an employee is retiring, information about that employee is added
to a table that holds information about retired employees. The user supplies the employee
number.
The record of the employee specified by the user is retrieved from the EMPLOYEES table
and stored in the emp_rec variable, which is declared using the %ROWTYPE attribute.
DECLARE
v_employee_number number:= 124;
v_emp_rec employees%ROWTYPE;
BEGIN
SELECT * INTO v_emp_rec FROM employees
WHERE employee_id = v_employee_number;
INSERT INTO retired_emps(empno, ename, job, mgr,
hiredate, leavedate, sal, comm, deptno)
VALUES (v_emp_rec.employee_id, v_emp_rec.last_name,
v_emp_rec.job_id, v_emp_rec.manager_id,
v_emp_rec.hire_date, SYSDATE,
v_emp_rec.salary, v_emp_rec.commission_pct,
v_emp_rec.department_id);
END;
/
This is the CREATE statement that creates the RETIRED_EMPS table.
And this is the record that is inserted into the RETIRED_EMPS table.
CREATE TABLE retired_emps
(EMPNO NUMBER(4), ENAME VARCHAR2(10),
JOB VARCHAR2(9),MGR NUMBER(4),
HIREDATE DATE, LEAVEDATE DATE,
SAL NUMBER(7,2), COMM NUMBER(7,2),
DEPTNO NUMBER(2))
In the sample code, the emp_rec record is of type retired_emps. The number of fields
in the record must be equal to the number of field names in the INTO clause. You can use
this record to insert values into a table. This makes the code more readable.
Here you select HIRE_DATE twice and insert the HIRE_DATE value in the LEAVEDATE
field of retired_emps. No employee retires on the hire date.
...
DECLARE

v_employee_number number:= 124;


v_emp_rec retired_emps%ROWTYPE;
BEGIN
SELECT employee_id, last_name, job_id, manager_id,
hire_date, hire_date, salary, commission_pct,
department_id INTO v_emp_rec FROM employees
WHERE employee_id = v_employee_number;
INSERT INTO retired_emps VALUES v_emp_rec;
END;
/
SELECT * FROM retired_emps;
To update a row in a table by using a record, the ROW keyword is used to represent the
entire row. The sample code updates the leavedate of the employee. The record is
updated.
SET VERIFY OFF
DECLARE
v_employee_number number:= 124;
v_emp_rec retired_emps%ROWTYPE;
BEGIN
SELECT * INTO v_emp_rec FROM retired_emps;
v_emp_rec.leavedate:=CURRENT_DATE;
UPDATE retired_emps SET ROW = v_emp_rec WHERE
empno=v_employee_number;
END;
/
SELECT * FROM retired_emps;

Question
Which statements best illustrate the capabilities of the %ROWTYPE attribute?
Options:
1.

%ROWTYPE is identical to %TYPE

2.

%ROWTYPE records can store an entire row

3.

Field names and data types are automatically defined using %ROWTYPE

4.

References can be made to objects that do not yet exist

Answer
%ROWTYPE records can store an entire row and field names and data types are
automatically defined using %ROWTYPE.

Option 1 is incorrect. %TYPE has limitations in that it can not define the data of an
entire row.
Option 2 is correct. The %ROWTYPE attribute is used to declare a record that can
hold an entire row of a table or view. The record can also store an entire row of
data fetched from a cursor or cursor variable.
Option 3 is correct. The fields in the record take their names and data types from
the columns of the table or view.
Option 4 is incorrect. The reference is the name of the table, view, cursor, or
cursor variable on which the record is to be based. The table or view must exist for
this reference to be valid.

Question
Which statements are true about inserting or updating rows using %ROWTYPE?
Options:
1.

The ROW keyword can be used to represent individual columns

2.

When inserting, the number of fields in the record must be equal to the number of
field names in the INTO clause

3.

You can insert an entire row at once using %ROWTYPE record types

4.

You may not use a WHERE clause when inserting or updating based on %ROWTYPE,
since you are affecting a row rather than a column

Answer
When inserting rows using %ROWTYPE, the number of fields in the record must be
equal to the number of field names in the INTO clause. Also you can insert an
entire row at once using the %ROWTYPE record type.
Option 1 is incorrect. %TYPE is used for individual columns, not the ROW keyword.
Option 2 is correct. The number of fields in the record must be equal to the
number of field names in the INTO clause. You can then use the record to insert
values into a table.
Option 3 is correct. %ROWTYPE's purpose is to define an entire row. The value is
then initialized so that it has the same number of columns as a full row of the
table. Thus the variable is the same type as one row and can be inserted as such.

Option 4 is incorrect. Although you have the ability to affect a row using
%ROWTYPE, you may also use a WHERE clause to limit the records that are affected
by comparing columnar data within the table.

Summary
A PL/SQL record is a collection of individual fields that represent a row in a table. Using
records, you can group the data into one structure and then manipulate this structure as
one entity or logical unit. This helps reduce coding and keeps the coding easy to maintain
and understand.
The %ROWTYPE attribute is used to declare a record that can hold an entire row of a table
or view. Data types are automatically defined using %ROWTYPE. When inserting rows
using %ROWTYPE, the number of fields in the record must be equal to the number of field
names in the INTO clause. Also, you can insert an entire row at once using %ROWTYPE
record type.

Table of Contents
| Top of page |
| Learning objective |
| 1. Creating user-defined PL/SQL records |
| 2. Creating records with %ROWTYPE attribute |
| Summary |
Copyright 2008 SkillSoft. All rights reserved.
SkillSoft and the SkillSoft logo are trademarks or registered trademarks
of SkillSoft in the United States and certain other countries.
All other logos or trademarks are the property of their respective owners.

| Print | Contents | Close |

Using INDEX BY, Nested Tables, and VARRAYs


Learning objective

After completing this topic, you should be able to recognize the steps for creating an
INDEX BY table and table of records, and identify the uses of nested tables and
VARRAYs.

1. Creating INDEX BY tables

INDEX BY tables are composite types (collections) and are user defined. They can store
data using a primary key value as the index, where the key values are not necessarily
sequential. INDEX BY tables are sets of key value pairs.
INDEX BY tables have only two columns:

A column of integer or string type that acts as the primary key. The key can be numeric, either
BINARY_INTEGER or PLS_INTEGER. The BINARY_INTEGER and PLS_INTEGER keys require
less storage than NUMBER. They are used to represent mathematical integers in a compact form
and to implement arithmetic operations by using machine arithmetic. Arithmetic operations on
these data types are faster than NUMBER arithmetic. The key can also be of type VARCHAR2 or one
of its subtypes.

A column of scalar or record data type to hold values. If the column is of scalar type, it can hold
only one value. If the column is of record type, it can hold multiple values.
The INDEX BY tables are unconstrained in size. However, the key in the PLS_INTEGER
column is restricted to the maximum value that a PLS_INTEGER can hold. The keys can
be both positive and negative. The keys in INDEX BY tables are not necessarily in
sequence.
There are two steps involved in creating an INDEX BY table:
1. declare a TABLE data type
2. declare a variable of that data type
This is the syntax for creating an INDEX BY table.
TYPE type_name IS TABLE OF
{column_type | variable%TYPE
| table.column%TYPE} [NOT NULL]
| table%ROWTYPE
[INDEX BY PLS_INTEGER | BINARY_INTEGER
| VARCHAR2(<size>)];
identifier type_name;
These are some key components in the syntax for an INDEX BY table.

type_name

column_type

identifier

NOT NULL
type_name

type_name is the name of the TABLE type. It is a type specifier used in subsequent
declarations of the PL/SQL table identifiers.
column_type
column_type is any scalar or composite data type such as VARCHAR2, DATE, NUMBER, or
%TYPE. You can use the %TYPE attribute to provide the column data type.
identifier
identifier is the name of the identifier that represents an entire PL/SQL table.
NOT NULL
The NOT NULL constraint prevents nulls from being assigned to the PL/SQL table of that
type. Do not initialize the INDEX BY table.
INDEX BY tables can have the element of any scalar type.
They are not automatically populated when you create them. You must programmatically
populate the INDEX BY tables in your PL/SQL programs and then use them.
This sample code declares an INDEX BY table to store the last names of employees.
...
TYPE ename_table_type IS TABLE OF
employees.last_name%TYPE
INDEX BY PLS_INTEGER;
...
ename_table ename_table_type;
Like the size of a database table, the size of an INDEX BY table is unconstrained. That
is, the number of rows in an INDEX BY table can increase dynamically so that your
INDEX BY table grows as new rows are added.
INDEX BY tables can have one column and a unique identifier to that column, neither of
which can be named. The column can belong to any scalar or record data type. The
primary key is either a number or a string. You cannot initialize an INDEX BY table in its
declaration. An INDEX BY table is not populated at the time of declaration. It contains no
keys or values. An explicit executable statement is required to populate the INDEX BY
table.
This sample code creates two INDEX BY tables.
DECLARE
TYPE ename_table_type IS TABLE OF
employees.last_name%TYPE
INDEX BY PLS_INTEGER;
TYPE hiredate_table_type IS TABLE OF DATE

INDEX BY PLS_INTEGER;
ename_table ename_table_type;
hiredate_table hiredate_table_type;
BEGIN
ename_table(1) := 'CAMERON';
hiredate_table(8) := SYSDATE + 7;
IF ename_table.EXISTS(1) THEN
INSERT INTO ...
...
END;
/
You use the key of the INDEX BY table to access an element in the table. This is the
syntax for using the key in which index belongs to type PLS_INTEGER.
INDEX_BY_table_name(index)
This example shows how to reference the third row in an INDEX BY table called
ename_table. The magnitude range of PLS_INTEGER is 2,147,483,647 through
2,147,483,647, so the primary key value can be negative. Indexing does not need to
start with 1.
ename_table(3)

Note
The exists(i) method returns TRUE if a row with index i is returned. You use
the exists method to prevent an error that is raised in reference to a nonexistent
table element.
An INDEX BY table method is a built-in procedure or function that operates on a PL/SQL
table and is called by using dot notation.
This is the syntax of an INDEX BY table method.
table_name.method_name[ (parameters) ]
These are the INDEX BY table methods:

EXISTS(n)

COUNT

FIRST

LAST

PRIOR(n)

NEXT(n)

DELETE
EXISTS(n)
The EXISTS(n) method returns TRUE if the nth element in a PL/SQL table exists.
COUNT
The COUNT method returns the number of elements that a PL/SQL table currently contains.
FIRST
The FIRST method:

returns the first or smallest index number in a PL/SQL table

returns NULL if the PL/SQL table is empty

LAST
The LAST method:

returns the last or largest index number in a PL/SQL table

returns NULL if the PL/SQL table is empty

PRIOR(n)
The PRIOR(n) method returns the index number that precedes index n in a PL/SQL table.
NEXT(n)
The NEXT(n) method returns the index number that succeeds index n in a PL/SQL table.
DELETE
DELETE removes all elements from a PL/SQL table.
DELETE(n) removes the nth element from a PL/SQL table.
DELETE(m, n) removes all elements in the range m...n from a PL/SQL table.
At any particular time, an INDEX BY table declared as a table of scalar data type can
store the details of only one column in a database table. There is often a need to store all
the columns retrieved by a query. The INDEX BY table of records offers a solution to this.
Because only one table definition is needed to hold information about all the fields of a
database table, the table of records greatly increases the functionality of INDEX BY
tables.
In this sample code, you can refer to fields in the dept_table record because each
element of the table is a record.
DECLARE
TYPE dept_table_type IS TABLE OF
departments%ROWTYPE
INDEX BY VARCHAR2(20);

dept_table dept_table_type;
-- Each element of dept_table is a record
This is the syntax to refer to the fields in the record.
table(index).field
In the sample code, LOCATION_ID represents a field in DEPT_TABLE.
dept_table(IT).location_id := 1400;
You can use the %ROWTYPE attribute to declare a record that represents a row in a
database table. The differences between the %ROWTYPE attribute and the composite data
type PL/SQL record are

PL/SQL record types can be user defined, whereas %ROWTYPE implicitly defines the record.

PL/SQL records enable you to specify the fields and their data types when declaring them. When
you use %ROWTYPE, you cannot specify the fields. The %ROWTYPE attribute represents a table row
with all the fields based on the definition of that table.

User-defined records are static. %ROWTYPE records are dynamic because they are based on a
table structure. If the table structure changes, the record structure also picks up the change.
This sample code declares an INDEX BY table of records emp_table_type to
temporarily store the details of employees whose employee IDs are between 100 and
104. Using a loop, the information of the employees from the EMPLOYEES table is
retrieved and stored in the INDEX BY table. Another loop is used to print the last names
from the INDEX BY table.
Note the use of the FIRST and LAST methods in the example.
DECLARE
TYPE emp_table_type IS TABLE OF
employees%ROWTYPE INDEX BY PLS_INTEGER;
my_emp_table emp_table_type;
max_count NUMBER(3):= 104;
BEGIN
FOR i IN 100..max_count
LOOP
SELECT * INTO my_emp_table(i) FROM employees
WHERE employee_id = i;
END LOOP;
FOR i IN my_emp_table.FIRST..my_emp_table.LAST
LOOP DBMS_OUTPUT.PUT_LINE(my_emp_table(i).last_name);
END LOOP;

END;
/

Note
The sample code demonstrates one way to work with an INDEX BY table of
records; however, you can do the same more efficiently using cursors.

Question
Which statements are true about INDEX BY table methods?
Options:
1.

COUNT returns the total number of table elements

2.

EXISTS returns NULL if the PL/SQL table is empty

3.

LAST returns NULL if the PL/SQL table is empty

4.

NEXT returns the largest index number in a PL/SQL table

Answer
The COUNT method returns the total number of table elements and the LAST
method returns NULL if the PL/SQL table is empty.
Option 1 is correct. COUNT returns the number of elements that a PL/SQL table
currently contains.
Option 2 is incorrect. EXISTS returns TRUE if the nth element in a PL/SQL table
exists.
Option 3 is correct. The primary function of LAST is to return the last, largest index
number in a PL/SQL table. However, it returns NULL if the PL/SQL table is empty.
Option 4 is incorrect. NEXT returns the index number that succeeds index n in a
PL/SQL table.

2. Using nested tables and VARRAYs


The functionality of nested tables is similar to that of INDEX BY tables. However, there
are differences in the nested table implementation. The nested table is a valid data type
in a schema-level table, but an INDEX BY table is not. The key cannot be a negative
value unlike in the INDEX BY table. Though you refer to the first column as key, there is
no key in a nested table. There is a column with numbers in sequence that is considered
as the key column.

Elements can be deleted from anywhere in a nested table, leaving a sparse table with
nonsequential keys. The rows of a nested table are not in any particular order. When you
retrieve values from a nested table, the rows are given consecutive subscripts starting
from 1. Nested tables can be stored in the database, unlike INDEX BY tables.
This is the syntax for a nested table.
TYPE type_name IS TABLE OF
{column_type | variable%TYPE
| table.column%TYPE} [NOT NULL]
| table.%ROWTYPE
Starting with Oracle Database 10g, nested tables can be compared for equality. You can
check whether an element exists in a nested table and also whether a nested table is a
subset of another.
Here is sample code and its output that depicts the use of nested tables.
DECLARE
TYPE location_type IS TABLE OF locations.city%TYPE;
offices location_type;
table_count NUMBER;
BEGIN
offices := location_type('Bombay', 'Tokyo','Singapore',
'Oxford');
FOR i in 1.. offices.count() LOOP
DBMS_OUTPUT.PUT_LINE(offices(i));
END LOOP;
END;
/
--Output-anonymous block completed
Bombay
Tokyo
Singapore
Oxford
If you do not initialize a nested table, it is automatically initialized to NULL. You can
initialize the offices nested table by using a constructor.
A variable-size array (VARRAY) is similar to a PL/SQL table, except that a VARRAY is
constrained in size. A VARRAY is valid in a schema-level table. Items of VARRAY type are
called VARRAYs. VARRAYs have a fixed upper bound. You have to specify the upper
bound when you declare them. This is similar to arrays in the C language.

The maximum size of a VARRAY is 2 GB, as in nested tables. The distinction between a
nested table and a VARRAY is the physical storage mode. The elements of a VARRAY are
stored contiguously in memory and not in the database. You can create a VARRAY type in
the database by using SQL.
This sample code uses a VARRAY. In this code, the size of this VARRAY is restricted to 3.
You can initialize a VARRAY by using constructors. If you try to initialize the VARRAY with
more than three elements, a "Subscript outside of limit" error message is displayed.
TYPE location_type IS VARRAY(3) OF locations.city%TYPE;
offices location_type;

Question
Which statements best illustrate nested tables?
Options:
1.

Nested table keys can be a negative value similar to INDEX BY

2.

Nested tables that are not explicitly initialized are initialized to NULL

3.

The nested table is not at a schema level

4.

The rows of a nested table are not in any particular order

Answer
Nested tables that are not explicitly initialized are initialized to NULL and the rows
of a nested table are not in any particular order.
Option 1 is incorrect. The key cannot be a negative value, unlike in an INDEX BY
table. Although the first column is regarded as key, there is actually no key in a
nested table. There is a column with numbers in sequence that is considered the
key column.
Option 2 is correct. Nested tables must be initialized. If you do not initialize a
nested table, it is automatically initialized to NULL.
Option 3 is incorrect. The nested table is a valid data type in a schema-level table,
though an INDEX BY table is not at the schema level.
Option 4 is correct. The rows of a nested table are not in any particular order.
When you retrieve values from a nested table, the rows are given consecutive
subscripts starting from 1.

Question

Which statement accurately describes a VARRAY?


Options:
1.

A VARRAY can grow dynamically

2.

A VARRAY has a fixed upper bound

3.

The elements of a VARRAY are stored in the database

4.

The maximum size of a VARRAY is 1 GB

Answer
A VARRAY has a fixed upper bound.
Option 1 is incorrect. It cannot grow dynamically. Although a VARRAY is similar to a
PL/SQL table, a VARRAY differs in that it is constrained in size.
Option 2 is correct. A VARRAY has a fixed upper bound. You have to specify the
upper bound when you declare one.
Option 3 is incorrect. The elements of a VARRAY are stored contiguously in
memory and not in the database. You can create a VARRAY type in the database
by using SQL.
Option 4 is incorrect. The maximum size of a VARRAY is 2 GB, because it is in
nested tables.

Summary
INDEX BY tables can store data using a primary key value as the index, where the key
values are not necessarily sequential. The size of an INDEX BY table is unconstrained.
INDEX BY tables store a key and a value pair. The key column can be an integer or a
string; the column that holds the value can be of any data type.
The nested table is a valid data type in a schema-level table. The key for the nested
tables cannot have a negative value, unlike the case with INDEX BY tables. The key
must also be in a sequence. VARRAYs are similar to PL/SQL tables, except that a VARRAY
is constrained in size.

Table of Contents
| Top of page |
| Learning objective |

| 1. Creating INDEX BY tables |


| 2. Using nested tables and VARRAYs |
| Summary |
Copyright 2008 SkillSoft. All rights reserved.
SkillSoft and the SkillSoft logo are trademarks or registered trademarks
of SkillSoft in the United States and certain other countries.
All other logos or trademarks are the property of their respective owners.

| Print | Contents | Close |

Working with INDEX BY Tables and PL/SQL Records


Learning objective

After completing this topic, you should be able to define, create, and use INDEX BY
tables and a PL/SQL record in a given scenario.

Exercise overview
In this exercise, you are required to identify code lines and segments that complete
PL/SQL blocks based on specified requirements.
This involves the following tasks:

identifying the code that declares variables and completing a PL/SQL block so that it returns the
required output

identifying the code that completes the creation of a PL/SQL block to retrieve the name of 10
departments from the DEPARTMENTS table, print each department name on the screen, and
incorporate an INDEX BY table
You are a database administrator for a company. As part of your responsibilities, you
need to identify code lines and segments that complete PL/SQL blocks, retrieve the name
of 10 departments, print each department name, and incorporate a table.

Task 1: Declaring PL/SQL block variables


You have written a PL/SQL code block. You need to identify the code in the declarative
section of a PL/SQL block to declare variables and assign different values to it.

Step 1 of 3

Which line of code in the declarative section of a PL/SQL block declares a variable
V_COUNTRYID and assigns a value of CA to it?
Options:
1.

v_country_id VARCHAR2(20) := 'CA';

2.

v_countryid VARCHAR(20) := 'CA';

3.

v_countryid VARCHAR2(20) = 'CA';

4.

v_countryid VARCHAR2(20) := 'CA';

Result
v_countryid VARCHAR2(20) := 'CA'; line of code declares the
V_COUNTRYID variable and assigns a value of CA in the declarative section of a
PL/SQL block.
Option 1 is incorrect. This line would correctly declare a variable and assign it a
value of CA. However, the name of the variable should be V_COUNTRYID.
Option 2 is incorrect. The data type for this variable should be VARCHAR2 with a
size of 20.
Option 3 is incorrect. When declaring a variable and assigning it a value, :=
should be used in place of an equals sign.
Option 4 is correct. This line of code declares a variable V_COUNTRYID with a
data type of VARCHAR2, a size of 20, and an initial value of CA.

Step 2 of 3
In the declarative section of a PL/SQL block, which line of code declares the
V_COUNTRY_RECORD variable of type COUNTRIES using the %ROWTYPE attribute?
Options:
1.

v_country_record countries#ROWTYPE;

2.

v_country_record countries%ROWTYPE;

3.

v_countryrecord ROWTYPE%countries;

4.

v_country_record ROWTYPE%countries;

Result
v_country_record countries%ROWTYPE; code line declares the
V_COUNTRY_RECORD variable of type COUNTRIES using the %ROWTYPE attribute.

Option 1 is incorrect. To use the %ROWTYPE attribute, you need to prefix


%ROWTYPE with the database table or view name.
Option 2 is correct. The %ROWTYPE attribute allows you to declare a variable
according to a collection of columns in a database table or view. To declare a
variable with the %ROWTYPE attribute, you specify the variable name, followed by
the table or view name and %ROWTYPE.
Option 3 is incorrect. The %ROWTYPE attribute should be preceded by the name of
the table or view. Also, the required name of the variable is V_COUNTRY_RECORD
not V_COUNTRYRECORD.
Option 4 is incorrect. The %ROWTYPE attribute should be preceded by the name of
the database table or view that contains the required columns.
Now you now want to display country information in a particular format.

Step 3 of 3
You want to display country information in a way that returns the sample output
Country ID: CA Country Name: Canada Region: 2.
Complete the code to return this output successfully?

DECLARE
v_cty_id varchar2(20) := 'CA';
v_cty_rec countries%ROWTYPE;
BEGIN
SELECT * INTO v_cty_rec
FROM countries
WHERE country_id = UPPER(v_cty_id);
DBMS_OUTPUT.PUT_LINE <missing code>
END;
Options:
1.

'Country ID: ' || v_cty_rec.country_id || ' Country Name: ' ||


v_cty_rec.country_name || ' Region:);

2.

(Country ID: || v_cty_rec.country_id || Country Name: ||


v_cty_rec.country_name || Region: || v_cty_rec.region_id);

3.

('Country ID: ' || v_cty_rec.country_id || ' Country Name: '


|| v_cty_rec.country_name || ' Region: ' ||
v_cty_rec.region_id);

4.

('Country ID: ' || v_cty_rec_country_id || ' Country Name: '


|| v_cty_rec_country_name || ' Region: ' ||
v_cty_rec_region_id);

Result
To display the output in the required format, you use this code:
('Country ID: ' || v_cty_rec.country_id || ' Country Name: '
|| v_cty_rec.country_name || ' Region: ' ||
v_cty_rec.region_id);
Option 1 is incorrect. This line of code will not display the required region code
following the string Region:.
Option 2 is incorrect. This line of code results in an error because single quotes
are required for all string literals, such as Country ID:.
Option 3 is correct. This line displays the country id, country name, and country
region of the country with the ID CA.
Option 4 is incorrect. The variable names and the column names must be
separated by a period, not an underscore. These should be
v_cty_rec.country_id, v_cty_rec.country_name, and
v_cty_rec.region_id.

Task 2: Creating PL/SQL block


You need to create a PL/SQL block to retrieve the name of some departments from the
DEPARTMENTS table and print each department name on the screen, incorporating an
INDEX BY table.

Step 1 of 5
Which line of code declares an INDEX BY table named DEPT_TABLE_TYPE of
type DEPARTMENTS.DEPARTMENT_NAME?
Options:
1.

dept_table_type is table of departments.department_name TYPE


INDEX BY PLS_INTEGER;

2.

dept_table_type is table of departments.department_name%TYPE


INDEX BY PLS_INTEGER;

3.

TYPE dept.table_type is table of departments.department_name


%TYPE INDEX BY PLS_INTEGER;

4.

TYPE dept_table_type is table of departments.department_name


%TYPE INDEX BY PLS_INTEGER;

Result
This code declares an INDEX BY table named DEPT_TABLE_TYPE of type
DEPARTMENTS.DEPARTMENT_NAME:
TYPE dept_table_type is table of departments.department_name
%TYPE
INDEX BY PLS_INTEGER;
Option 1 is incorrect. The keyword TYPE is required before the name of the type to
create an INDEX BY table. Also, the % symbol is required between the column
type and TYPE.
Option 2 is incorrect. The keyword TYPE is required before the name of the type to
create an INDEX BY table.
Option 3 is incorrect. The required type name is DEPT_TABLE_TYPE, not
DEPT.TABLE_TYPE.
Option 4 is correct. This line of code declares an INDEX BY table named
DEPT_TABLE_TYPE that is of type DEPARTMENTS.DEPARTMENT_NAME. It is
indexed by PLS_INTEGER.
You want to declare a variable to temporarily store the name of departments.

Step 2 of 5
Which line of code declares a variable MY_DEPT_TABLE of type
DEPT_TABLE_TYPE used to temporarily store the name of departments?
Options:
1.

dept_table_type my_dept_table;

2.

dept_table_type TYPE my_dept_table;

3.

my_dept_table dept_table_type;

4.

my_dept_table TYPE dept_table_type;

Result
my_dept_table dept_table_type; line of code declares the variable
MY_DEPT_TABLE of type DEPT_TABLE_TYPE to temporarily store the name of
departments.

Option 1 is incorrect. The name of the variable should precede the name of the
type.
Option 2 is incorrect. The TYPE keyword is not required to declare the variable
MY_DEPT_TABLE of the type DEPT_TABLE_TYPE. Also, the name of the variable
should precede the name of the type.
Option 3 is correct. There are two steps required to create an INDEX BY table.
First, you declare a TABLE data type and then you declare a variable of that type.
This line of code declares the variable MY_DEPT_TABLE of the type
DEPT_TABLE_TYPE.
Option 4 is incorrect. The TYPE keyword is not required to declare the variable
MY_DEPT_TABLE of the type DEPT_TABLE_TYPE.
You want to declare two variables with the data type NUMBER and assign some values to
those variables.

Step 3 of 5
You want to declare two variables, F_LOOP_COUNT and V_DEPTNO, with the data
type NUMBER. You also want to assign an initial value of 10 to F_LOOP_COUNT and
a value of 0 to V_DEPTNO.
Which lines of code must you include in the declarative section of your PL/SQL
block?
Options:
1.

f_loop_count NUMBER(2);
v_deptno NUMBER(4);

2.

f_loop_count NUMBER(2) = 10;


v_deptno NUMBER(4) = 0;

3.

f_loop_count NUMBER(2) := 10;


v_deptno NUMBER(4) := 0;

4.

v_loop_count NUMBER(2) := 10;


v_deptno NUMBER(4) := 0;

Result
This code declares two variables F_LOOP_COUNT and V_DEPTNO with the
data type NUMBER and assigns required values variables:
f_loop_count NUMBER(2) := 10;
v_deptno NUMBER(4) := 0;

Option 1 is incorrect. These lines of code declare the F_LOOP_COUNT and


V_DEPTNO variables, but do not specify their initial values.
Option 2 is incorrect. In both lines of code, a colon is required before the equals
sign.
Option 3 is correct. The first line of code declares the F_LOOP_COUNT variable
with a data type of NUMBER, a size of 2, and an initial value of 10. The second line
of code declares the V_DEPTNO variable with a data type of NUMBER, a size of 4,
and an initial value of 0.
Option 4 is incorrect. Although the second line of code declares the V_DEPTNO
variable correctly, the first line of code declares a variable named V_LOOP_FOUND
instead of F_LOOP_COUNT.
You want to use a loop to retrieve the names of departments and store the names in an
INDEX BY table in some specific format.

Step 4 of 5
You want to use a loop to retrieve the names of 10 departments and store the
names in an INDEX BY table. You want to start with DEPARMENT_ID 10 and
increase the value of the V_DEPTNO variable by 10 for each iteration of the loop.
Which line of code should you replace the line <missing code> with?

DECLARE
TYPE dept_table_type is table of
departments.department_name%TYPE INDEX BY PLS_INTEGER;
my_dept_table dept_table_type;
f_loop_count NUMBER (2) := 10;
v_deptno NUMBER (4) := 0;
BEGIN
FOR i IN 1..f_loop_count
LOOP
<missing code>
SELECT department_name INTO my_dept_table(i) FROM
departments
WHERE department_id = v_deptno;
END LOOP;
END;
Options:
1.

deptno := v_deptno + 10;

2.

v_deptno = v_deptno + 10;

3.

v_deptno := deptno + 10;

4.

v_deptno := v_deptno + 10;

Result
v_deptno := v_deptno + 10; code line uses a loop to retrieve the names of
10 departments and store the names in an INDEX BY table.
Option 1 is incorrect. This line of code will result in an error because DEPTNO has
not been declared as a variable.
Option 2 is incorrect. This line of code will result in an error because a colon is
required before the equals sign.
Option 3 is incorrect. DEPTNO is not recognized because it has not been declared
as a variable, so this line of code will cause an error.
Option 4 is correct. Within the loop, this line of code will cause 10 to be added to
the current value of the V_DEPTNO variable at each iteration of the loop.

Step 5 of 5
You want to retrieve the department names from an INDEX BY table and display
them.
Which code segment should you use?
Options:
1.

FOR i IN 1..f_loop_count
LOOP
DBMS_OUTPUT.PUT_LINE(my_dept_table(10));
END LOOP;
END;

2.

FOR i IN 1..f_loop_count
LOOP
DBMS_OUTPUT.PUT_LINE(my_dept_table(i));
END LOOP;
END;

3.

FOR i IN 1..f_loop_count
LOOP
DBMS_OUTPUT.PUT_LINE(v_deptno(i));
END LOOP;
END;

4.

FOR i IN 1..f_loop_count
LOOP

PUT_LINE(my_dept_table(i));
END LOOP;
END;

Result
This code enables you to retrieve the department names from an INDEX BY table:
FOR i IN 1..f_loop_count
LOOP
DBMS_OUTPUT.PUT_LINE(my_dept_table(i));
END LOOP;
END;
Option 1 is incorrect. Within the loop, the call to DBMS_OUTPUT.PUT_LINE should
reference the implicitly declared counter i and not a value of 10.
Option 2 is correct. This code segment loops through the values 1 to 10, the value
of F_LOOP_COUNT. Each iteration of the loop prints out the current value of
MY_DEPT_TABLE(i). In this case, it prints department names.
Option 3 is incorrect. This code segment would result in an error because a
function with the name V_DEPTNO does not exist in scope. MY_DEPT_TABLE
should be used in the place of V_DEPTNO in the call to
DBMS_OUTPUT.PUT_LINE.
Option 4 is incorrect. The name of the package DBMS_OUTPUT must precede the
procedure PUT_LINE within the loop.

Table of Contents
| Top of page |
| Learning objective |
| Exercise overview |
| Task 1: Declaring PL/SQL block variables |
| Task 2: Creating PL/SQL block |
Copyright 2008 SkillSoft. All rights reserved.
SkillSoft and the SkillSoft logo are trademarks or registered trademarks
of SkillSoft in the United States and certain other countries.
All other logos or trademarks are the property of their respective owners.

| Print | Contents | Close |

Using Explicit Cursors and Cursor Attributes


Learning objective

After completing this topic, you should be able to identify the steps for declaring and
controlling explicit cursors, and for writing FOR loops to fetch data from them.

1. Understanding explicit cursor operations


Every executed SQL statement by Oracle server has an associated individual cursor.
There are two types of cursors:

implicit

explicit
The Oracle server uses work areas, called private SQL areas, to execute SQL statements
and store processing information. You can use explicit cursors to name a private SQL
area and to access its stored information.
These types of cursors can be used with the SQL statement:

implicit

explicit
implicit
Implicit cursors are declared by PL/SQL implicitly for all dynamic manipulation language
(DML) and PL/SQL SELECT statements.
explicit
For queries that return more than one row, explicit cursors are declared and managed by
the programmer and manipulated through specific statements in the block's executable
actions.
The Oracle server implicitly opens a cursor to process each SQL statement that is not
associated with an explicitly declared cursor. Using PL/SQL, you can refer to the most
recent implicit cursor as the SQL cursor.
You can declare explicit cursors in PL/SQL when you have a SELECT statement that
returns multiple rows. You can process each row returned by the SELECT statement.
The set of rows returned by a multiple-row query is called the active set. Its size is the
number of rows that meet your search criteria. Here an explicit cursor points to the
current row in the active set. This enables a program to process the rows one at a time.

These are the functions performed by an explicit cursor:

it can perform row-by-row processing beyond the first row returned by a query

it keeps track of the row that is currently being processed

it enables the programmer to manually control explicit cursors in the PL/SQL block
The graphic depicts a flowchart for controlling explicit cursors. As the first step, you create
a named SQL area to declare the cursor. Then you open the cursor to identify the active
set. Next you perform the fetch operation to load the current row into variables. Then you
test the existing rows. If there are no rows, you close the cursor to release the active set.
If rows are found, you return to the fetch operation.

defining the structure of the query

opening the cursor

fetching data from the cursor

closing the cursor


defining the structure of the query
The first step is to define the structure of the query. In the declarative section of a PL/SQL
block, you declare the cursor by naming it and defining the structure of the query to be
associated with it.
opening the cursor
After declaring the cursor, you need to open the cursor to identify the active set of rows.
The OPEN statement executes the query and binds any variables that are referenced.
Rows identified by the query are called the active set and are available for fetching.
fetching data from the cursor
The third step involves fetching data from the cursor. After each fetch, you test the cursor
for any existing row. If there are no more rows to process, you must close the cursor.
closing the cursor
The fourth step involves closing the cursor. The CLOSE statement releases the active set
of rows. You can reopen the cursor to establish a fresh active set.
A PL/SQL program opens a cursor, processes rows returned by a query, and then closes
the cursor. The cursor marks the current position in the active set.
You perform these steps to control explicit cursors in a PL/SQL program:

OPEN

FETCH

CLOSE
OPEN

The OPEN statement executes the query associated with the cursor, identifies the active
set, and positions the cursor at the first row. The OPEN statement is included in the
executable section of the PL/SQL block.
FETCH
The FETCH statement retrieves the current row and advances the cursor to the next row
until there are no more rows or a specified condition is met.
CLOSE
The CLOSE statement releases the cursor.

2. Declaring explicit cursors


In the syntax to declare a cursor, cursor_name is the PL/SQL identifier, and the
select_statement is used to identify the SELECT statement without an INTO clause.
CURSOR cursor_name IS
select_statement;
The active set of a cursor is determined by the SELECT statement in the cursor
declaration. It is mandatory to have an INTO clause for a SELECT statement in PL/SQL.
However, the SELECT statement in the cursor declaration cannot have an INTO clause.
This is because you are only defining a cursor in the declarative section and not retrieving
any rows into the cursor.
You need to consider these guidelines when declaring a cursor:

do not include the INTO clause in the cursor declaration because it appears later in the FETCH
statement

if processing rows in a specific sequence is required, use the ORDER BY clause in the query

the cursor can be any valid SELECT statement, including joins, subqueries, and so on
In this example, the c_emp_cursor is being declared to retrieve the EMPLOYEE_ID and
LAST_NAME columns for those employees working in the department with a
DEPARTMENT_ID of 30.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
In this sample code, the c_dept_cursor cursor is declared to retrieve all the details for
the department with the LOCATION_ID 1700.
Note that a variable is used while declaring the cursor. These variables are considered

bind variables, which must be visible when you are declaring the cursor. These variables
are examined only once at the time the cursor opens. Explicit cursors are used when you
have to retrieve and operate on multiple rows in PL/SQL. However, this example shows
that you can use the explicit cursor even if your SELECT statement returns only one row.
DECLARE
v_locid NUMBER:= 1700;
CURSOR c_dept_cursor IS
SELECT * FROM departments
WHERE location_id = v_locid;
...
OPEN is an executable statement that performs these operations:
1. it dynamically allocates memory for a context area
2. it parses the SELECT statement
3. it binds the input variables and sets the values for the input variables by obtaining their
memory addresses
4. it identifies the active set the set of rows that satisfy the search criteria rows in the
active set are not retrieved into variables when the OPEN statement is executed, the
FETCH statement retrieves the rows from the cursor to the variables
5. it positions the pointer to the first row in the active set
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
...
BEGIN
OPEN c_emp_cursor;

Note
If a query returns no rows when the cursor is opened, PL/SQL does not raise an
exception. You can find out the number of rows returned with an explicit cursor by
using the <cursor_name>%ROWCOUNT attribute.
The FETCH statement retrieves the rows from the cursor one at a time. After each fetch,
the cursor advances to the next row in the active set. You can use the %NOTFOUND
attribute to determine whether the entire active set has been retrieved.

Consider this sample code to fetch data from the cursor. In this code, two variables,
v_empno and v_lname, are declared to hold the fetched values from the cursor.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
v_empno employees.employee_id%TYPE;
v_lname employees.last_name%TYPE;
BEGIN
OPEN c_emp_cursor;
FETCH c_emp_cursor INTO v_empno, v_lname;
DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname);
END;
/
This is the output of the sample code. You have successfully fetched the values from the
cursor to the variables. However, there are six employees in department 30, but only one
row is fetched. To fetch all rows, you must use loops.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
v_empno employees.employee_id%TYPE;
v_lname employees.last_name%TYPE;
BEGIN
OPEN c_emp_cursor;
FETCH c_emp_cursor INTO v_empno, v_lname;
DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname);
END;
/
--Output-anonymous block completed
114 Raphaely
The FETCH statement performs these operations:

it reads the data for the current row into the output PL/SQL variables

it advances the pointer to the next row in the active set


You can include the same number of variables in the INTO clause of the FETCH
statement as there are columns in the SELECT statement. You need to be sure that the
data types are compatible and match each variable to correspond to the columns
positionally.

Alternatively, you can define a record for the cursor and reference the record in the
FETCH INTO clause. Then, you need to test to find out whether the cursor contains rows.
If a fetch acquires no values, there are no rows left to process in the active set and no
error is recorded.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
v_empno employees.employee_id%TYPE;
v_lname employees.last_name%TYPE;
BEGIN
OPEN c_emp_cursor;
FETCH c_emp_cursor INTO v_empno, v_lname;
DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname);
END;
/
This sample code uses a simple LOOP to fetch all the rows. Also, the cursor attribute
%NOTFOUND is used to test for the exit condition.
This is the output of the PL/SQL block.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
v_empno employees.employee_id%TYPE;
v_lname employees.last_name%TYPE;
BEGIN
OPEN c_emp_cursor;
LOOP
FETCH c_emp_cursor INTO v_empno, v_lname;
EXIT WHEN c_emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname);
END LOOP;
END;
/
--Output-anonymous block completed
114 Raphaely
115 Khoo
116 Baida

117 Tobias
118 Himuro
119 Colmenares
The CLOSE statement disables the cursor, releases the context area, and undefines the
active set. You should close the cursor after completing the FETCH statement processing.
You can reopen the cursor if required. A cursor can be reopened only if it is closed. If you
attempt to fetch data from a cursor after it is been closed, then an INVALID_CURSOR
exception is raised.
...
LOOP
FETCH c_emp_cursor INTO empno, lname;
EXIT WHEN c_emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_empno ||' '||v_lname);
END LOOP;
CLOSE c_emp_cursor;
END;
/

Note
Although it is possible to terminate the PL/SQL block without closing cursors, you
should ensure to close any cursor that you declare explicitly to free up resources.
There is a maximum limit on the number of open cursors per session, which is
determined by the OPEN_CURSORS parameter in the database parameter file
OPEN_CURSORS = 50 by default.

3. Processing explicit cursors


You can define records that have the structure of columns in a table. You can also define
a record based on the selected list of columns in an explicit cursor. This is convenient for
processing the rows of the active set, because you can simply fetch into the record.
Therefore, the values of the row are loaded directly into the corresponding fields of the
record.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
v_emp_record c_emp_cursor%ROWTYPE;
BEGIN
OPEN c_emp_cursor;

LOOP
FETCH c_emp_cursor INTO v_emp_record;
EXIT WHEN c_emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_emp_record.employee_id
||' '||v_emp_record.last_name);
END LOOP;
CLOSE c_emp_cursor;
END;
You can fetch data from cursors by using a simple loop, however, you can also use the
FOR cursor loop to process rows in an explicit cursor. It is a shortcut because the cursor is
opened, a row is fetched once for each iteration in the loop, the loop exits when the last
row is processed, and the cursor is closed automatically. The loop itself is terminated
automatically at the end of the iteration where the last row is fetched. In the FOR cursor
loop, the record is implicitly declared.
In the syntax for declaring a cursor FOR loop, record_name is the name of the implicitly
declared record and the cursor_name is a PL/SQL identifier for the previously declared
cursor.
FOR record_name IN cursor_name LOOP
statement1;
statement2;
...
END LOOP;
You need to consider these guidelines for declaring a cursor FOR loop:

do not declare the record that controls the loop; it is declared implicitly

test the cursor attributes during the loop, if required

supply the parameters for a cursor, if required, in parentheses following the cursor name in the
FOR statement
The example code that was used to show a simple loop to fetch the data from the cursors
has been rewritten here to use the cursor FOR loop. In this code, the emp_record is the
record that is implicitly declared. You can access the fetched data with this implicit record.
In the code, no variables are declared to hold the fetched data using the INTO clause.
The code does not have OPEN and CLOSE statements to open and close the cursor,
respectively.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id =30;
BEGIN

FOR emp_record IN c_emp_cursor


LOOP
DBMS_OUTPUT.PUT_LINE( emp_record.employee_id
||' ' ||emp_record.last_name);
END LOOP;
END;
/
In this code, note that there is no declarative section in this PL/SQL block. The difference
between the cursor FOR loops using subqueries and the cursor FOR loop lies in the cursor
declaration. If you are writing cursor FOR loops using subqueries, you need not declare
the cursor in the declarative section. You have to provide the SELECT statement that
determines the active set in the loop itself. This sample code is used to illustrate a cursor
FOR loop using subqueries.
BEGIN
FOR emp_record IN (SELECT employee_id, last_name
FROM employees WHERE department_id =30)
LOOP
DBMS_OUTPUT.PUT_LINE( emp_record.employee_id
||' '||emp_record.last_name);
END LOOP;
END;
/

Note
You cannot reference explicit cursor attributes if you use a subquery in a cursor
FOR loop because you cannot give the cursor an explicit name.
As with implicit cursors, there are explicit cursor attributes for obtaining status information
about a cursor. When appended to the cursor variable name, these attributes return
useful information about the execution of a cursor manipulation statement.

Note
You cannot reference cursor attributes directly in a SQL statement.
These are the four explicit cursor attributes for obtaining status information about a
cursor:

%ISOPEN

%NOTFOUND

%FOUND

%ROWCOUNT

%ISOPEN
The %ISOPEN attribute is Boolean and evaluates to TRUE if the cursor is open.
%NOTFOUND
The %NOTFOUND attribute evaluates to TRUE if the most recent fetch does not return a row
and is of Boolean type.
%FOUND
The %FOUND evaluates to TRUE if the most recent fetch returns a row. It is a complement of
%NOTFOUND and is of Boolean type.
%ROWCOUNT
The %ROWCOUNT attribute evaluates to the total number of rows returned so far and is of
Number type.
Using the %ISOPEN attribute, you can fetch rows only when the cursor is open. You can
use the %ISOPEN cursor attribute before performing a fetch to determine whether the
cursor is open. The %ISOPEN cursor attribute fetches rows in a loop and determines
when to exit the loop.
This is a sample code that uses the %ISOPEN cursor attribute.
IF NOT c_emp_cursor%ISOPEN THEN
OPEN c_emp_cursor;
END IF;
LOOP
FETCH c_emp_cursor...

Note
%ISOPEN returns the status of the cursor as TRUE if open and FALSE if not.
You can use the %ROWCOUNT cursor attribute to

process an exact number of rows

fetch the rows in a loop and determine when to exit the loop
In the example, the first ten employees are retrieved one by one. The %ROWCOUNT and
%NOTFOUND attributes can be used for exit conditions in a loop.
This is the output of the PL/SQL block.
DECLARE
CURSOR c_emp_cursor IS SELECT employee_id,
last_name FROM employees;

v_emp_record c_emp_cursor%ROWTYPE;
BEGIN
OPEN c_emp_cursor;
LOOP
FETCH c_emp_cursor INTO v_emp_record;
EXIT WHEN c_emp_cursor%ROWCOUNT > 10 OR
c_emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_emp_record.employee_id
||' '||v_emp_record.last_name);
END LOOP;
CLOSE c_emp_cursor;
END;
/
--Output-anonymous block completed
100 King
101 Kochhar
102 De Haan
103 Hunold
104 Ernst
105 Austin
106 Pataballa
107 Lorentz
108 Greenberg
109 Faviet

Question
Which statements are true about fetching data from a cursor?
Options:
1.

Each variable must correspond to a column positionally

2.

The FETCH statement can retrieve multiple cursor rows at a time

3.

You cannot define a record for a cursor for fetching

4.

You can use the %NOTFOUND attribute to determine whether the entire cursor has
been processed

Answer
Each variable must correspond to a column positionally, and you can use the
%NOTFOUND attribute to determine the processing of the entire cursor.
Option 1 is correct. Each variable used to fetch data must correspond to the
columns in size, type and position.
Option 2 is incorrect. The FETCH statement retrieves the rows from the cursor one
at a time. After each fetch, the cursor advances to the next row in the active set.
Option 3 is incorrect. You can define a record for the cursor and reference the
record in the FETCH INTO clause.
Option 4 is correct. You can use the %NOTFOUND attribute to determine whether
the entire active set has been retrieved. Once the entire cursor has been
processed, the %NOTFOUND attribute returns TRUE. It returns FALSE if the cursor
is still processing.

Question
Which statements about explicit cursor attributes are true?
Options:
1.

%ISOPEN returns a Boolean value

2.

%ROWCOUNT allows you to process a fixed amount of rows

3.

%ROWCOUNT does not allow you to determine when to exit a loop

4.

Using the %ISOPEN attribute, you can access a cursor even if it has not yet been
opened

Answer
%ISOPEN returns a Boolean value, and %ROWCOUNT allows you to process a fixed
amount of rows.
Option 1 is correct. The %ISOPEN attribute determines whether or not a cursor is
open, returning TRUE if it is open and FALSE if it is not.
Option 2 is correct. The %ROWCOUNT attribute can be used to fetch and process
cursor rows within a loop, which can be limited to an exact amount of rows.

Option 3 is incorrect. The %ROWCOUNT attribute can be used for the exit condition
of a loop. %ROWCOUNT evaluates the total number of rows returned so far.
Option 4 is incorrect. Using %ISOPEN, you can fetch rows only when the cursor is
open. You use the %ISOPEN cursor attribute to determine whether the cursor is
open.

Question
Which statement accurately describes cursor FOR loops?
Options:
1.

FOR loops are useful in processing implicit cursors

2.

FOR loops must be terminated explicitly after the cursor has finished processing

3.

The cursor is closed automatically when the FOR loop completes

4.

You must declare the record that controls the FOR loop

Answer
The cursor is closed automatically when the FOR loop completes.
Option 1 is incorrect. Cursor FOR loops process the rows in an explicit cursor.
Option 2 is incorrect. The loop is terminated automatically at the end of the
iteration in which the last row is fetched.
Option 3 is correct. When a cursor FOR loop runs, the cursor is opened and a row
is fetched once for each iteration in the loop. The cursor is then closed
automatically once the loop completes.
Option 4 is incorrect. You do not declare the record that controls the loop. It is
declared implicitly.

Summary
Every executed SQL statement by Oracle server has an associated individual cursor,
which can be an implicit or explicit cursor. The Oracle server uses work areas to execute
SQL statements and store processing information. PL/SQL implicitly declares a cursor for
all SQL data manipulation statements, including queries that return only one row.
For queries that return more than one row, you must explicitly declare a cursor to process
the rows individually. You can use simple loops or cursor FOR loops to operate on the
multiple rows fetched by the cursor. To declare a simple explicit cursor, you have to open,

fetch, and close the cursor however, cursor FOR loops do this implicitly.
Every explicit cursor and cursor variable has %FOUND, %ISOPEN, %NOTFOUND, and
%ROWCOUNT attributes. When appended to the cursor variable name, these attributes
return useful information about the execution of a SQL statement.

Table of Contents
| Top of page |
| Learning objective |
| 1. Understanding explicit cursor operations |
| 2. Declaring explicit cursors |
| 3. Processing explicit cursors |
| Summary |
Copyright 2008 SkillSoft. All rights reserved.
SkillSoft and the SkillSoft logo are trademarks or registered trademarks
of SkillSoft in the United States and certain other countries.
All other logos or trademarks are the property of their respective owners.

| Print | Contents | Close |

Using Cursors with Parameters


Learning objective

After completing this topic, you should be able to recognize the steps for declaring and
using cursors with parameters, locking rows, and referencing the current row in an
explicit cursor.

1. Using cursors with parameters


You can pass parameters to a cursor when the cursor is opened and the query is
executed. This means that you can open and close an explicit cursor several times in a
block, returning a different active set on each occasion. For each execution, the previous
cursor is closed and reopened with a new set of parameters.
Here is the syntax for cursor declaration using parameters.
CURSOR cursor_name
[(parameter_name datatype, ...)]

IS
select_statement;
These are the components in the syntax for cursor declaration.

cursor_name

parameter_name

datatype

select_statement
cursor_name
cursor_name is a PL/SQL identifier for the declared cursor.
parameter_name
parameter_name is the name of a parameter.
datatype
datatype is the scalar data type of the parameter.
select_statement
select_statement is a SELECT statement without the INTO clause.
Each formal parameter in the cursor declaration must have a corresponding actual
parameter in the OPEN statement. The parameter notation does not offer greater
functionality; it simply allows you to specify input values easily and clearly. This is
particularly useful when the same cursor is referenced repeatedly.
OPEN cursor_name(parameter_value,.....) ;
Parameter data types are the same as those for scalar variables, but you do not give
them sizes. The parameter names are for references in the query expression of the
cursor.
In this sample code, a cursor is declared and is defined with one parameter.
DECLARE
CURSOR c_emp_cursor (deptno NUMBER) IS
SELECT employee_id, last_name
FROM employees
WHERE department_id = deptno;
...
BEGIN
OPEN c_emp_cursor (10);
...
CLOSE c_emp_cursor;
OPEN c_emp_cursor (20);
...

In the sample code, the OPEN statements open the cursor and return different active sets.
You can also pass parameters to the cursor that is used in a cursor FOR loop.
DECLARE
CURSOR c_emp_cursor(p_deptno NUMBER, p_job VARCHAR2)IS
SELECT ...
BEGIN
FOR emp_record IN c_emp_cursor(10, 'Sales') LOOP ...

Question
Which statements are true about using cursors with parameters?
Options:
1.

An explicit cursor may only be opened once

2.

Any parameter declared in a cursor must have a corresponding actual parameter in


the OPEN statement

3.

Parameter data types are the same as those for scalar variables

4.

You may pass a parameter to a cursor even before it has been opened

Answer
When using cursors with parameters, any parameter declared in a cursor must
have a corresponding actual parameter in the OPEN statement. Parameter data
types are the same as those for scalar variables.
Option 1 is incorrect. You can open and close an explicit cursor several times in a
block, returning a different active set on each occasion. For each execution, the
previous cursor is closed and reopened with a new set of parameters.
Option 2 is correct. Each formal parameter in the cursor declaration must have a
corresponding actual parameter in the OPEN statement. For example, if a number
parameter has been declared in the cursor declaration, there must be a variable
declared in the PL/SQL block that corresponds to this parameter in type; so it can
be handed to the cursor when it is processed.
Option 3 is correct. Parameter data types are the same as those for scalar
variables, but you do not give them sizes. The parameter names are for
references in the query expression of the cursor.
Option 4 is incorrect. You may pass parameter values to a cursor when the cursor
is opened and the query is executed.

2. Locking and referencing rows


If there are multiple sessions for a single database, there is the possibility that the rows of
a particular table were updated after you opened the cursor. You see the updated data
only when you reopen the cursor. Therefore, you should use explicit locking to deny
access to other sessions for the duration of a transaction.
It is better to have locks on the rows before you update or delete rows. You can lock the
rows with the FOR UPDATE clause in the cursor query.
The FOR UPDATE clause is the last clause in a SELECT statement. It is placed even after
the ORDER BY clause if that clause exists. When querying multiple tables, you can use
the FOR UPDATE clause to confine row locking to particular tables. FOR UPDATE OF
col_name(s) locks rows only in tables that contain col_name(s).
SELECT ...
FROM ...
FOR UPDATE [OF column_reference][NOWAIT | WAIT n];
These are the components in the syntax for the FOR UPDATE clause.

column_reference

NOWAIT

WAIT n
column_reference
column_reference is a column in the table against which the query is performed. A list
of columns may also be used.
NOWAIT
The NOWAIT keyword returns an Oracle server error if the rows are locked by another
session. This optional keyword tells the Oracle server not to wait if requested rows have
been locked by another user. Control is immediately returned to your program so that it
can do other work before trying again to acquire the lock. If you omit the NOWAIT keyword,
the Oracle server waits until the rows are available.
WAIT n
You can use WAIT instead of NOWAIT, specify the number of seconds to wait, and
determine whether the rows are unlocked. If the rows are still locked after n seconds, an
error is returned.
The SELECT ... FOR UPDATE statement identifies the rows that are to be updated or
deleted, and then locks each row in the result set. This is useful when you want to base
an update on the existing values in a row. In this case, you must ensure that the row is
not changed by another session before the update.

If the Oracle server cannot acquire the locks on the rows that it needs in a SELECT FOR
UPDATE operation, it waits indefinitely. You can use NOWAIT to handle such situations. If
the rows are locked by another session and you have specified NOWAIT, opening the
cursor results in an error. You can try to open the cursor later.
DECLARE
CURSOR c_emp_cursor IS
SELECT employee_id, last_name FROM employees
WHERE department_id = 80 FOR UPDATE OF salary NOWAIT;
...
It is not mandatory for the FOR UPDATE OF clause to refer to a column, but it is
recommended for better readability and maintenance.
To refer to the current row in an explicit cursor, you can use the WHERE CURRENT OF
clause in conjunction with the FOR UPDATE clause.
In the syntax, cursor is the name of a declared cursor that must have been declared with
the FOR UPDATE clause.
WHERE CURRENT OF cursor ;
You use cursors to update or delete the current row. The WHERE CURRENT OF clause is
used in the UPDATE or DELETE statement, whereas the FOR UPDATE clause is specified
in the cursor declaration.
This sample code uses the WHERE CURRENT OF clause in an UPDATE statement.
UPDATE employees
SET salary = ...
WHERE CURRENT OF c_emp_cursor;
You can use a combination of the WHERE CURRENT OF clause and the FOR UPDATE
clause for updating and deleting the current row from the corresponding database table.
This enables you to apply updates and deletes to the row currently being addressed,
without the need to explicitly reference the row ID.
You must include the FOR UPDATE clause in the cursor query so that the rows are locked
on OPEN.
You can also use subqueries with cursors. A subquery is a query, usually enclosed by
parentheses, that appears within another SQL statement. When evaluated, the subquery
provides a value or set of values to the outer query.
Subqueries are often used in the WHERE clause of a SELECT statement. They can also be
used in the FROM clause, creating a temporary data source for that query.

In this sample code, the subquery creates a data source consisting of department
numbers and the number of employees in each department, known by the alias STAFF. A
table alias, t2, refers to this temporary data source in the FROM clause.
When this cursor is opened, the active set contains the department number, department
name, and total number of employees working for departments that have three or more
employees.
DECLARE
CURSOR my_cursor IS
SELECT t1.department_id, t1.department_name,
t2.staff
FROM departments t1, (SELECT department_id,
COUNT(*) AS staff
FROM employees
GROUP BY department_id) t2
WHERE t1.department_id = t2.department_id
AND t2.staff >= 3;
...
You are writing a PL/SQL block that declares and uses cursors with parameters. Inside a
loop, you want to use a cursor to retrieve the department number and department name
from the DEPARTMENTS table for a department whose DEPARTMENT_ID is less than 100.
You then want to pass this department number to another cursor as a parameter.
You want to do this to retrieve the last name, job, hire date, and salary of the employees
in the EMPLOYEES table whose EMPLOYEE_ID is less than 120 and who work in the
specified department.
Here are the steps to write the PL/SQL block to perform this task using cursors with
parameters.
Step 1: Declare a cursor named C_DEPT_CURSOR to retrieve the department ID and
department name for departments with an ID that is less than 100. Order the cursor by
DEPARTMENT_ID.
Step 2: Declare another cursor c_emp_cursor that takes the department number as a
parameter and retrieves LAST_NAME, JOB_ID, HIRE_DATE, and SALARY of employees
with employee_id less than 120 and who work in that department.
Step 3: Declare variables to hold the values retrieved from each cursor, and use the
%TYPE attribute when declaring variables.
Here are the remainder of the steps to complete this task of declaring and using the
cursors with parameters.

Step 4: Open c_dept_cursor, use a simple loop, and fetch values into the variables
declared. Display the department number and department name.
Step 5: For each department, open c_emp_cursor by passing the current department
number as a parameter. Start another loop and fetch the values of emp_cursor into
variables and print all the details retrieved from the EMPLOYEES table. To print a line after
you have displayed the details of each department, use appropriate attributes for the exit
condition and check whether a cursor is already open before opening the cursor.
Step 6: Close all loops and cursors, end the executable section, and execute the script by
clicking Run Script.
This is the sample output of the code.

Question
Which statements are true about the FOR UPDATE clause?
Options:
1.

FOR UPDATE OF must refer to a column

2.

FOR UPDATE locks rows that are being affected by a transaction

3.

NOWAIT can be used in conjunction with FOR UPDATE because it enables the
cursor to wait for locked rows to become available

4.

The Oracle server waits indefinitely for locked rows

Answer
The FOR UPDATE clause locks rows that are being affected by a transaction, and
the Oracle server waits indefinitely for these locked rows.
Option 1 is incorrect. It is not mandatory for the FOR UPDATE OF clause to refer
to a column, but it is recommended for better readability and maintenance.
Option 2 is correct. If there are multiple sessions for a single database, there is the
possibility that the rows of a particular table are updated after you open the cursor.
You see the updated data only when you reopen the cursor. Therefore, it is better
to have locks on the rows before you update or delete rows. You can lock the rows
with the FOR UPDATE clause in the cursor query.
Option 3 is incorrect. The NOWAIT keyword tells the Oracle server not to wait if the
requested rows have been locked by another user. If the rows are locked by
another session and you have specified NOWAIT, opening the cursor results in an
error.

Option 4 is correct. If the Oracle server cannot acquire the locks on the rows that it
needs in a SELECT FOR UPDATE operation, it waits indefinitely.

Question
Identify the true statements about the WHERE CURRENT OF clause.
Options:
1.

FOR UPDATE is used in the actual UPDATE or DELETE statement whereas WHERE
CURRENT OF is used in the cursor declaration

2.

FOR UPDATE must be included in the cursor query

3.

When using FOR UPDATE and WHERE CURRENT OF in conjunction, any affected
row ID must be referenced explicitly

4.

WHERE CURRENT OF can be used with FOR UPDATE

Answer
When using the WHERE CURRENT OF clause, you must include the FOR UPDATE
clause in the cursor query. You can use the WHERE CURRENT OF clause with the
FOR UPDATE clause.
Option 1 is incorrect. The WHERE CURRENT OF clause is used in the UPDATE or
DELETE statement, whereas the FOR UPDATE clause is specified in the cursor
declaration.
Option 2 is correct. You must include the FOR UPDATE clause in the cursor query
so that the rows are locked on OPEN.
Option 3 is incorrect. You may apply updates and deletes to the row currently
being addressed without the need to explicitly reference the row ID.
Option 4 is correct. You can use WHERE CURRENT OF and FOR UPDATE for
updating and deleting the current row from the corresponding database table.

Summary
You can pass parameters to a cursor when the cursor is open and the query is executed.
For each execution, the previous cursor is closed and reopened with a new set of
parameters. Each formal parameter in the cursor declaration must have a corresponding
actual parameter in the OPEN statement. You can pass parameters to the cursor that is
used in a cursor FOR loop.
If you are updating or deleting rows, you lock the rows by using a FOR UPDATE clause.

This ensures that the data you are using is not updated by another session after you
open the cursor. You use a WHERE CURRENT OF clause in conjunction with the FOR
UPDATE clause to reference the current row fetched by the cursor.

Table of Contents
| Top of page |
| Learning objective |
| 1. Using cursors with parameters |
| 2. Locking and referencing rows |
| Summary |
Copyright 2008 SkillSoft. All rights reserved.
SkillSoft and the SkillSoft logo are trademarks or registered trademarks
of SkillSoft in the United States and certain other countries.
All other logos or trademarks are the property of their respective owners.

Das könnte Ihnen auch gefallen