Вы находитесь на странице: 1из 343

Oracle 10g PLSQL

This book belongs to

Name

: ______________________________________

Batch

: ______________________________________

SQL STAR INTERNATIONAL LTD.


SQL Star House,
No. 8-2-293/174/A 25,
Road No. 14, Banjara Hills,
Hyderabad - 500 034,
Andhra Pradesh, INDIA

SQL Star International Ltd.

Copyright 2008
Second Edition

SQL STAR INTERNATIONAL LIMITED


SQL Star House,8-2-293/174/A25, Road No.14,
Banjara Hills, Hyderabad - 500 034.
Tel. No. 91- 40-23101600(30 lines)
Fax No. 23101663
Toll Free No: 1800 425 2944

Email:

info@sqlstar.com

No part of this publication may be reproduced (incl. photocopying) in any way, without prior
agreement and written permission of SQL Star International Ltd., Hyderabad. SQL Star
International Ltd., Hyderabad assumes no responsibility for its use, nor for any infringements
of patents or other rights of third parties which could result.

Table of Contents
10g PL /SQL
CHAPTER
No.

CHAPTER TITLE

PAGE
NO

1.

Introduction to PL/SQL

1-25

2.

DMLs in PL/SQL

26-38

3.

Control Structure

39-59

4.

Composite DataTypes

5.

Cursors and advanced cursors

105-128

6.

Handling exception in PL/SQL

129-146

7.

Creating subprograms

147-170

8.

Managing subPrograms

171-184

9.

Creating Packages

185-204

10.

Using More Package Concepts

205-223

11.

Oracle Supplied Packages

224-250

12.

PL/SQL Performance Enhancements

251-277

13.

Large Objects

278-295

14.

Database Triggers

296-320

15.

Creating advanced Database Triggers

321-339

60-104

-3-

SQL Star International Ltd.

Chapter 1

Introduction to PL/SQL
Need for PL/SQL
Benefits of Using PL/SQL
PL/SQL Block Types and Constructs
PL/SQL Block Structure
Operators and SQL Functions in PL/SQL
Variables
Nested Blocks

1
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 State the need for a procedural language in Oracle
 Create PL/SQL blocks
 Write nested blocks

2
SQL Star International Ltd.

Introducing PL/SQL
In the journey so far, you have learnt about the Structured Query Language (SQL).
The Oracle software provides a language, Procedural Language/SQL (PL/SQL), which
is an extension of SQL.
PL/SQL implements modularity in the codes you write. It allows you to perform
iterations and handle errors. Features like data hiding and error handling make
PL/SQL a state-of-the-art language for databases.
PL/SQL also allows data manipulation and SQL query statements to be included
within procedural units of code. This makes PL/SQL a powerful transaction processing
mechanism.

Need for PL/SQL


The requirement for PL/SQL was mainly due to the need to centralize automated
business tasks. For example, if each employee in an organization maintains separate
programs to manage their tasks and updates them at their discretion, it could create
confusions, such as:

New employees or existing employees who are handed over someone elses
work, would have a problem understanding the process followed by
the employee vis--vis the organization

Any change in business policy or functionality would have to be updated by all


the employees individually in all relevant programs.

In a centralized functionality this is avoided because all changes need to be made at


one place. Changes will get reflected in the relevant codes and all users can access
updated information.

Benefits of Using PL/SQL


The reasons for using PL/SQL are as follows:
 Integrating with the Oracle server and Oracle development tools
 Implementing performance enhancements in an application
 Modularizing the development of programs
 Implementing Portability
 Declaration of variables
 Programming with procedural language control structures
 Handling errors

3
SQL Star International Ltd.

Integration
PL/SQL plays a key role in:
 Oracle server, through stored procedures and functions, database triggers and
packages
 Oracle development tools, through Oracle Developer component triggers
PL/SQL supports the use of SQL datatypes. With direct access provided by SQL,
these shared datatypes integrate PL/SQL with the Oracle server data dictionary.
Hence, PL/SQL successfully bridges the gap between access to database technology
and the need for procedural language capabilities.
Most applications such as Oracle Forms, Oracle Reports and Oracle Graphics, use
shared libraries (that hold code), which can be accessed locally or remotely. To
execute these stored codes, the Oracle tools have their own PL/SQL engine
(independent of the engine present in the Oracle server). The engine first filters out
the SQL statements to send them individually to the SQL statement executor in the
Oracle server. It then processes the remaining procedural statements in the
procedural statement executor in the PL/SQL engine. The procedural statement
executor processes data, which is already inside the client environment and not in
the database. This reduces the workload on the Oracle server and also the amount of
memory required.

Performance Enhancements of Applications


When SQL statements are sent to the Oracle server one at a time, each statement
results in a call to the Oracle server, leading to performance overhead and network
traffic. If your application is SQL intensive, then instead of sending SQL statements
individually to the server, you can put them into one block using PL/SQL and send
the entire block to the server at one time. This reduces network traffic.
PL/SQL also operates with Oracle development tools, thereby adding procedural
processing power to these tools and enhancing performance.

4
SQL Star International Ltd.

Modularized Program Development


PL/SQL programs are made up of one or more blocks. These blocks may be
individual ones or may be nested within another. That is, a block may represent a
small part of another block, which may in turn be part of a whole unit of code. The
units (procedures, functions or anonymous blocks) making up a PL/SQL program are
called logical blocks.

A diagrammatic representation of modularization is:

The advantages associated with modularized development of programs are:


 Logical groupings of related statements within blocks
 Nesting blocks within larger blocks help build powerful programs
 Breaking down complex problems into manageable sets of logical, well-defined
modules, which can be implemented within blocks

Portability
PL/SQL is portable. That means, PL/SQL programs can be run wherever the Oracle
server exists. There is no need to tailor them to suit each new environment. This is
achieved because PL/SQL is native to the Oracle server, and therefore can be moved
to any environment (operating system or platform) that supports the Oracle server.
PL/SQL code can also be moved between the Oracle server and Oracle Developer
applications by writing programs and creating libraries in different environments.

5
SQL Star International Ltd.

Variable Declaration
In PL/SQL, you can declare variables:
 To use them in SQL and procedural statements
 Belonging to different data types
 Dynamically based on the structure of tables and columns in the database

Programming with Procedural Language Control Structures


PL/SQL allows the usage of control structures, which enable you to execute:
 Sequence of statements conditionally
 Sequence of statements iteratively in a loop
 Individually the rows returned by multiple-row query
Handling Errors
PL/SQL implements error handling functionality by:
 Processing Oracle server errors with error handling routines
 Declaring your own error conditions and process them with error handlers

PL/SQL Block Types and Constructs


There are two block types and different types of constructs in PL/SQL. A block is a
PL/SQL code. A construct is the way in which a block is written to implement
different functionality.
Block Types
Logical blocks are the basic units of code that make up a PL/SQL program. The two
kinds of PL/SQL blocks are:
 Anonymous blocks
 Named blocks or Subprograms
Anonymous Blocks
Anonymous blocks are declared at that point in an application from where they are
to be executed and are passed to the PL/SQL engine for execution at runtime. These
blocks are unnamed blocks. They can be embedded in the iSQL*Plus environment.
An application trigger consists of these blocks.
Subprograms
Unlike anonymous blocks, named blocks or subprograms are given a name. These
blocks can be invoked for execution and they can also accept parameters.

6
SQL Star International Ltd.

Subprograms can be declared as Procedures or Functions. You declare a subprogram


as a Procedure to perform an action, and you declare a subprogram as a Function to
compute a value. Subprograms can be written and stored either at the server side or
at the client side.
Constructs
There are various PL/SQL program constructs available that use the basic PL/SQL
block. The availability of program constructs depends on the environment in which
they are executed.

The different program constructs are given below.

7
SQL Star International Ltd.

PL/SQL Block Structure


To write a PL/SQL block, you need to know the different parts of a PL/SQL block and
what each part should hold. The structure of all PL/SQL blocks is the same. The only
difference is that, if it is an anonymous block it is not given a name.
A PL/SQL block has three sections . They are :
 Declarative section
 Executable section
 Exception handling section
The declarative section is where the variables and constants used in the body of the
block are declared. Any cursors or user-defined error handlers that are used in the
body are declared here. This section is optional.
The executable section holds the set of statements or the logic of the tasks that are
to be performed. You can include SQL statements to make changes to the database
and PL/SQL statements to manipulate data in the block. The statements in this block
are enclosed within the keywords BEGIN and END. This section is mandatory.
The last section of a block is the exception section. Here a set of statements is
written to handle any errors that might occur when the statements in the executable
section are being executed. This section is optional .

8
SQL Star International Ltd.

The diagramatic expression of PL/SQL block structure is given below.

The syntax for writing a PL/SQL block is:


DECLARE
<variable name> datatype(size);
BEGIN
SQL statements;
PL/SQL statements;
EXCEPTION
WHEN <exception name> THEN

END;
/
Some points that will help you write a block:


A line of PL/SQL text which contains group of characters known as lexical


units. These units are classified as follows:

 Delimiters are simple or compound symbols that have a special meaning to


PL/SQL. The following table presents both the simple as well as the compound
symbols:

9
SQL Star International Ltd.

 Identifiers are used to name PL/SQL program constructs such as Constants,


Variables and Exceptions. The following points need to be kept in mind while
using identifiers:

Identifiers can be up to 30 characters in length, but ensure they start


with an alphabet.

Do not give the same name for the identifiers as the name of columns
in a table used in the block. If so, then the Oracle server assumes that
the table column is being referred.

An identifier consists of a letter, followed (optional) by other letters,


numerals, underscores, dollar signs and number signs. The following
characters are however illegal:
Abc&efg illegal ampersand
Abc-efg illegal hyphen
Abc/efg illegal slash
Abc efg illegal space

Identifiers should not be reserved words except when they are used
within double quotes, for instance INSERT.

 Literals are made up of definite values such as character, numeric, string or


Boolean values, not represented by identifiers. These are case sensitive.

10
SQL Star International Ltd.

Literals are of two types:

Character literals: are all the printable characters such as letters,


numerals, spaces, and special symbols. Specify character literals in
single quotes.

Numeric literals: are represented either by a simple value such as 12


or by a scientific notation such as 3E6.

 Comments are extra information given by the programmer to improve


readability
documenting in each phase and debugging.

in a code, to enable

PL/SQL code can be commented in two ways:

Single line comment represented by --

Multi line comment represented by /*

*/

Use a semicolon (;) at the end of all SQL and PL/SQL control statements and
the
END keyword.

Do not use semicolons after the keywords DECLARE, BEGIN and EXCEPTION.

Increase the readability of the block by writing the keywords in uppercase.

Use a slash (/) to terminate a PL/SQL block on a line by itself.

Using Quotes in Literals


Oracle 10g allows you to define your own string delimiters to remove the need to
double up any single quotes.
SET SERVEROUTPUT ON
BEGIN
--original code
DBMS_OUTPUT.PUT_LINE(He is New Jersey Library
s Member!);
-- using quote symbol
DBMS_OUTPUT.PUT_LINE(q# He is New Jersey Librarys
Member!#);

DBMS_OUTPUT.PUT_LINE(q[ He is New Jersey


Librarys
Member !]);
11
SQL Star International Ltd.

END;
/
He is New Jersey Librarys Member!
He is New Jersey Librarys Member!
He is New Jersey Librarys Member !
PL/SQL procedure successfully completed

Operators in PL/SQL
The following operators are supported in PL/SQL:
 Arithmetic
 Logical
 Relational or Comparison
 Concatenation
 Exponentiation [Represented by (**)]

SQL Functions in PL/SQL Statements


All the SQL functions can also be used in procedural statements in a block.
These include:


Single-row number and character functions

Datatype conversion functions

Date functions

Timestamp functions

GREATEST and LEAST functions

The functions that cannot be used in procedural statements are:





DECODE
Group functions like AVG, MIN, MAX, COUNT, SUM, STDDEV and
VARIANCE. These functions work only on a group of rows in a table and
hence, they can be used only with the SQL statements in a PL/SQL block.

Variables
A variable are named memory locations used to store data temporarily. The data
stored in variables is used in the blocks and then processed. When the processing is
completed, the data held in the variables may be written to the database or simply
erased in case of session wide variables.
Why is a Variable used?
A Variable stores data temporarily. It manipulates the stored data and performs
calculations with the data without accessing the database. Once declared, variables
can be used repeatedly in an application by referencing them in other statements in
the block.

12
SQL Star International Ltd.

When variables are declared using %TYPE and %ROWTYPE (more information is
provided later in the chapter), you are infact basing the variable declaration on the
column definition of a table. In this case, if the column definition changes, then the
variable declarations also changes accordingly. This helps in data independence,
reduces maintenance cost and allows the program to adjust to the new business
logic.
How to handle variables in PL/SQL?
In a PL/SQL block, variables are:


Declared and initialized in the declarative section of the block.

Assigned new values in the executable section. On doing so, the existing
value is replaced with the newly assigned value. Care must be taken to see
that a variable being referred to is already declared in the declarative section.
Forward references cannot be made.

Types of Variables
Variables are of two types. They are:


PL/SQL variables

Non-PL/SQL variables

PL/SQL Variables
PL/SQL variables have a data type that specifies a storage format, constraints and
also valid range of values.
The data types used to declare PL/SQL variables are:
 Scalar data types are those that correspond to database column types. These
data types hold only a single value. The base scalar data types include:
 CHAR
 VARCHAR2
 LONG
 LONG RAW
 NUMBER
 BINARY_INTEGER
 PLS_INTEGER

 BINARY_FLOAT
 BINARY_DOUBLE
 BOOLEAN
 DATE
 TIMESTAMP
 TIMESTAMP WITH TIMEZONE
 TIMESTAMP WITH LOCAL TIMEZONE
 INTERVAL YEAR TO MONTH
 INTERVAL DAY TO SECOND
Binary_Float and Binary_Double are the two new Datatypes introduced in
Oracle10g.They represent floating point numbers in IEEE 754 format (Institute of

13
SQL Star International Ltd.

Electrical and Electronic Engineers) and require 5 byte and 9 bytes to store the
values respectively. IEEE format s supported by most of the computer system
operating through native processor instructions thereby helping us to carry out
complex computations using floating point data.

 Composite data types are those that are defined by users. They enable you to
manipulate groups of data in PL/SQL blocks
 Reference data types are those that hold values pointing to other objects.
These are also known as pointers.
 LOB (large object) data types: are those that hold values, which specify the
location of large objects such as graphic images. These values are known as locators.
Large objects are stored in the same database as the table but not within the table.
LOB data type allows you to store unstructured data of a maximum of 8-128
terabytes. This data could be a movie clip or a graphic image or a sound wave form.
LOBs are further classified into:





CLOB (Character Large Objects) is used to store large blocks of character


data of a single byte.
BLOB (Binary Large Object) is used to store large binary objects within the
database.
BFILE (Binary File) is used to store large binary objects that are in the
operating system files outside the database.
NCLOB (National Language Character Large Objects), are used to store large
blocks of NCHAR data that may be single-byte or fixed-width multiple bytes

The syntax for declaring PL/SQL variables is:


identifier [CONSTANT] datatype [NOT NULL] [:= | DEFAULT expr];
Where,
identifier is the name assigned to the variable declared.
CONSTANT specifies a constraint that the value of the variable cannot change.
Constant variables must be initialized. While declaring a variable as a constant, the
CONSTANT keyword must precede the datatype specification.
DATATYPE specifies the type of data the variable can hold. It could be scalar,
composite, reference or LOB datatype.
NOT NULL specifies a constraint that the variable must contain a value. Therefore,
NOT NULL variables must be initialized.
:= is the assignment operator used to assign an expression to a variable. Instead of
the assignment operator, the DEFAULT expr (expression) can be used to assign
values to the variables.
By default, all variables are initialized to NULL. To prevent null values, variables are
initialized using the DEFAULT keyword.

14
SQL Star International Ltd.

The following code snippet shows how PL/SQL variables are declared in the
declarative section of a block:
DECLARE
FirstName CHAR(20);
BranchID CHAR(7) NOT NULL: = 09RANNJ;
FeeAmt CONSTANT NUMBER(2): = 15;
CatgName CHAR(15) DEFAULT Fiction;
% TYPE Attribute
If you need to store a database column value in a variable or write a value from a
variable to a database column, then the data type of the variable needs to be the
same as that of the database column. You can use the %TYPE attribute to declare a
variable to be of the same data type as that of a previously declared variable or
database column.
Incorrect variable data types generate PL/SQL errors during execution. When you
want to declare a variable using a table attribute instead of the datatype the syntax
is
<variable_name> table.columnname%TYPE
For example, in case you want to declare a variable that will store the address of a
library member, you will write in the following syntax:
DECLARE
Address VARCHAR2(45);
But, the above declaration will result in an error when an attempt is made to
populate the variable with address details of members from the database table. This
is because there is a mismatch in type specification of the variable. The datatype
width of the vAddress column in table Member is 50, but you have declared it as
45. To overcome this, declare the variable using %TYPE attribute as follows:
DECLARE
Address Member.vAddress%TYPE;
This statement declares a variable whose datatype and width is based on the
vAddress column.
If you want to declare a variable of the same type as a previously declared variable
then the syntax is
<variable_name> variable_name%TYPE
Using %TYPE attribute to declare a variable based on a previously declared variable,
is illustrated in the section dealing with the iSQL*Plus variables within PL/SQL blocks.

15
SQL Star International Ltd.

Datatype and Variable size is determined when the block is compiled. So even if
there is a change in the database column datatype the code manages the changed
datatype information.

Data Conversion Functions


In any programming language generally, we have to deal with different datatypes
simultaneously or receive data which is not in the default format. In such cases,
Oracle server implicitly converts data into valid datatypes wherever feasible. Explicit
conversions come into the scenario where automatic conversions are not possible.
Oracle Server takes care of implicit conversion between
1. Character and Number
2. Character and Date
But how is it done? Let us take an example.
DECLARE
cons_nfine NUMBER(3):=50;
cons_extra_fine VARCHAR2(20):=5';
tot_fine Transaction.nfine%TYPE;
BEGIN
tot_fine:= cons_nfine+cons_extra_fine;
DBMS_OUTPUT.PUT_LINE(The
total
fine
Rs.||tot_fine);
END;
/

payable

is

So, did you notice something?


Variable cons_nfine is of number datatype and cons_extra_fine is of VARCHAR2
datatype. While assigning the result to tot_fine variable, cons_extra_fine is
converted to number by PL/SQL executer and then the operation is performed.

16
SQL Star International Ltd.

To perform explicit conversion, following built in functions can be used.


to_char()
to_number()
to_date()
to_Binary_float()
to_Binary_double()

DECLARE
v_Date date:= to_Date( April 04 2007,Month dd YYYY);
BEGIN
DBMS_OUTPUT.PUT_LINE(You have entered
||v_Date || as
input);
END;
/
Example to show the usage of new datatypes.
DECLARE
l_binary_float
l_binary_double

BINARY_FLOAT;
BINARY_DOUBLE;

BEGIN
l_binary_float := 2.1f;
l_binary_double := 2.00001d;
DBMS_OUTPUT.PUT_LINE(l_binary_double);
DBMS_OUTPUT.PUT_LINE(l_binary_float);
l_binary_float := TO_BINARY_FLOAT(2.1);
l_binary_double := TO_BINARY_DOUBLE(2.00001);
DBMS_OUTPUT.PUT_LINE(l_binary_double);
DBMS_OUTPUT.PUT_LINE(l_binary_float);
END;
/

17
SQL Star International Ltd.

Non-PL/SQL Variables
Since PL/SQL has neither input nor output capabilities, it relies on the environment in
which it is executed in order to pass values into and out of a PL/SQL block.
The following non-PL/SQL variables can be used within PL/SQL blocks:



Substitution variables
Host variables

Substitution variables are those that you can use to pass values to a PL/SQL block at
runtime. To reference a substitution variable in a block, prefix it with an ampersand
(&). Before the block is executed the values for the variables are substituted with the
values passed.
Hence, you cannot input different values for the substitution variables using a loop.
The substitution variable can be replaced only by one value.
Here is an example showing the use of substitution variables within PL/SQL blocks.
Suppose, the library wants to calculate its quarterly income based on its annual
income. To do this, it declares a substitution variable, which would prompt the user
to enter the figure of annual income. Based on the value entered, the quarterly
income would be calculated.
DECLARE
AnnualIncome NUMBER (7): = &annualinc;
QuarterlyInc AnnualIncome%TYPE;
-declaring variable based on previously declared variable
using
--%TYPE attribute.
BEGIN
QuarterlyInc:= AnnualIncome/4;
DBMS_OUTPUT.PUT_LINE (The quarterly income of the library
is:
|| QuarterlyInc);
END;
/
In the above block, to display the quarterly income calculated, you can specify the
DBMS_OUTPUT.PUT_LINE in the PL/SQL block. DBMS_OUTPUT is an Oracle supplied
package and PUT_LINE is a procedure within it. To use this you need to specify the
information
you
want
printed,
in
parentheses,
following
the
DBMS_OUTPUT.PUT_LINE command as shown in the above code:

is:

DBMS_OUTPUT.PUT_LINE (The quarterly income of the library


|| QuarterlyInc);

For DBMS_OUTPUT.PUT_LINE to work, you need to run the iSQL*Plus command


SET SERVEROUTPUT ON.
iSQL*Plus host variables (also known as bind variables) are used to pass runtime
values from the PL/SQL block back to the iSQL*Plus environment. These variables
can be referenced in a PL/SQL block by placing a colon(:) before the variable.
The keyword VARIABLE is used to declare a bind variable. The syntax is:

18
SQL Star International Ltd.

VARIABLE <variable_name> datatype


The syntax to display the variable value is:
PRINT <variable_name>
Bind variables cannot be referred within the PL/SQL block of a function, procedure or
a package.
In a PL/SQL block, to differentiate between host variables and declared PL/SQL
variables, prefix the former with a colon(:).
The code wherein you had calculated the quarterly income using substitution
variables can be re-written using host variables as follows:

VARIABLE QuarterlyInc NUMBER


DECLARE
AnnualIncome NUMBER(7):= &AnnInc;
BEGIN
:QuarterlyInc:= AnnualIncome/4;
END;
/
old
2: AnnualIncome NUMBER(7):= &AnnInc;
new
2: AnnualIncome NUMBER(7):= 80000;
PL/SQL procedure successfully completed.
PRINT QuarterlyInc

QUARTERLYINC
-----------20000

The above example shows the Host variable being assigned a value inside a PL/SQL
block. To assign a value to a host variable outside the Pl/SQL block following can be
done:SQL > VARIABLE QuarterlyInc NUMBER
SQL > Exec :QuarterlyInc := 20000
SQL> PRINT QuarterlyInc
QUARTERLYINC
-----------20000
Where,

19
SQL Star International Ltd.

Exec stand for Execute privilege.

Nested Blocks
PL/SQL allows blocks to be nested wherever you can have an executable statement.
[This makes the nested block a statement.] Hence, the executable part of a block
can be broken down into smaller blocks. Even the exception section can contain
nested blocks.
Variable Scope
Issues that concern references to identifiers can be resolved taking into account their
scope and visibility.
By scope, we mean that region of a program unit from which an identifier can be
referenced. For instance, a variable in the declarative section can be referenced from
the exception section of that block.

The diagram shown below illustrates the concept of nested blocks and variable
scope.

20
SQL Star International Ltd.

By visibility, we mean the regions from which an identifier can be referenced without
using a qualified name.
The following code snippet shows how to qualify identifiers:
<<outer_blk>> --Block label
DECLARE --This is the parent block
JoiningDt DATE;
BEGIN
DECLARE --his is the child block
JoiningDt DATE;
BEGIN

outer_blk.JoiningDt :=TO_DATE (20- JAN-1998, DD-MONYY);


END;

END;
/
In the code snippet, a variable with the same name as the variable declared in the
outer block is declared in the inner block. To reference the outer block variable in the
inner block, the variable is qualified by prefixing it with the block name.
Identifiers are considered local to the PL/SQL block in which they are declared, and
are considered global to all its sub-blocks. Within the sub-block, only local identifiers
are visible because to reference the global identifiers you must use a qualified name.
If a block cannot find the identifier declared locally, it will look up to declarative
section of the enclosing block (parent block). However, the block will never look
down to the enclosed blocks (child blocks).
Look at the following code and determine the variable scope:
<<OUTER_BLK>>
DECLARE
vSal NUMBER(8,2) := 50000;
vComm NUMBER(8,2) := vSal * 0.10;
vNote VARCHAR2(200) := Eligible for commission;
BEGIN
DECLARE
vSal NUMBER(8,2) := 90000;
vComm NUMBER (4) := 0;
vAnnualComp NUMBER(8,2) := vSal + vComm;
BEGIN
vNote := Manager not||vNote;
OUTER_BLK.vComm := vSal * 0.20;
END;
vNote := Salesman||vNote;

END;

21
SQL Star International Ltd.

Based on the rules of scoping, determine values of:


 vNote at position 1
 vAnnualComp at position 2
 vComm at position 1
 OUTER_BLK.vComm at position 1
 vComm at position 2
 vNote at position 2
Guidelines for Writing PL/SQL Code
Programs can be indented using carriage return,tabs and aligning keywords in the
same line at different levels.Writing so, the task of debugging is made simpler.
While writing programs we can follow a case convention, though PL/SQL is not case
sensitive. For instance:

All keywords should be written in uppercase.


They must be aligned in the same line.
Table names and column names are to be in Initcap and lower case
respectively.

Indented programs improve the performance in those cases where similar


statements are to be issued repeatedly because there is no need to parse the
statements again.
Let us write a program following all the rules stated above.
DECLARE
AnnualIncome NUMBER(7):= 60000;
QuarterlyInc
NUMBER(8,2);
BEGIN
QuarterlyInc:= AnnualIncome/4;
DBMS_OUTPUT.PUT_LINE(The
Quarterly
||QuarterlyInc);
END;

income

is

/
Here, the keywords are aligned in the same line but at different levels. This
improves readability of the code.

22
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
 PL/SQL bridges gap between SQL and a procedural language.
 Two types of PL/SQL blocks are:
1. Anonymous or unnamed blocks which are not stored in database and
compiled each time they are executed.
2. Named Blocks which are compiled only once and stored in the database.
 The three sections of a PL/SQL Block are:
1. Declarative Section, where all the variables used in the program are
declared. This is optional. Keyword: DECLARE
2. Executable Section where actual logic of the program lies. Keyword: BEGIN
3. Exception Section where all the errors are handled. This section is optional.
Keyword: EXCEPTION
4. END to end the PL/SQL program.
 PL/SQL variables are declared and are accessible only within that PL/SQL block.
Non- PL/SQL variables are declared outside the PL/SQL block and are available
throughout the session.
 Two new datatypes FLOAT and DOUBLE introduced in this release help users in
computing complex calculations.
 Nesting of blocks is allowed to have good control on the scope of the variables.
Nested Blocks can also have exception sections.

23
SQL Star International Ltd.

Lab Exercises
1.

Identify whether the following declarations are correct or incorrect:


a) DECLARE
empID NUMBER(5);
b) DECLARE
str1, str2, str3 VARCHAR2(20);
c) DECLARE
hiredate

DATE NOT NULL;

d) DECLARE
on BOOLEAN := 5;
2.
Create an anonymous block to display the phrase Welcome to the world of
PL/SQL.

3.
Create a block that declares two variables, one of VARCHAR2 type and the
other of NUMBER type. Assign the following values to the variables and display the
output on the screen.

4.
Write a PL/SQL code to display the ID, Last Name, job ID and salary of an
employee?

24
SQL Star International Ltd.

5.

6.

Among the following which datatypes are enhancements of Oracle10g?


a)

BINARY_INTEGER

b)

BINARY_FLOAT

c)

BINARY_DOUBLE

Display the following text using quote operator q:


Im Oracle certified,youre not.

25
SQL Star International Ltd.

Chapter 2

DMLs in PL/SQL
SELECT Statements in PL/SQL
DML Statements in PL/SQL

26
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Use SELECT statements within PL/SQL blocks
 Perform Data Manipulations within PL/SQL blocks

27
SQL Star International Ltd.

SELECT Statements in PL/SQL Blocks


The use of a PL/SQL block would be incomplete without its ability to interact with the
database. To interact with the database you must use SQL. PL/SQL therefore,
supports the use of Data Manipulation Language, Transaction Control commands,
SQL functions for extracting data and also applying changes to the database.
Embedding SELECT Statements
SELECT statements within PL/SQL blocks help retrieve data from the database. The
syntax is the same as when issued from iSQL*Plus, with a slight difference.
SELECT <select_list>
INTO (variable_name[, variable_name] | record_name)
FROM <table_name>
WHERE condition;
In the syntax,
select_list specifies a list of columns whose values are to be retrieved. They could
also include SQL expressions, row functions or group functions.
variable_name is the variable which has been declared to hold the values that are
retrieved. They are also known output variables.
record_name is the PL/SQL record which has been declared to hold the retrieved
values. [PL/SQL records will be dealt with in a later session]
table_name is the database table from which values are retrieved.
condition comprises of various operators used to compare column names, constants,
expressions and PL/SQL input variables.
The main factor that differentiates this syntax from the usual SELECT syntax written
in iSQL*Plus, is the INTO clause. This clause is mandatory and is placed between the
SELECT clause and the FROM clause. It specifies the variables into which the values
returned by the SELECT clause are to be stored. For each item selected in the
SELECT clause there must be corresponding output variables in the INTO clause. The
order of the output variables too must correspond to the columns selected.
Embedded SELECT statements are within the scope of ANSI (American National
Standard Institute) Classification of Embedded SQL, which states that Queries must
return one and only one row. Therefore, if queries return more than one row the
Oracle server generates an error.

28
SQL Star International Ltd.

The following example will illustrate the use of SELECT statements within PL/SQL
blocks:
SET SERVEROUT ON
DECLARE
vBkname Book.cBookName%TYPE;

Output

vAuthor Book.cAuthorName%TYPE;

Variables
BEGIN
SELECT cBookName, cAuthorName
INTO vBkname, vAuthor
FROM Book
WHERE cBookID = HUM020000323;
DBMS_OUTPUT.PUT_LINE(vBkname|| written by: ||vAuthor);
END;
/

On executing the code, the following result is generated:


Rapunzel meets Santa Claus in Hell
written by: Tom Boyle
PL/SQL procedure successfully completed.

In the above program, the block retrieves the book name and author name for the
specified book.
In the following code SQL functions are used in the SELECT statement within the
block:
DECLARE
vTotcopies NUMBER(4); Output variable
vBrID Book.cBranchID%TYPE:=02CHSNJ;
BEGIN
SELECT SUM(nNoOfCopies)
INTO vTotcopies
FROM Book
WHERE cBranchID = vBrID;

29
SQL Star International Ltd.

DBMS_OUTPUT.PUT_LINE(The

total

stock

of

books

stored

at

the
Chester NJ Library is: ||vTotcopies);
END;
/

On executing the code, the following result is generated:


The total stock of books stored at the Chester NJ Library is: 176
PL/SQL procedure successfully completed.
The block retrieves the total number of copies of books stored in the specified
branch.
There is a possibility of ambiguity in the WHERE clause if the variable name and the
column name are identical. This is because the Oracle server on encountering a
variable on the right side of the comparison operator in the WHERE clause, checks
whether it is a column in the database. If it is not, then it assumes it to be a PL/SQL
variable. However, if the variable name is identical to the column name, the server
considers it to be a column and therefore generates an erroneous result.
Why dont you try this out to check what erroneous result the server gives?
There is another interesting aspect of the WHERE clause. As you know, it is used in
the SELECT statement to restrict the rows that are to be selected. However, if no
rows are selected, the server generates an error. For example, if the branch ID
specified does not exist in the Branch table and you use it in the WHERE clause, the
query would be unable to retrieve any details pertaining to the branch ID specified.
The following code would clarify this:
DECLARE
vBrName Branch.cBranchName%TYPE;
BEGIN
SELECT cBranchName
INTO vBrName
FROM Branch
WHERE cBranchID = 00SCHNJ;
END;
/

30
SQL Star International Ltd.

On executing the code, the following error is generated:


DECLARE
*
ERROR at line 1:
ORA-01403: no data found

While embedding SELECT statements within PL/SQL blocks you must remember the
following points:

Each SQL statement must be terminated with a semicolon (;).

Do not forget the INTO clause in the SELECT statement.

The WHERE clause specifies a condition using constants, literals or PL/SQL


input variables (for example, vBrID). This clause is optional.

The number of output variables in the INTO clause must be the same as the
number of columns selected in the SELECT clause.

Make sure that the output variables in the INTO clause are mapped correctly
to the selected columns, and that their datatypes are also compatible.

To ensure that the datatypes of variables match that of the columns selected
in the SELECT clause, use the %TYPE attribute.

You can use group functions in the SELECT statement but not in the PL/SQL
statements.

Do not give names to variables that are identical to column names. This could
create ambiguity in the WHERE clause.

31
SQL Star International Ltd.

DML Statements in PL/SQL


You can manipulate data stored in the database by embedding DML statements
within
PL/SQL blocks. This has its benefits. When you issue DML statements in iSQL*Plus,
they are sent to the Oracle server for execution one at a time. This results in high
performance overhead especially in a networked environment. Therefore, to improve
performance, club all the DML statements into one PL/SQL block, because the entire
block gets sent to the server for execution in one go.
The transaction control options COMMIT, SAVEPOINT and ROLLBACK, that define the
beginning, breakpoint and end of a logical unit of activity in SQL statement
processing, are also available in PL/SQL. The option that provides locking
mechanism, which enables only one user at a time to change a record in the
database, is also supported in PL/SQL.
However, an important difference between issuing SQL statements within PL/SQL
blocks and in iSQL*Plus is that the beginning and end of a PL/SQL block does not
necessarily imply the beginning or end of a transaction. The execution of the first
DML statement in the
PL/SQL block begins a transaction. To save or discard the changes made, the PL/SQL
code should explicitly contain COMMIT or ROLLBACK statements.

Embedding INSERT, UPDATE, DELETE and MERGE Statements


The INSERT statement within a PL/SQL block is embedded just as you would enter
them in iSQL*Plus.
For example, insert details of a new member Ann Judd into the Member table.
BEGIN
INSERT INTO Member
VALUES(CAJ040501,ANN,JUDD,26A,Due Drops Apts,Blue
Mountain Villa,Elizabeth,78099',
NULL,22,SYSDATE,C,N,04RANNJ);
END;
/
You can confirm the insertion of a new row by issuing a SELECT statement:
SELECT *
FROM Member
WHERE cMemberID=CAJ040501';

32
SQL Star International Ltd.

This block does not have a declarative section, as you have not declared any
variables that are to be used in the INSERT statement. The VALUES clause requires:

The actual values Ann and Judd to be entered into the cFirstName and
cLastName columns respectively

The SYSDATE function to enter the current date into the dMembershipDt
column.

You can also insert the above details in the following manner:

DECLARE
cFirstName Member.cFirstName%TYPE DEFAULT Ann;
BEGIN
INSERT INTO
Member(cMemberID,cFirstName,cLastName,vAddress,
cArea,cZipcode,cPhone,nAge,dMembershipDt,
cGrade,cMaritalStatus,cBranchID)
VALUES (CAJ040501,cFirstName,Judd,
26A,Due Drops Apts,Blue Mountain Villa,
Elizabeth,78099',NULL,22,SYSDATE,C,N,04RANNJ);
END;
/
The block declares an input variable cFirstName of Member.cFirstName%TYPE and
initializes it with a value Ann using the DEFAULT keyword instead of the assignment
operator. This variable is then used in the VALUES clause to populate cFirstName
column with the value Ann.
Therefore, in the INSERT statement you can:

Use functions such as SYSDATE

Use database sequences to generate sequential numbers

Add default column values

33
SQL Star International Ltd.

You can make changes to data or remove data stored in the database by embedding
UPDATE and DELETE statements within PL/SQL blocks.

For example, the librarian has to:

Update the number of copies of the book On The Street Where You Live in
the Book table

Remove details pertaining to one Mr. Derian Bates from the Member table

DECLARE
vCopiesIncrease Book.nNoOfCopies%TYPE:=3;
vMemID Member.cMemberID%TYPE: = CDB028504;
BEGIN
UPDATE Book
SET nNoOfCopies = nNoOfCopies + vCopiesIncrease
WHERE cBookName = On The Street Where You Live;
DELETE FROM Member
WHERE cMemberID = vMemID;
COMMIT;
END;
/
It is important to remember that there could be ambiguity in the SET clause if the
declared variable has the same name as that of the column whose value is to be
updated. This is because the name on the left of the assignment operator in the SET
clause always corresponds to a database column, but on the right side, it could
correspond either to a database column or a PL/SQL variable.

34
SQL Star International Ltd.

For instance, in the above code,


SET nNoOfCopies = nNoOfCopies + vCopiesIncrease;
nNoOfCopies to the left of the operator corresponds to the nNoOfCopies column of
the Book table, where as nNoOfCopies to the right also corresponds to the same
column. But vCopiesIncrease to the right of the operator corresponds to the input
variable. Therefore, there is no ambiguity in this SET clause.
Why dont you see the outcome of naming a variable as that of a column name?

Use the MERGE statement to update or insert rows in a table using data from
another table. Rows are inserted or updated based on a specified equijoin condition.
For example,
DECLARE
vMemID Member.cMemberID%TYPE:= CDB028504;
BEGIN
MERGE INTO MemberTabCopy mc
USING Member m
ON (m.cMemberID=vMemID)
WHEN MATCHED THEN
UPDATE SET
mc.cFirstName = m.cFirstName,
mc.cLastName = m.cLastName,
mc.vAddress = m.vAddress,
mc.cArea = m.cArea,
mc.cZipcode = m.cZipcode,
mc.cPhone = m.cPhone,

35
SQL Star International Ltd.

mc.nAge = m.nAge,
mc.dMembershipDt = m.dMembershipDt,
mc.cGrade = m.cGrade

WHEN NOT MATCHED THEN


INSERT VALUES (m.cMemberID, m.cFirstName, m.cLastName,
m.vAddress, m.cArea, m.cZipcode, m.cPhone,
m.nAge, m.dMembershipDt, m.cGrade);
END;
/
[For the purpose of this code, we have created a table MemberTabCopy, which is
similar in structure to the Member table]
The code matches the member ID in the MemberTabCopy table to the member ID
in the Member table. If they match, the row is updated to match the row in the
Member table or else the row is inserted into the MemberTabCopy table.
But, where do the SQL statements issued within the blocks get parsed (that is,
checked whether the tables the statements are accessing are the ones on which
accessing privileges have been granted) and executed? This brings in the concept of
cursors which will be discussed in a later chapter.

36
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:

 Embedded SELECT statement has a INTO clause in it. One or more variable
follows after the INTO clause.

 Variable names should not collide with the names of the database columns.
Data fetched from the database are stored in these variables.

 WHERE clause restricts the number of rows to be fetched into these scalar
variables.

 DML Statements INSERT, UPDATE, DELETE and MERGE can be used to


manipulate the data in the database by embedding these statements inside the
PL/SQL block.

 By doing so, one can reduce the network traffic, resulting in the performance
improvement.

37
SQL Star International Ltd.

Lab Exercises
1.

Create a PL/SQL block that retrieves the sum of salary of department 80 and
stores it in an iSQL*Plus variable. Print the value to the screen.

2.

Create a PL/SQL block that will:


Retrieve the maximum employee ID in the Employees table.
Insert data regarding a new employee into the Employees table. For the
employee ID of the new employee, add 1 to the maximum employee ID
retrieved. Leave the phone number, commission and manager ID as null.
Display the new row created.

38
SQL Star International Ltd.

Chapter 3

Control Structures
Conditional Constructs
Case Constructs
Loop Constructs
GOTO Statement

39
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:


Use Branching structures

Use Case Control structures

 Use Loop Control Structures

40
SQL Star International Ltd.

Control Structures
Oracle PL/SQL provides a range of constructs that allow you to control the flow of
process and there by produce well-structured programs. The selection structure tests
a condition and then executes one sequence of statements instead of another, based
on whether the condition is true or false. A condition is any variable or expression
that returns a Boolean value (TRUE or FALSE). The iteration structure executes a
sequence of statements repeatedly as long as a condition holds true. The sequence
structure simply executes a sequence of statements in the order in which they occur.
Hence, Control structures are the backbones of a program, where logic of the
transactions are specified.
Control Structures are broadly divided into
Conditional constructs
IF Statements
CASE Statement and CASE Expression
Loop constructs
Basic Loop
WHILE Loop
FOR Loop
CONDITIONAL CONSTRUCTS USING IF Statements
When you need to perform actions based on conditions, you use the IF statements.
The structure of the IF statement is the same as those used in other procedural
programming languages. It has three variations. They are:
 IF - THEN - END IF
 IF - THEN - ELSE - END IF
 IF - THEN - ELSIF - END IF
Their syntaxes are shown below.

IF condition THEN
statements;
END IF;

IF condition THEN
statements;
[ELSE
statements;]
END IF;

IF condition THEN
statements;
[ELSIF condition THEN
statements;]
[ELSE
statements;]
END IF;

41
SQL Star International Ltd.

Where,
condition is an expression, which when executed will return a value of TRUE or
FALSE or NULL. If the result of the expression is TRUE then the set of statements,
following the THEN clause is performed else the statements following the ELSE or
ELSIF clauses are performed.
THEN is the clause that connects the value returned by the condition to the
statements that are to be executed
statements include the SQL or PL/SQL statements that would perform the required
actions. They may include further IF statements.
The ELSIF keyword is associated with another set of SQL or PL/SQL statements that
should be executed in case the condition does not return the desired value.
The ELSE keyword is also associated with a set of SQL or PL/SQL statements that will
be executed, if none of the statements higher up in the construct has been executed,
as the condition associated with each was not satisfied.
Let us now look into a few examples of the various IF statements, to have a better
understanding of its functioning.
Example of a simple IF statement:
The library does not permit a person to be its member if his/her age happens to be
below five. This could be checked using the following IF-THEN-END IF statement:
DECLARE
Age Member.nAge%TYPE:= &age;
BEGIN
IF Age<5 THEN
DBMS_OUTPUT.PUT_LINE
(Membership
age);
END IF;
END;
/

denied

Invalid

If you execute the above code entering the value for age as 4, you get the following
output:
Membership denied Invalid age
PL/SQL procedure successfully completed.
In the code, PL/SQL displays the message only if the condition is TRUE. If the
condition is FALSE or NULL, PL/SQL just ignores them. But if you want some action
to be performed in case of FALSE or NULL result, use the ELSE clause.

42
SQL Star International Ltd.

Example of an IF THEN ELSE END IF Statement


The library charges a fine amount of Rs.1.5 per day after the elapse of the due date.
To perform this calculation, the following IF-THEN-ELSE statement can be used:
IF ActualRetDt > ReturnDt THEN
Fine := (ActualRetDt ReturnDt)*1.5;
ELSE
Fine: = 0;
END IF;
In the statement, if the actual return date is later than the due date, fine amount is
calculated. If not, the fine amount is set to 0.
Example of an IF-THEN-ELSE-ENDIF Statement using Logical Operators
IF LibraryName=Jersey AND ActualRetDt>ReturnDt
THEN
Fine:=(ActualRetDt-ReturnDt)*1.5;
ELSE
Fine:=0;
END IF;
In the statement, the fine is calculated only if the Library Name is Jersey and actual
return date is later than the due date. If not, the fine amount is set to 0.
Nested IF Statements
Either set of actions of the first IF statement can include further IF statements. Such
a condition is known as a nested IF statements. Its syntax is as follows:
IF condition1 THEN
Statement1;
ELSE
IF condition2 THEN
Statement2;
END IF;
END IF;
The nested IF statement is terminated with its corresponding END IF.
Example of nested IF statements
The library charges fee from members based on their ages. To illustrate this execute
the following code:
DECLARE
Age NUMBER :=&age ;

43
SQL Star International Ltd.

Fee NUMBER;
BEGIN
IF Age < 5 THEN
DBMS_OUTPUT.PUT_LINE (Membership
denied: Age below permissible
membership age);
ELSE
IF Age BETWEEN 5 AND 13 THEN
Fee := 10;
ELSE
IF Age BETWEEN 14 AND 20 THEN
Fee := 15;
ELSE
IF Age BETWEEN 21 AND 50 THEN
Fee := 20;
ELSE
IF Age > 50 THEN
Fee := 5;
END IF;
END IF;
END IF;
END IF;
END IF;
DBMS_OUTPUT.PUT_LINE (Fee for Member is:|| Fee);
END;
/
If you execute the above code, you can see the following output:
Enter value for age: 44
old
2: Age NUMBER :
=
new
2: Age NUMBER :
=
Fee for Member is:20

&age ;
44 ;

PL/SQL procedure successfully completed.


The above code does not permit a person to be the librarys member if his age is
below five. However, if his age is above five, then membership fee is charged based
on a set of nested IF conditions.
If the age is between 5 and 13, then the fee amount is 10. If the age is between 14
and 20, then the fee amount is 15. If the age is between 21 and 50, then the fee
amount is 20. If the age is above 50, then the fee amount is 5.
Example of IF THEN ELSIF END IF Statements
The above code was quite cumbersome due to large number of nested IF
statements. Instead you can use the ELSIF clause to make the code easier to read.

44
SQL Star International Ltd.

DECLARE
Age NUMBER := &age ;
Fee Number;
BEGIN
IF Age < 5 THEN
DBMS_OUTPUT.PUT_LINE (Membership denied: Age below
permissible membership age);
ELSIF Age BETWEEN 5 AND 13 THEN
Fee := 10;
ELSIF Age BETWEEN 14 AND 20 THEN
Fee := 15;
ELSIF Age BETWEEN 21 AND 50 THEN
Fee := 20;
ELSIF Age > 50 THEN
Fee := 5;
END IF;
DBMS_OUTPUT.PUT_LINE (Fee for Member is:|| Fee);
END;
/
On executing the above code, you get the following result:
Enter value for age: 55
old
2: Age NUMBER : = &age ;
new
2: Age NUMBER : = 55 ;
Fee for Member is:5
PL/SQL procedure successfully completed.
After having used all the variations of the IF constructs, there are certain guidelines
to remember while writing them in a program. They are:


Every IF is followed by a THEN after stating the condition.

Every IF must have a matching END IF.

An IF construct may have any number of ELSIF statements.

END IF is not one word but two words and ELSIF is one word.

Every IF construct can have only one ELSE statement.

The ELSIF keyword does not have a matching END IF.

Do not use a semicolon (;) on the lines with the IFTHEN, ELSIF, and ELSE
keywords. END IF must be followed by a semicolon (;).

You could indent the SQL and PL/SQL statements to be executed to increase
readability of the code.

CASE Expressions and CASE Statements


In PL/SQL, two types of CASE constructs are supported:

45
SQL Star International Ltd.

CASE expressions: The expressions select a result and return it. CASE
expressions can be assigned to variables, can be part of SQL statements and
can be used in logical expressions. The syntax begins with CASE and ends
with END.

CASE statements: They are independent statements just like other PL/SQL
statements such as IFTHENELSE statements. The syntax begins with CASE
and ends with END CASE.

CASE Expressions
CASE expressions select a result and return it. To understand better, look at the
following syntax of a CASE expression:
CASE selector
WHEN expr1 THEN result1
WHEN expr2 THEN result2

WHEN exprn THEN resultn


[ELSE resultn+1;]
END;
From the syntax, it becomes clear that, to select the result, the CASE expression
uses a selector, which is an expression whose value is used to select one of the
several alternatives provided. The selector is followed by one or more WHEN clauses.
The value of the selector determines which clause is executed. This is the syntax of a
simple CASE expression.

Execute the following code of a simple CASE expression:


DECLARE
vGrade CHAR(1):= &grade;
vGradeWiseAge VARCHAR2(25);
BEGIN
VGradeWiseAge :=
CASE vGrade
WHEN A THEN Age between 5 and 13
WHEN B THEN Age between 14 and 20
WHEN C THEN Age between 21 and 50
WHEN D THEN Age above 50
END;
DBMS_OUTPUT.PUT_LINE (Grade : ||vGrade|| Stands for:
||vGradeWiseAge);
END;
/
In the code, the CASE expression is being assigned to a variable vGradeWiseAge.
The CASE expression uses the value in the vGrade variable (The value is accepted

46
SQL Star International Ltd.

using a substitution variable) as the expression. Depending on the value entered by


the user, the CASE expression assigns the value to variable vGradeWiseAge.
PL/SQL also supports a searched CASE expression. It has the following syntax:
CASE
WHEN searchCondition1 THEN result1
WHEN searchCondition2 THEN result2

WHEN searchConditionn THEN resultn


[ELSE resultn+1]
END;
Searched CASE expressions have no selectors. The WHEN clauses contain search
conditions, which return a Boolean value, and not expressions that can return values
of any type. Each WHEN clause can have different conditional expressions, which can
take the form of searchCondition = <variableExpr> operator <variableExpr>. The
operator can be any comparison operator.
Execute following code of a searched CASE expression:
DECLARE
vAge NUMBER(3):= &age;
vAgeWiseGrade CHAR(25);
BEGIN
vAgeWiseGrade :=
CASE
WHEN vAge<5 THEN Membership not
permitted
WHEN vAge BETWEEN 5 AND 13 THEN A
WHEN vAge BETWEEN 14 AND 20 THEN B
WHEN vAge BETWEEN 21 AND 50 THEN C
WHEN vAge BETWEEN 50 AND 100 THEN D
END;
DBMS_OUTPUT.PUT_LINE
(For
Age
:
||vAge||
The
corresponding grade is: ||vAgeWiseGrade);
END;
/
In the code, the searched CASE expression is assigned to a variable
vAgeWiseGrade. The block accepts the age and returns the grade based on the
CASE expression.
CASE Statements
PL/SQL supports simple as well as searched CASE statements. Their differences are
as follows:
 Simple CASE statements:
 Evaluate a single variable or expression for multiple values
 Do not support comparison operators in the WHEN clause
 Searched CASE statements:

47
SQL Star International Ltd.

Evaluate multiple variables or expressions

 Each WHEN clause can:


Evaluate different expressions
Have comparison operators
The following examples will help us in understanding the different ways in which the
two different CASE statements function.

A simple CASE statement example:


DECLARE
vGrade CHAR(1):= &grade;
vGradeWiseAge VARCHAR2(25);
BEGIN
CASE vGrade
WHEN A THEN vGradeWiseAge:=Age
WHEN B THEN vGradeWiseAge:=Age
WHEN C THEN vGradeWiseAge:=Age
WHEN D THEN vGradeWiseAge:=Age
END CASE;
DBMS_OUTPUT.PUT_LINE (Grade :
||vGradeWiseAge);
END;
/

between 5 and 13;


between 14 and 20;
between 21 and 50;
above 50;
||vGrade||

Stands

for:

In the code, you will notice that the CASE statement ends with an END CASE
statement, and that every executable statement following the WHEN clause is
terminated with a
semicolon. The above code accepts the value of vGrade and based on it assigns
different values to vGradeWiseAge variable.
A searched CASE statement example:
DECLARE
vAge NUMBER(3):= &age;
vAgeWiseGrade CHAR(2);
BEGIN
CASE
WHEN vAge<5 THEN vAgeWiseGrade:=Membership not permitted;
WHEN vAge BETWEEN 5 AND 13 THEN vAgeWiseGrade:=A;
WHEN vAge BETWEEN 14 AND 20 THEN vAgeWiseGrade:=B;
WHEN vAge BETWEEN 21 AND 50 THEN vAgeWiseGrade:=C;
WHEN vAge BETWEEN 50 AND 100 THEN vAgeWiseGrade:=D;
END CASE;
DBMS_OUTPUT.PUT_LINE
(For
Age
:
||vAge||
The
corresponding grade is: ||vAgeWiseGrade);

48
SQL Star International Ltd.

END;
/
In the code, you will observe that the searched CASE statement is similar to the
searched CASE expression except that the former cannot be assigned to variables.

NULLIF and COALESCE Expressions


NULLIF and COALESCE expressions are a form of shorthand for PL/SQL CASE
expressions.
The NULLIF expression behaves like an inverse of the NVL function. The semantics
can be written as follows:
CASE
WHEN expr1=expr2 THEN NULL;
ELSE expr1;
END;
Execute the following PL/SQL code:
DECLARE
vDate DATE:= &Date;
vReturnDt DATE;
BEGIN
IF vDate >= SYSDATE THEN
vReturnDt:= NULLIF (vDate, SYSDATE);
DBMS_OUTPUT.PUT_LINE (The member has to pay a fine
amount of: || (vReturnDt SYSDATE) * 1.5);
ELSE
DBMS_OUTPUT.PUT_LINE (No fines due);
END IF;
END;
/
In the code, if the date entered by the user is before the SYSDATE, the ELSE
condition is executed. However, in case the date entered is after the SYSDATE or
equal to the SYSDATE, the NULLIF expression is evaluated. If the vDate variable

49
SQL Star International Ltd.

value is after SYSDATE, then the vDate value is returned to vReturnDt variable. If
vDate variable value is same as SYSDATE, then a NULL value is returned.
The COALESCE expression behaves like the NVL function, but it can take a list of
values. Its semantics can be written as follows:

Expression with two arguments:


CASE
WHEN expr1 IS NOT NULL THEN expr1
ELSE expr2
END;
Expression with three or more arguments:
CASE
WHEN expr1 IS NOT NULL THEN expr1;
ELSE COALESCE (expr2, expr3,,exprn);
END CASE;

LOOP Constructs
You might require performing a set of steps continuously till the transaction or task
to be performed is complete.
Using loops will enable you to perform such kind of execution. The execution of the
statements in a loop also depends on the result of a condition being true or false. As
long as the condition is true, the statements in the loop will be executed. The
condition to be satisfied is the starting point of the loop. Only when the condition is
satisfied, the control of the block will enter the loop and will stay in the loop till the
condition is reversed. An important point to remember is that the loop should
provide for a condition to be checked for exiting from the loop.
The different looping constructs include:
 BASIC loop
 FOR loop
 WHILE loop
BASIC LOOP
This is the simplest form of a loop. The statements are enclosed between the
keywords LOOP and END LOOP.

50
SQL Star International Ltd.

When the flow of execution encounters the END LOOP keyword, the control goes
back to the corresponding LOOP statement above it. Hence, the statements in a
basic loop will be executed at least once.
The loop should contain an EXIT statement. This statement helps to end the loop. If
it is not specified, then the loop will be endless. An EXIT statement can also be
specified as an action within an IF statement. The EXIT statement must be placed
within the loop.

The syntax for a basic loop is:


LOOP
statements;
. . .
EXIT [WHEN condition];
END LOOP;
Where,
LOOP is the beginning of the loop statements
statements are the SQL and/or PL/SQL statements to be executed.
EXIT is the keyword to end the loop.
WHEN condition is the condition to be true to exit the loop.
END LOOP marks the end of the loop and takes the loop back to the beginning.
An example of a basic loop to display the first ten even numbers is as follows:
DECLARE
Counter NUMBER(2):= 0;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE(Counter);
Counter:= Counter+2;
EXIT WHEN Counter= 20;
END LOOP;
END;
/
The output generated by the above code is:

Result:

51
SQL Star International Ltd.

0
2
4
6
8
10
12
14
16
18
PL/SQL procedure successfully completed.
The EXIT-WHEN statement replaces a simple IF statement. For example, compare
the following statements.

IF counter=20 THEN | EXIT WHEN counter=20;


EXIT;
END IF;
These statements are logically equivalent, but the EXIT-WHEN statement is easier to
read and understand.
If you are checking the condition first and then executing the statements, EXIT
statement should be placed immediately
after LOOP statement in the above
program
FOR LOOP
In a FOR LOOP a condition is placed in the beginning of the loop as a counter. The
condition controls the number of times the statements is to be executed. The syntax
is:
FOR counter in [REVERSE ]
lower_bound .. upper_bound LOOP
statement1;
statement2;
. . .
END LOOP;

Where,
counter is an integer, which is implicitly declared and whose value increases (or
decreases when used with the REVERSE keyword) by 1 until its value reaches either
of the limits specified.
REVERSE enables the counter to decrement in intervals of 1.
lower_bound is the lower limit for the range of the counter value.
upper_bound is the upper limit for the range of the counter value.

52
SQL Star International Ltd.

The values for these limits can be strings, variables or expressions or numeric
values. When the lower value is increased to a values higher or greater than the
specified upper limit then the loop will not be executed.
Certain points to remember when using a FOR loop is that:
 The counter can be referenced only inside the loop.
 When referring to the counter use an expression to refer to its existing value.
 Cannot assign a value to the counter.

Use a FOR loop instead of the basic loop to display the first ten even numbers.

DECLARE
EvenNo NUMBER(2): = 0;
BEGIN
FOR I IN 1..10
LOOP
DBMS_OUTPUT.PUT_LINE(EvenNo);
EvenNo: = EvenNo +2;
END LOOP;
END;
/
This code generates the following output:
Result:

0
2
4
6
8
...
...
PL/SQL procedure successfully completed.

WHILE LOOP
In this loop construct, the statements are executed based on a condition at the
beginning of the loop. The condition controls the end of the loop as well. In other
words, the statements in the loop will be executed as long as the condition specified
is true.
The condition is checked at the beginning of the LOOP and if the condition is FALSE,
the loop is terminated and none of the statements are executed. The syntax is:

53
SQL Star International Ltd.

WHILE condition LOOP


statement1;
statement2;
. . .
END LOOP;
Where,
condition is the expression that returns a boolean value of TRUE, FALSE or NULL
statement is the set of SQL and PL/SQL statements to be executed.
In case the condition returns a NULL value then the loop is skipped and none of the
statements are executed.
The same code regarding the display of even numbers can be done using the WHILE
loop as follows:
DECLARE
EvenNo NUMBER(2):= 0;
BEGIN
WHILE EvenNo < 20
LOOP
DBMS_OUTPUT.PUT_LINE(EvenNo);
EvenNo := EvenNo+2;
END LOOP;
END;
/
The result when you execute the code is similar to that of the previous one.

GOTO Statement
The GOTO statement branches to a label unconditionally. The label must be unique
within its scope and must precede an executable statement or a PL/SQL block. When
executed, the GOTO statement transfers control to the labeled statement or block. In
the following example, you move to an executable statement further down in a
sequence of statements:
BEGIN
...
GOTO insert_row;
...
<<insert_row>>
INSERT INTO emp VALUES ...
END;
/
In the next example, you move to a PL/SQL block further up in a sequence of
statements:
DECLARE
x NUMBER := 0;
BEGIN
<<increment_x>>
BEGIN
x := x + 1;

54
SQL Star International Ltd.

END;

IF x < 10 THEN
GOTO increment_x;
END IF;
END;
/
The label <<end_loop>> in the following example is not allowed because it does not
precede an executable statement:
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..50
LOOP
IF done THEN
GOTO end_loop;
END IF;
<<end_loop>> --not allowed
END LOOP; --not an executable statement
END;
/
To correct the previous example, add the NULL statement::
FOR i IN 1..50
LOOP
IF done THEN
...
GOTO end_loop;
END IF;
...
<<end_loop>>
NULL; -- an executable statement
END LOOP;

Restrictions on the GOTO Statement


A GOTO statement cannot branch from one IF statement clause to another, or from
one CASE statement WHEN clause to another.
A GOTO statement cannot branch from an outer block into a sub-block (that is, an
inner BEGIN-END block).
A GOTO statement cannot branch out of a subprogram. To end a subprogram early,
you can use the RETURN statement or use GOTO to branch to a place right before
the end of the subprogram.
A GOTO statement cannot branch from an exception handler back into the current
BEGIN-END block. However, a GOTO statement can branch from an exception
handler into an enclosing block.

55
SQL Star International Ltd.

Nested Loops
You can write a loop within another loop. The inner loop is called a nested loop as it
is nested within a loop, which is called the parent loop. You can nest FOR, WHILE
and BASIC loops within one another. If the nested loop is terminated, the parent
loop does not get terminated unless there is an exception raised in the nested loop.
Loops can be labeled, to identify them. These are like identifiers and are placed
before the word LOOP within label delimiter (<<label>>). You could also place the
label after the END LOOP keyword.
Syntax for a nested loop is:
. . .
BEGIN
<<outerloop>>
LOOP
counter_variable:= counter_variable + 1;
EXIT WHEN nCount>20;
<<innerloop>>
LOOP
. . .
EXIT outerloop WHEN condition;
. . .
EXIT WHEN condition;
. . .
END LOOP innerloop;
. . .
END LOOP outerloop;
END;
Where,
BEGIN is the start of the block.
LOOP on the second line marks the beginning of the first loop, labeled outerloop.
counter_variable is the counter that is used to check the number of times the loop is
being executed and is defined earlier.
EXIT WHEN is the condition to exit the outer loop.
The next LOOP marks the beginning of the nested or inner loop labeled innerloop.
EXIT outerloop will exit both the loops based on the condition.
EXIT WHEN on the next line will exit only the nested loop if the condition is satisfied.
The control goes back to the outerloop.
END LOOP innerloop ends the inner loop and execution of the outer loop resumes if
there are any statements to be executed.

56
SQL Star International Ltd.

END LOOP outerloop ends and exits the outer loop.


END marks the end of the block.
Let us look at a complete example illustrating the Nested loop.
DECLARE
counter number:=0;
counter1 number:=0;
total_done varchar2(4);
inner_done varchar2(4);
BEGIN
<<outer_loop>>
LOOP
counter:=counter+1;
EXIT WHEN counter>10;
<<inner_loop>>
LOOP
IF counter = 10 THEN
total_done:=yes;
END IF;
EXIT outer_loop WHEN total_done= yes;
counter1:=counter1+1;
IF (MOD(counter1,2)!=0) THEN
inner_done:=yes;
END IF;
EXIT WHEN inner_done =yes;
DBMS_OUTPUT.PUT_LINE(Printing Even numbers
from inner loop-||counter1);
END LOOP

inner_loop;

inner_done:=No;
DBMS_OUTPUT.PUT_LINE(*****Printing Whole Number from
outer

loop***** ||counter);

END LOOP outer_loop;


END;
/

57
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
 Statements such as IF and CASE give a directive approach to a program logic.
 Using IF statement, multiple nested conditions can be solved. Where as CASE
statement improves the readability of a program.
 The Iteration of a program can be controlled using
1. Basic LOOP that runs at least once
2. WHILE LOOP that runs till the specified condition is true
3. FOR loop that executes till the specified Iteration in the loop.
These loops can be nested within one another.
 Jumping from one point to other in a program can be done with GOTO
Statement using the LABEL Option.

58
SQL Star International Ltd.

Lab Exercises

1.

Create a PL/SQL block that rewards an employee by appending an asterisk in


the STARS column for every $1000 of the employees salary. Save your
PL/SQL block in a file called c3q1.sql by clicking on the save script button.
Remember to save script with .sql extension.
a. Use the DEFINE command to provide the employee id. Pass the value to the
PL/SQL block through a iSQL*Plus substitution variable.
SET VERIFY OFF;
DEFINE p_empno=104;
b. Initailize a v_asterisk variable that contains a NULL.
c. Append an asterisk to the string for every $1000 of the salary amount. For
example, if the employee has a salary amount of $8000, the string of asterisks
should contain eight asterisks. If the employee has a salary amount of
$12500, the string of asterisks should contain 13 asterisks.
d. Update the STARS column for the employee with the string of asterisks.
e. Commit.
f.

Test the block for the following values.


i. employee_id=174
ii. employee_id=176

59
SQL Star International Ltd.

Chapter 4
Composite Data-types
Types of Composite Data-types
INDEX BY Tables
INDEX BY Table of Records
Varying Arrays
Nested Tables
Ref Cursor

60
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:


Identify the different composite datatypes

Process data using different composite datatypes

61
SQL Star International Ltd.

What are Composite Datatypes?


In the previous chapters, you have used variables of scalar datatypes such as
VARCHAR2, NUMBER or DATE. These are primitive datatypes. In addition to scalar
variables, there are also variables of composite datatypes.
Composite datatypes are created by users and hence are also known as user defined
datatypes. They are made up of a collection of different predefined datatypes.
Therefore, composite datatypes are also referred to as collections.
In this chapter you will learn how to create PL/SQL records, INDEX BY tables, nested
tables and varrays.
But, what is the need of composite variables? The answer lies in the issue of
reducing network traffic.
A major concern for programmers is to keep the network traffic as minimal as
possible. Every time you embed an SQL statement within the PL/SQL block, a
request is sent to the server for execution. This increases the network traffic and
also takes up a lot of servers memory. To overcome these problems, variables are
declared within PL/SQL blocks. Scalar variables can store only one column value from
the database. Therefore, the need was felt to declare variables, which could create
temporary memory area within the client machine for storing collections of database
items. Once declared, these collections of data could repeatedly be used for data
manipulations in any application, without having to access the database. In other
words, composite datatypes are reusable. This need led to the usage of composite
datatypes.

Types of Composite Datatypes


Composite datatypes store collections of data items and the result set (of data) is
treated as one logical unit of data. Once you have this result set stored within the
client memory, you can use the values returned by the result set instead of
accessing the database. This reduces network traffic. Following are the different
composite datatypes that a user can define:

PL/SQL record type




User-defined records

%ROWTYPE records

62
SQL Star International Ltd.

Collections


INDEX BY Table

Nested Table

VARRAY

Reference (REF)

PL/SQL Records
A record is defined as a collection of different but related data items stored in fields.
For example, the New Jersey Central Library has different kinds of data regarding its
members, such as their ID, name, address, membership date, age, etc. Each piece
of information or data item is dissimilar in type, but it is all logically related to
members. Therefore, a member record is made up of a collection of these logically
related data items.
In PL/SQL these data items are stored in fields and a PL/SQL record is made up of a
collection of such fields.
A PL/SQL record is similar in concept and structure to a row in a database table. Just
as a row in the Member table contains information about members stored in
columns, similarly a record contains information of members stored in fields.
Therefore, a record is a composite data structure, as it comprises more than one
element or field. The record as a whole does not have any value of its own. Instead,
each individual field in a record has a value. The record enables you to store and
access these values as a group rather than accessing them from the database.
To create a record, you should first create a record type and then declare records of
that type. There are two types of PL/SQL records:

User-defined records

User-defined records are based on


record types where you can specify the
fields along with datatypes.

%ROWTYPE records

%ROWTYPE records on the other hand


are based on record types created using
the %ROWTYPE attribute. Here, you
cannot specify your own fields because
using %ROWTYPE attribute creates a
record that represents a row in a
database table.

User-defined Records
A user-defined record is a composite datatype that allows you to declare fields of
your own choice. You can define their datatypes based on database columns using
%TYPE attribute. The syntax is:

63
SQL Star International Ltd.

TYPE <type_name> IS RECORD


(field_name1 {field_type | variable%TYPE | table.column%TYPE

}[NOT

NULL][:=| DEFAULT value],


(field_name2 {field_type | variable%TYPE | table.column%TYPE } [NOT
NULL] [:= |DEFAULT value],
...);
Where,
type_name is the name you give to the record type you are creating.
field_name1| field_name2 is the unique name given to each field contained within
the record.
field_type is the datatype assigned to fields. You can use %TYPE or %ROWTYPE
attribute. You can also define the NOT NULL constraint to prevent null values from
being entered into the fields or assign values to fields by using the assignment
operator.
(: =) or the DEFAULT keyword.
After creating record types, you can create identifiers of that type using the following
syntax:
<record_name> <type_name>;
Where,
record_name is the name given to the record you are creating.
type_name is the record type based on which you are creating the record.

Before you start using PL/SQL records, you need to understand how to create them.

Creating user-defined PL/SQL Records


As there are no predefined datatypes based on which a PL/SQL record can be
created, you should first create a datatype and then create a record based on that
type. The following PL/SQL block illustrates how to create a user-defined record type
and a record based on it:
DECLARE
TYPE rectpBk IS RECORD
(BookID CHAR(13),BkName Book.cBookName%TYPE,
Author Book.cAuthorName%TYPE);
-- Create a record

64
SQL Star International Ltd.

recBook rectpBk;

In the above code snippet:

A record type rectpBk has been created which is made up of three fields
namely BookID, BkName and Author. The %TYPE attribute is used to
specify the datatypes of BkName and Author.

A record recBook is then created using rectpBk.

If you have not used the NOT NULL or the DEFAULT keyword to initialize fields, you
must reference or initialize them explicitly.

Initializing Records
You can reference or initialize record fields using the following syntax:
<record_name>.<field_name>
In the syntax the dot notation is used between the record name and the field name.
In the above code snippet, you would reference the Bkname field as follows,
recBook.Bkname
and initialize it with a value,
RecBook.Bkname:= Illusions;
However, you cannot assign a list of values to a record by using an assignment
operator. The following syntax is illegal:
<record_name> := (value1, value2, value3, ...);
For instance, you cannot assign the values of book id, book name and author name
all together to the record recBook in the following manner:
recBook:=(0109918818122,The Jewel In The Crown, Paul
Scott);
Also, you cannot test records for equality or inequality. For instance, the following IF
statement is illegal:
IF record_name1 = record_name2 THEN
END IF;
You now know the syntax for assigning values to fields, but there are different ways
to do it.
Way of assigning values to Records
There are two ways to assign values to fields:

Instead of assigning values to fields individually, you can assign values to all
the fields at once. This can be done using a SELECT statement in the code
where you had declared recBook record.

65
SQL Star International Ltd.

DECLARE
BkID Book.cBookID%TYPE:=&Bkid;
TYPE rectpBk IS RECORD
(Bookid CHAR(13),
Bkname Book.cBookName%TYPE,
Author Book.cAuthorName%TYPE);
recBook rectpBk;
BEGIN
SELECT cBookID,cBookName,cAuthorName
INTO recBook
FROM Book
WHERE cBookID=BkID;
DBMS_OUTPUT.PUT_LINE(recBook.Bookid|| || recBook.Bkname||
||recBook.Author);
END;
/
In the code, the fields of recBook have been populated with values retrieved from
the Book table by the SELECT statement. But you must ensure that the column
names in the SELECT statement appear in the same order as the fields in the record.
Also, their datatypes should be compatible with one another.
On executing this code, the values are retrieved from the record rather than from
the database table Book. This reduces your network traffic.
Enter value for bkid: FIC011111111
FIC011111111

Jewel In The Crown Paul Scott

The second way is to assign one record to another. The following code
illustrates this:

DECLARE
TYPE rectpBk IS RECORD
(Bkid CHAR (15),

66
SQL Star International Ltd.

Bkname CHAR (100));


TYPE rectpBkCtg IS RECORD
(Bk rectpBk,
Ctname CHAR (5),
Pbyr DATE); --nested record
TYPE rectpBrn IS RECORD
(Brid CHAR (7),
Brname CHAR (25),
Bk rectpBk); --nested record
recCtg rectpBkCtg;
recBks rectpBkCtg;
recBrn rectpBrn;
BEGIN
recCtg.Ctname:=Humor;
recCtg.Bk.Bkid:=0117955574699';
recCtg.Bk.Bkname:=Very Good Jeeves;
recBks.Pbyr:=12-MAY-1965';
recBks.Bk:=recCtg.Bk;
recBrn.Bk:=recCtg.Bk;
END;
/

This code snippet illustrates the declaration of nested records, i.e. a record defined
as an element or a field of another record. For instance, Bk is a nested record as it is
defined as a field (which is of rectpBk type) of recCtg, recBks and recBrn records.
You can assign the values of a nested record to another if they are of the same
datatype. For instance, recCtg and recBks are both of the same datatype,
rectpBkCtg. The nested record Bk has been assigned values:
recCtg.Bk.Bkid:=0117955574699;
recCtg.Bk.Bkname:=Very Good Jeeves;

You can now assign them to recBks record in the following manner:

67
SQL Star International Ltd.

recBks.Bk:=recCtg.Bk;
Such assignments are also permissible if the records that contain the nested records
are of different datatype. For instance, recBrn record of rectpBrn record type,
which contains the nested record Bk. Yet, the values of Bk initialized by recCtg
record can be assigned to recBrn.
recBrn.Bk:=recCtg.Bk;
The problem of not knowing the column details of a database table and unexpected
changes taking place in the number and datatype of columns can be overcome using
the %ROWTYPE attribute. The % ROWTYPE attribute enables you to create
%ROWTYPE records, which are similar to rows in a database table.

%ROWTYPE Records
Sometimes you may want to create a record whose record type is same as a row of a
database table (or a view). Such records are known as %ROWTYPE records. Just as
you can create a record based on a user-defined record type, similarly, you can
create a %ROWTYPE record using the %ROWTYPE attribute. The %ROWTYPE
attribute enables you to declare a record type that represents a row in a table or a
view.
Creating %ROWTYPE Records
Prefixing % ROWTYPE with the table name creates %ROWTYPE records. The syntax
for declaring a %ROWTYPE record is:
<record_name>

reference%ROWTYPE;

Where,
record_name is the name you assign for a record.
reference specifies the name of a table, view or cursor on which you want the record
to be based.

The following code snippet illustrates how to declare a %ROWTYPE record:


DECLARE
recTran Transaction%ROWTYPE;
In the code snippet, recTran record is created using %ROWTYPE attribute to store
the same transaction details, as are stored in the Transaction table. The structure
of recTran record consists of fields, each of which represents a column in the
Transaction table. Therefore, the fields of the recTran record have the same name
and datatype as the columns in the Transaction table. Any change in the number or
datatype of columns in the Transaction table will get reflected in the recTran
record.
The structure of the composite variable recTran would be as follows (This is not the
code, but only the structure of recTran record).
(cTranID CHAR (11),

68
SQL Star International Ltd.

cMemberID CHAR (7),


cBookID CHAR (15),
dIssueDt DATE,
dReturnDt DATE,
dActualReturnDt DATE,
nFine NUMBER (2))

However, you cannot include an initialization clause using an assignment operator, or


assigning values using NOT NULL or DEFAULT keyword within %ROWTYPE
declaration. If you have to reference or assign values to individual fields of
%ROWTYPE record, you must use the dot notation syntax:
<record_name>.<field_name>
For instance, assign value to the field nFine in recTran record as follows:
recTran.nFine: = 2;
Instead of assigning values to individual record fields, you can assign values to the
record by using a SELECT statement or fetching values into it using cursors. The
following code illustrates assigning of values using a SELECT statement:
DECLARE
vTranID Transaction.cTranID%TYPE:= &tranid;
recTran Transaction%ROWTYPE;
BEGIN
SELECT *
INTO recTran
FROM Transaction
WHERE cTranID=vTranID;
DBMS_OUTPUT.PUT_LINE(recTran.cMemberId
|| ||recTran.cBookID|| ||
recTran.dIssueDt|| ||
recTran.dReturnDt|| ||
recTran.dActualReturnDt|| ||
recTran.nFine);
END;
/
Following is the output of the above code:
Enter value for tranid: T0000020594

69
SQL Star International Ltd.

CBW109702 CLS020005771

22-MAY-94 05-JUN-94 09-JUN-94 6

The code creates a record recTran of %ROWTYPE that represents an entire row of
Transaction table. The column values in the Transaction table are assigned to the
fields in the recTran record using the SELECT statement. The same code can be
written using cursors. Try it yourself.
As mentioned earlier in the chapter, records are mainly created to reduce network
traffic and save server resources. But how is the New Jersey Central Library
achieving this? The following scenario will make it clear how the library is making use
of %ROWTYPE records.
The library maintains a table called NonMembers, that stores the details of all
members whose membership has been terminated. As the details are similar to the
Member table, you can use this to insert values into NonMembers table, by writing
the following code:
DECLARE
vMemID Member.cMemberID%TYPE:=&memid;
recMember Member%ROWTYPE;
BEGIN
SELECT *
INTO recMember
FROM Member
WHERE cMemberID=vMemID;
INSERT INTO NonMember (nSerialNo,cMemberID,cFirstName,
cLastName,cPhone,dMembershipDt,cBranchID)

VALUES(sqSerialNo.nextval,recMember.cMemberID,recMember.cFirstName,
recMember.cLastName,recMember.cPhone,recMember.dMembershipDt,
recMember.cBranchID);
COMMIT;
END;
/
In the code, the programmer declares a recMember record to store the details of a
member, as it is stored in the Member table, by using the %ROWTYPE attribute.
Values are assigned to the fields of recMember record by the SELECT statement
that retrieves details of the member whose ID is supplied.

70
SQL Star International Ltd.

The assigned values are then inserted into NonMember table by using the dot
notation syntax of recMember.cFirstName, recMember.cLastName, and so on.
Whenever the membership of a member has to be terminated, his ID is supplied to
execute the above
PL/SQL block. This block will select his details from the Member table and store it in
the record created. His details are then inserted into the NonMember table from the
record and not from the database table. This code serves two purposes. Firstly, it
automates entries into the NonMember table. Secondly, it reduces the servers
network traffic and memory usage.

Inserting a Record using %ROWTYPE


The above example can be re-written as below:
DECLARE
vMemID Member.cMemberID%TYPE:=&memid;
recMember MemberCopyTab%ROWTYPE;
BEGIN
SELECT * INTO recMember
FROM Member
WHERE cMemberID=vMemID;
INSERT INTO MemberCopyTab VALUES recMember;
COMMIT;
END;
/
Compare the INSERT statement in the previous example with the INSERT statement
in this example. The recMember record is of type MemberCopyTab. 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.

Updating a row in the table using %ROWTYPE


DECLARE
vMemID Member.cMemberID%TYPE:=&memid;
recMember MemberCopyTab%ROWTYPE;
BEGIN
SELECT * INTO recMember

71
SQL Star International Ltd.

FROM Member
WHERE cMemberID=vMemID;
UPDATE

MemberCopyTab

SET ROW=recMember
WHERE Memberid=vMemID;
COMMIT;
END;
/
The example shows how to update a row using a record. The keyword ROW is used
to represent the entire row. The code shown in the example updates the
MemberCopyTab table with MemID detail of the Member table.
By now, as you should be familiar with PL/SQL records, let us move on to the next
type of composite variable INDEX BY tables.

INDEX BY Tables
Like PL/SQL records, INDEX BY tables are also composite variables and are created
based on a composite datatype TABLE. INDEX BY tables are objects of TABLE type,
which are designed as database tables but are not the same as database tables.
For example, an INDEX BY table of book names is designed as a database table with
two columns, one that stores the primary key and the other that stores the character
data, book names. But unlike a database table you cannot manipulate INDEX BY
table by using SQL statements. However, the primary key column enables you an
array-like access to rows. Consider the primary key values as index and rows of the
INDEX BY table as elements of a one-dimensional array.
A one-dimensional array is an ordered list of elements of the same type. An array
element is accessed by its index. An index number determines the position of an
element in an ordered list. All array indexes start at zero. For instance, an array
contains 12 elements of character datatype representing names of months. Each
element is assigned an index with which they are to be accessed.

To access the fourth month of a year, you should access index number 3. However,

72
SQL Star International Ltd.

INDEX BY tables are different from arrays in two ways:

Arrays have their lower and upper limit defined. For instance, the array
created above has a limit of 12 elements. It cannot accommodate a single
element exceeding its limit. However, the size of an INDEX BY table is not
fixed and can be increased dynamically.

Secondly, arrays necessarily must have consecutive index numbers. But this
is not a constraint in INDEX BY tables and this characteristic is known as
sparsity. For instance, to index book names in an INDEX BY table, you can
use book IDs that are not consecutive.

Defining INDEX BY Tables


To create INDEX BY tables, you must follow two steps :
1. Define a TABLE type
2. Declare INDEX BY tables of the TABLE type

TABLE types are declared in the declarative section of PL/SQL blocks


using the following syntax:

TYPE <type_name> IS TABLE OF {column_type | variable%TYPE |


table.column%TYPE} [NOT NULL]
table.%ROWTYPE
[INDEX BY BINARY_INTEGER|PLS_INTEGER];
Where,
type_name is the name of the TABLE type. It is this TABLE type based on which
subsequent INDEX BY tables are declared.
column_type specifies the type of the column that would be contained in the INDEX
BY table. It can be any scalar datatype such as VARCHAR2, NUMBER and DATE. If
the column type is a record type then every field of the record must be of a scalar
datatype. You can also use the %TYPE to provide the datatype of a variable or a
database column.
NOT NULL constraint prevents null values from being stored in the INDEX BY table.
INDEX BY BINARY_INTEGER|PLS_INTEGER specifies the datatype of INDEX BY
clause.
The key can be numeric, either BINARY_INTEGER
or PLS_INTEGER.
BINARY_INTEGER and PLS_INTEGER require less storage than NUMBER. They are
used to represent mathematical integers compactly and 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.
Both BINARY_INTEGER and PLS_INTEGER has a magnitude range of

73
SQL Star International Ltd.

2,147,483,647..2,147,483,647. Therefore, the primary key value can be negative.


Note that PLS_INTEGER requires less storage and are faster than BINARY_INTEGER.
The following code snippet illustrates how to define a TABLE type based on the
member IDs.
DECLARE
TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE
INDEX BY PLS_INTEGER;

The code snippet creates tbtypMemID TABLE type that will have the column type of
cMemberID.
After declaring a TABLE type, you need to create an INDEX BY table of the TABLE
type. You can create an INDEX BY table by using the following syntax:
identifier

<type_name>;

Where,
identifier is the name assigned to the INDEX BY table declared of type_name.
The following code snippet illustrates the declaration of an INDEX BY table of
tbtypBookName TABLE type:

DECLARE
TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE
INDEX BY PLS_INTEGER;
tabBkName tbtypBookName;

This code creates tabBkName INDEX BY table that is based on tbtypBookName


TABLE type.
Before you start using INDEX BY tables, remember that you are not allowed to
initialize them. An INDEX BY table cannot be populated when you are declaring it.
Even when you define the NOT NULL constraint, do not initialize any values. You can
populate the
INDEX BY table by explicitly using executable statements.
The next example illustrates that you can define NOT NULL constraint without
initializing the INDEX BY table.
DECLARE
TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE NOT NULL
INDEX BY BINARY_INTEGER;
tabBkName tbtypBookName;

74
SQL Star International Ltd.

By defining NOT NULL constraint on the tbtypBookName TABLE type, null values
are prevented from being entered into tabBkName INDEX BY table.
The INDEX BY table tabBkName has the following structure:

tabBkName INDEX BY table has one column and a primary key neither of which are
named. The column is of scalar datatype and the primary key is of PLS_INTEGER
type. The primary key plays an important role when it comes to referencing an
INDEX BY table.
The size of tabBkName is unconstrained or unbounded. Its number of rows can
increase dynamically as more books are added into the already existing list of books
in the Book table.
You cannot initialize tabBkName INDEX BY table in its declaration. By doing so, you
are limiting the number of books. In a library, there would always be additions to the
Book table. By defining a limit, your INDEX BY table would not reflect changes made
to its underlying database column.

Referencing INDEX BY Tables


To reference elements of an INDEX BY table, use the primary key index number in
the following syntax:
<index_by_table_name>(primarykey_value)
Where,
index_by_table_name is the name of the INDEX BY table whose element you want to
reference.
primarykey_value is the expression that yields the PLS_INTEGER value.
Reference an element in the third row of tabBkName INDEX BY table in the
following manner:
tabBkName(3)
So far, you have declared a TABLE type and INDEX BY table of the TABLE type in the
declarative section. You can now use INDEX BY table within the executable section.
For instance, use tabBkName INDEX BY table to update the number of copies of the
book Jewel In The Crown.

75
SQL Star International Ltd.

DECLARE
vBrId Book.cBranchID%TYPE:=&BrId;
TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE
INDEX BY BINARY_INTEGER;
tabBkName tbtypBookName;
BEGIN
tabBkName(1):=Jewel In The Crown;
UPDATE Book
SET nNoOfCopies = nNoOfCopies + 3
WHERE cBookName = tabBkName(1)
AND cBranchID=vBrId;
END;
/
On executing the code, you get the following result:
Enter value for brid: 02CHSNJ
PL/SQL procedure successfully completed.
INDEX BY tables are mainly used to move collections of data into and out of
database tables to reduce network traffic and save server resources. For example,
the New Jersey Central Library may want to temporarily store the details of nonmembers. The programmer will write the following code to retrieve the member IDs
from the NonMember table and store them in a INDEX BY table in the following
way:

DECLARE
TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE
INDEX BY BINARY_INTEGER;
tabMemID tbtypMemID;
nCount NUMBER (2);
BEGIN

76
SQL Star International Ltd.

SELECT COUNT (*)


INTO nCount
FROM NonMember;
FOR i IN 1..nCount
LOOP
SELECT cMemberID
INTO tabMemID(i)
FROM NonMember
WHERE nSerialNo= i;
END LOOP;
FOR i IN 1..nCount
LOOP
DBMS_OUTPUT.PUT_LINE (tabMemID (i));
END LOOP;
END;
/
In the above code, a loop is used to retrieve the IDs of non-members from the
NonMember table and store them in tabMemID INDEX BY table. Each of the rows
has been assigned serial numbers (nSerialNo) in the NonMember table, which are
incremental by 1. Therefore, the following WHERE clause:
WHERE nSerialNo = i ;
The above code also uses another loop to retrieve them from tabMemID and print
them on to the screen using DBMS_OUTPUT.PUT_LINE. Therefore, each time the
loop iterates, the values are displayed from the INDEX BY table rather than from the
database table in the following manner: This reduces a lot of network traffic.
On executing the code, you get the following result:
CAC033105
DSP023205

PL/SQL procedure successfully completed.

77
SQL Star International Ltd.

This list proves to be helpful when the librarian wishes to compare it with the
existing list of members in the Member table. By doing so, he will be able to identify
those who are no longer members of the library.
So far, the INDEX BY tables you created were based on TABLE types consisting of
scalar datatypes. You can also base them on record types. Such INDEX BY tables are
known as INDEX BY table of records.

INDEX BY Table of Records


In INDEX BY table of records, %ROWTYPE is used to define its element type. For
example, you can declare an INDEX BY table of records type, which is based on the
Book table.
The following code snippet illustrates the usage of %ROWTYPE to creating INDEX BY
tables:
DECLARE
TYPE tbtypBk Book%ROWTYPE
INDEX BY BINARY_INTEGER;
tabBk tbtypBk;
In the above code snippet, tabBk INDEX BY table holds details of all the fields of the
database table Book. You can reference tabBk fields by using the following syntax:
indexbytable_name (index).field
Usage of the syntax is as follows:
tabBk(5).cAuthorName:=Shakespeare;
cAuthorName represents a field in tabBk.
Now, based on the INDEX BY table of records, tabBk created in the above code,
display the author name and published year of books published between the years
1950 and 1975.

DECLARE
CURSOR curBk IS
SELECT * FROM Book
WHERE dPublishedYr between 01-JAN-1950' AND 01-JAN-1975;
TYPE tbtypBk IS TABLE OF Book%ROWTYPE
INDEX BY BINARY_INTEGER;

78
SQL Star International Ltd.

tabBk tbtypBk;
i NUMBER := 0;
BEGIN
OPEN curBk;
LOOP
i := i+1;
FETCH curBk INTO tabBk(i);
EXIT WHEN curBk%NOTFOUND;
DBMS_OUTPUT.PUT_LINE
tabBk(i).cBookName

(The
||written

tabBk(i).cAuthorName ||was published

book||

||

by||

||

in

the

year|| ||tabBk(i).dPublishedYr);
END LOOP;
CLOSE curBk;
End;
/
On executing this code, you will get the following output:
The book Jewel In The Crown written by Paul Scott was published in the
year 25-NOV-66
PL/SQL procedure successfully completed.

To further enhance the functionality of INDEX BY tables, there are some built-in
procedures or functions that operate on them. These built-ins are covered next.

INDEX BY Table Methods


INDEX BY tables use methods such as EXISTS, COUNT, FIRST, LAST, PRIOR etc.
These methods are built-in procedures or functions that make the using the INDEX
BY tables easier. They are accessed using the dot(.) notation. The syntax is:

<indexbytable_name>.<attribute_name>[(parameters)]

The various INDEX BY table methods are:

79
SQL Star International Ltd.

EXISTS

COUNT

FIRST

LAST

PRIOR

NEXT

TRIM

DELETE

Of these methods, EXISTS, PRIOR, NEXT, EXTEND and DELETE take parameters.
These parameters must be expressions that produce a BINARY_INTEGER value or
values that can implicitly be converted in to a value of that type.

EXISTS Method
EXISTS (n) returns TRUE if the nth element exists in an INDEX BY table. This
attribute is used to avoid the error that would be generated while referencing
nonexistent elements.
In the following example, PL/SQL executes the UPDATE statement only if the
element tabBkName (1) exists:
DECLARE
vBrId Book.cBranchID%TYPE:= &BrId;
TYPE tbtypBookName IS TABLE OF Book.cBookName%TYPE
INDEX BY BINARY_INTEGER;
tabBkName tbtypBookName;
BEGIN
tabBkName(1):=The Crown;
IF tabBkName.EXISTS(1) THEN
UPDATE Book
SET nNoOfCopies = nNoOfCopies + 3
WHERE cBookName = tabBkName(1)
AND cBranchID = vBrId;
END IF;
COMMIT;

80
SQL Star International Ltd.

END;
/

COUNT Method
The COUNT method returns the number of elements contained within an INDEX BY
table. You can use COUNT wherever integer expression is allowed. For example, the
following code creates a tabMemID INDEX BY table. Using COUNT you can ascertain
the total number of books contained in it.

DECLARE
TYPE tbtypMemID IS TABLE OF NonMember.cMemberID%TYPE
INDEX BY BINARY_INTEGER;
tabMemID tbtypMemID;
nCount NUMBER (2);
BEGIN
SELECT COUNT(*)
INTO nCount
FROM NonMember;
FOR i IN 1..nCount
LOOP
SELECT cMemberID
INTO tabMemID(i)
FROM NonMember
WHERE nSerialNo = i;
END LOOP;
FOR i IN 1..nCount
LOOP
DBMS_OUTPUT.PUT_LINE (tabMemID (i));
END LOOP;
DBMS_OUTPUT.PUT_LINE (The total number of books contained
in tabBkName is:||tabMemID.COUNT);
END;

81
SQL Star International Ltd.

The COUNT method is useful because the future size of an INDEX BY table is
unconstrained. For example, running the above code displays the total number of
elements contained in tabMemID.
CAC033105
DSP023205
CLK029204
DMA029204
DAC078801
CKB109305
The total number of books contained in tabMemID is:6
PL/SQL procedure successfully completed.
But if you run the same code after few days, you would find an increase in the
number of elements because the INDEX BY table is unconstrained and reflects any
change in the database table.

FIRST and LAST Method


The FIRST and LAST methods return the first (smallest) and last (largest) index
numbers in an INDEX BY table. These methods return null if the INDEX BY table is
empty or return the same index number if the INDEX BY table contains only one
element. For instance, in the above code you can identify the book names positioned
at the first and the last index numbers. The snippet of the relevant code:
DBMS_OUTPUT.PUT_LINE

(The

book

contained

(The

book

contained

in

the

first

index

is:

last

index

is:

||tabMemID.FIRST);
DBMS_OUTPUT.PUT_LINE

in

the

||tabMemID.LAST);
END;

PRIOR and NEXT Method


The PRIOR (n) method returns the index number that precedes index n in an INDEX
BY table. NEXT (n) returns the index number that succeeds index n in an INDEX BY
table. However, if n has no predecessor or successor, then both PRIOR and NEXT
return NULL. For example, the following statement returns NULL because the first
element in an INDEX BY table cannot have a predecessor:
n: = tabMemID.PRIOR(tabBkName.FIRST)

82
SQL Star International Ltd.

--NULL is assigned to n

You can use PRIOR and NEXT in tabMemID to display the member IDs that appear
prior to the last one and next to the first one as follows:
DBMS_OUTPUT.PUT_LINE (tabMemID.PRIOR (tabMemID.LAST));
DBMS_OUTPUT.PUT_LINE (tabMemID.NEXT (tabMemID.FIRST));

TRIM Method
The TRIM method removes one element from the end of an INDEX BY table. TRIM
(n) removes n elements from the end of an INDEX BY table. For example, you can
trim one element from the end of tabBkName INDEX BY table in the following
manner:
tabBkName.TRIM;

DELETE Method
The DELETE method has three forms. They are:

DELETE removes all the elements

DELETE (n) removes the nth element

DELETE (m, n) removes all the elements in the range of m to n from an


INDEX BY table

For example, the following snippet deletes the 20th element from tabBk INDEX BY
table.
tabBk.DELETE (20);

You can confirm this by issuing the following loop in the executable section of the
block:
FOR i IN 1..tabBk.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE (tabBk (i). cBookName);
END LOOP;
END;
This statement uses the COUNT method to specify the upper limit of the FOR loop.
The COUNT attribute will result in one element less than the total number of
elements and therefore the 20th element is not displayed.

83
SQL Star International Ltd.

Varying Arrays
Collections also include Varrays and Nested Tables. This section covers varrays.
If a record has more than one value for any of its attributes, then you would have to
enter the record into the table, as many times as there are values for the attribute.
This violates the primary key constraint and causes redundancy as the same record
gets stored multiple times. This problem can be taken care of by using varying
arrays. They are used to store multiple values of attributes of a record in the same
row.

Creating Varying Arrays


Varrays are created either as an Oracle datatype or a PL/SQL type. The syntax to
create varrays as a stored Oracle datatype is:
CREATE [OR REPLACE] TYPE <varray_name>
AS VARRAY(size) OF datatype(size);
Where,
varray_name is the name you want to give the varray
AS VARRAY (size) are the key words to create the varray specifying the size of the
varray in the parentheses. The size specifies how many values can be held for the
same attribute of one record.
OF datatype(size) is the type of data that the varray will store. This datatype is an
Oracle provided datatype.

84
SQL Star International Ltd.

For instance, in a library people borrow more than one book at a time. The library
has a rule that they will issue three books at a time to a member. Hence instead of
recording three transactions with different transaction IDs for the same customer,
the database should allow the library clerk to enter only one record with details of
the three books that the member wants to borrow. The attribute of book ID could be
created as a varray as shown below:
CREATE TYPE vaBooksID AS VARRAY(3) of CHAR(13);
/
Type created.
This code creates a varray vaBooksID that can be used in the Transaction table to
record the IDs of the books borrowed by the member. To use this in the
Transaction table, you need to create the table specifying the varray in the CREATE
TABLE statement:
CREATE TABLE objTransaction
(cTranID CHAR(4) PRIMARY KEY,
cMemberID CHAR (9),
BookID vaBooksID,
dIssueDt DATE,
dReturnDt DATE,
dActualReturnDt DATE,
nFine NUMBER (4,2),
CONSTRAINT TranMemIDFK FOREIGN KEY (cMemberID) REFERENCES
Member (cMemberID) ON
DELETE CASCADE);
Table created.
When viewing the table structure using data dictionary views, the column BookID
will be displayed as a column of type vaBooksID.
To see the attributes of the varying array, query the view, USER_COLL_TYPE. It will
display the datatypes and their characteristics. You could also specify the particular
datatype about which you want information.
For instance the following SELECT statement:
SELECT COLL_TYPE, ELEM_TYPE_OWNER, ELEM_TYPE_NAME, UPPER_BOUND, LENGTH
FROM USER_COLL_TYPES
WHERE Type_Name = VABOOKSID;
The query displays the following information about the varray vaBooksID:

85
SQL Star International Ltd.

COLL_TYPE

ELEM_TYPE_OWNER ELEM_TYPE_NAME UPPER_BOUND

--------

--------------- -------------- ----------

VARYING ARRAY

CHAR

LENGTH
-------13

Inserting Rows into Varrays


Varrays are abstract datatypes. Hence, to insert records into them you need to use
the constructor methods as you had done when inserting records into object tables.
For instance, to insert records into the objTransaction table created with a varray
type column, the code is:

INSERT INTO objTransaction VALUES


(T001, BTK119705, vaBooksID
(FIC00400213, ROM0020670), 1-NOV-2000,
15-NOV-2000', NULL,NULL);
1 row created.
In the code vaBooksID is the constructor of the varray type that is used to enter
values into the column.

Creating Varray Based on Abstract Dataype


In case the varying array is created using an abstract datatype, then to insert values
into the column the constructors of the abstract datatypes will have to be used
within the varray.
Example of a varray created using an abstract data type:
Create an object type as shown below.

86
SQL Star International Ltd.

CREATE OR REPLACE TYPE typBook AS OBJECT


(Bk_ID VARCHAR2(15));
/
Type created.
Create a varray based on typBook as follows:
CREATE OR REPLACE TYPE vaBkID AS VARRAY(3) OF typBook;
/
Type created.
Now, create a table objTrans having a column BookID of varray type vaBkID as
shown below.
CREATE TABLE objTrans
(cTranID CHAR(4) PRIMARY KEY,
cMemberID CHAR (9),
BookID

vaBkID,

dIssueDt DATE,
dReturnDt DATE,
dActualReturnDt DATE,
nFine NUMBER (4,2),
CONSTRAINT TransMemIDFK FOREIGN KEY (cMemberID) REFERENCES
Member (cMemberID) ON
DELETE CASCADE);
/
Table created.

To add a row into the table, issue the following INSERT statement:
INSERT INTO objTrans VALUES
(I001, BTK119705, vaBkID( TypBook(FIC00400213),
TypBook( ROM002067)), 25/01/2001, 08/02/2001, NULL, NULL);
1 row created.

87
SQL Star International Ltd.

An important point to remember is that, you should not insert more values into the
varray than what is specified when creating it. This is known as the LIMIT of a varray
and is specified when creating the varray. You can query the USER_COLL_TYPES
view to confirm the maximum values that the varray can hold.
Selecting Data from a Varray
You retrieve data stored in a varray by using a loop within a cursor. This can be
understood better by the code given below. This code, when executed extracts the
book IDs borrowed by a member. (To be able to see the output set the
SERVEROUTPUT on)
DECLARE
CURSOR curBookIssue IS
SELECT * FROM objTransaction;
BEGIN
FOR BookRec in CurBookIssue
LOOP
DBMS_OUTPUT.PUT_LINE(Books borrowed by: ||
BookRec.cMemberID);
FOR I IN 1 .. BookRec.BookID.Count
LOOP
DBMS_OUTPUT.PUT_LINE (BookRec.BookID(I));
END LOOP;
END LOOP;
END;
/
This code displays the result as shown below.
Books borrowed by:BTK119705
FIC00400213
ROM0020670

88
SQL Star International Ltd.

PL/SQL procedure successfully completed.


Another method to extract data from a varray is by using the Table() function. The
varray column name is passed as a parameter to this function and its output is given
an alias.
SELECT t.cTranID , B.*
FROM objTransaction t, Table(t.BookID) B;

The output for this code is:


CTRA COLUMN_VALUE
----

-----------

T001 FIC00400213
T001 ROM0020670

Creating VARRAY as PL/SQL Type :The maximum size of a VARRAY is 2 gigabytes (GB) as in nested tables which is
discussed later. In this case, the elements of a VARRAY are stored contiguously in
memory and not in the database.
Example:
TYPE location_type IS VARRAY(4) OF Member.carea%TYPE;
address location_type;
/
Type created.
DECLARE
TYPE location_type IS VARRAY(3) OF Member.carea%TYPE;
address location_type;
table_count NUMBER;
BEGIN
address:=
location_type(Bedminster,Allenbury,Ridgeland,Rockland
);
table_count := address.count();
FOR i in 1..table_count
LOOP

89
SQL Star International Ltd.

DBMS_OUTPUT.PUT_LINE(address(i));
END LOOP;
END;
/

In the above code, the size of a VARRAY is restricted to 4. You can initialize a
VARRAY by using constructors. If you try to initialize the VARRAY with more than
four elements, then a Subscript outside of limit error message is displayed.
If you want more flexibility when working with Composite Datatypes, you can use
Nested tables.

90
SQL Star International Ltd.

Nested Tables
As you have seen, varying arrays have a limit on the maximum number of values
that can be entered. In case you cannot define a maximum number of values to be
entered into a record, you can use nested tables. It can be created either as a
schema object or as a PL/SQL type.
A nested table is a table within a table, just as a nested query or a subquery is a
query within a query.

A nested table is an abstract datatype created consisting of another abstract type,


and the former abstract datatype is used to define a column when creating a table.
The syntax to create a nested table is:
CREATE TABLE <table_name>
(<column_name> datatype(size),
<column_name> abstract datatype,
. . .)
NESTED
TABLE
<abstract
datatype
<nested_table_name>;

column_name>

STORE

AS

Where,
CREATE TABLE <table_name> creates a relational table specifying the table name.
<column_name> datatype(size) are columns defined with an Oracle provided
datatype.
<column_name> abstract datatype are columns defined using abstract datatypes,
that are table types.

91
SQL Star International Ltd.

NESTED TABLE <abstract datatype column_name> STORE AS


<nested_table_name>; This statement specifies the name of the table that
will hold the data of the nested table.
Data of columns that are of Oracle provided datatypes are stored within the main
table being created, but the data of nested tables are stored in another table. In
other words nested tables are not stored inline with the rest of the table data. There
are pointers from the parent table to the nested tables.
For instance, to enter the book IDs that are issued to members, you created a
varray. But as you saw there was a maximum limit that the varray can hold. Assume
the library issues as many books as the member wants, and you want to store the
books IDs, category and author of the book, then a nested table will serve the
purpose.
First create an abstract datatype typBookDeta to store the attributes BookID,
BookCategory and Author.
CREATE TYPE typBookDeta AS OBJECT
(BookID CHAR(15),BookCategory CHAR(5),Author CHAR(30));
/
Type created.
Use this object type to create a type, called BookDetaNest that will be the basis of
a nested table.
CREATE TYPE BookDetaNest AS TABLE OF typBookDeta;
/
Type created.
This code creates type BookDetaNest allowing it to hold many rows. This type can
now be used to create a table of members and the books that they borrow as these
two bits of information are required very frequently to generate status reports on the
movement of books.
CREATE TABLE MemberBook
(MemberName VARCHAR(50),
Books BookDetaNest)
NESTED TABLE Books STORE AS BookNestTab;
Table created.
In this code, a table called MemberBook is created, with one VARCHAR2 column
and the other of an abstract datatype BookDetaNest that is created from an
abstract datatype. The values of the column Books are stored in another table called
BookNestTab, which is a nested table while the member names are stored in the
table MemberBook.

Inserting Records into a Nested Table


To insert values into a table that has a nested table the syntax is:

92
SQL Star International Ltd.

INSERT INTO <table_name> VALUES


(value, abstract datatype constructor( abstract datatype
constructor(),abstract datatype constructor(),. . .));
Where,
<table_name> is the relational table that is created.
The outer abstract datatype constructor is the constructor of the abstract datatype to
which the column for the nested table belongs.
abstract datatype constructor() is the set of attributes of the abstract datatype that
in turn are of another abstract datatype.
To populate the MemberBook table, the INSERT statement would be:
INSERT INTO MemberBook VALUES
(Jane Willow,
BookDetaNest (
typBookDeta(FIC00400213,Fic, Robin Cook),
typBookDeta(ROM002067,Rom, Danielle Steel),
typBookDeta(ROM002099,Rom, Danielle Steel),
typBookDeta(CLS002067,Cla, Mark Twain)));
1 row created.
This code when executed stores the member name Jane in one table and the details
of the books borrowed by Jane in another table.

Confirming the Structure


To confirm the structure of the columns and abstract datatypes used in tables, you
can query the data dictionary views as was shown in the case of varrays. You can
make queries for a specific table and/or a datatype, as shown below.
SELECT COLL_TYPE, ELEM_TYPE_NAME, ELEM_TYPE_OWNER
FROM USER_COLL_TYPES
WHERE Type_Name=BOOKDETANEST;
On executing the SELECT statement the following result is displayed:
COLL_TYPE
---------TABLE

ELEM_TYPE_NAME
------------TYPBOOKDETA

ELEM_TYPE_OWNER
--------------SCOTT

Displaying Data
Nested table as you have seen is a column within a table.

93
SQL Star International Ltd.

Oracle provides a special keyword, THE, to query from nested tables. You cannot
query a nested table with a SELECT statement.
You need to enclose the SELECT query that selects the nested table column from the
table, within THE keyword. This statement is then used as the source for the FROM
clause of another SELECT query to extract the required vale from the nested table.
The syntax to query from nested tables is as follows:
THE (SELECT nested table column
FROM <table_name>
WHERE column_name = value);
If you want to know the details of the books borrowed by Jane Willow you need to
execute the following code:
SELECT *
FROM THE (SELECT Books
FROM MemberBook
WHERE MemberName = Jane Willow);
On executing the code, the following result is displayed:

BOOKID

BOOKC

AUTHOR

------

-----

------

FIC00400213

Fic

Robin Cook

ROM002067

Rom

Danielle Steel

ROM002099

Rom

Danielle Steel

CLS002067

Cla

Mark Twain

Inserting Data Using THE


The THE function can also be used to insert data into the nested table. In case you
want to perform an insert into the main table leaving the contents of the nested
table intact, you use the THE function. When you want to populate a table with
contents from another table you use the INSERT AS SELECT statement. Here you
cannot use the SELECT statement within the INSERT statement, hence it is enclosed
within the keyword THE.
Let us assume that Jane, who had borrowed four books when she came, brings along
a friend who wants to borrow the same books. An entry has to be made into the
MemberBook table to include a new member but the details of the nested table
remain the same. In this case, the SELECT query within the keyword is:
THE (SELECT Books FROM MemberBook
WHERE MemberName = Jane Willow) nt

94
SQL Star International Ltd.

This query selects the name of the member from the MemberBook table. The
query should now be made the SELECT query for the INSERT, to add the new
members issue transaction.
INSERT INTO MemberBook
SELECT Kety,nt.BookID,nt.BookCategory,nt.Author
FROM THE (SELECT Books FROM MemberBook
WHERE MemberName=Jane Willow) nt;
INSERT INTO MemberBook
*
ERROR at line 1:
ORA-00913: too many values

This statement fails because the statement attempts to insert more values than
there are columns in the MemberBook table. The MemberBook table has two
columns. Though one of the columns is a nested table, you cannot directly insert
values into its columns in the manner done above. Hence, the statement needs to be
rewritten using two keywords, CAST and MULTISET.
CAST represents the result of a query as a nested table and MULTISET allows the
CAST query to have multiple rows. These two keywords can be used together.
Now the INSERT transaction you want to perform can be done as follows:
INSERT INTO MemberBook
VALUES (James Willow,
CAST (MULTISET (SELECT *
FROM THE (SELECT Books FROM
MemberBook
WHERE MemberName =
Jane Willow)) AS
BookDetaNest));
1 row created.
To confirm the insertion of a row, you can query the MemberBook table as follows:
SELECT nt.Author
FROM THE (SELECT Books FROM MemberBook
WHERE MemberName = James
Willow) nt;
The query displays the following result set:

95
SQL Star International Ltd.

AUTHOR

Danielle Steel
Danielle Steel
Mark Twain
Robin Cook
Nested tables are very powerful in their functionality. With a combination of the
keywords provided you could perform queries and other data manipulations on
nested tables and also use them with commands to manipulate the main tables.

Creating Nested table as PL/SQL Type


The functionality of nested tables is similar to INDEX BY tables. however, there are
differences in the nested table implementation. The nested table is a valid datatype
in a schema-level table but an INDEX BY table is not.

The key type for nested tables is not PLS_INTEGER.


The key cannot be a negative value unlike in the INDEX BY table.
There is a column with numbers in sequence which is considered as the key
column. 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.

Syntax to create Nested table:


TYPE

type _name

IS TABLE OF

{column _type | variable%TYPE


| table.column%TYPE} [NOT NULL]
| table.%ROWTYPE

The following program illustrates the use of PL/SQL type Nested table.

96
SQL Star International Ltd.

TYPE location_type IS TABLE OF Member.carea%TYPE;


address location_type;
If you do not initialize an INDEX BY table, it is empty. If you do not initialize a
nested table, then it is automatically initialized to NULL. You can initialize the
Address nested table by using a constructor as shown below:
address:=
location_type(Bedminster,Allenbury,Ridgeland,Roxbury);
Complete example:
SET SERVEROUTPUT ON
DECLARE
TYPE location_type IS TABLE OF Member.carea%TYPE;
address location_type;
table_count NUMBER;
BEGIN
address:=
location_type(Bedminster,Allenbury,Ridgeland,Roxbury);
table_count := address.count();
FOR i in 1..table_count
LOOP
DBMS_OUTPUT.PUT_LINE(address(i));
END LOOP;
END;
/

Output
Bedminster
Allenbury
Ridgeland
Roxbury
PL/SQL procedure successfully completed.

97
SQL Star International Ltd.

Managing Nested Tables and Varrays


There are some issues regarding the management of collections like nested tables
and varrays. A list of these issues is:
1. Indexing data when it becomes large. When the rows in a table increase,
relational tables can be indexed but varrays cannot. Hence when a varray increases
in size it could impair its performance.
2. You should know when to use a varray, when to create nested tables or a different
relational table. Varrays have limitations(2 GB) in the size of data they can hold,
hence, you need to decide when to use a nested table or a relational table.
3. If you want methods for your data use nested tables instead of relational as the
former are abstract datatypes.
4. If your data requires it to be related to lots of other tables, use relational tables
instead of nested ones. It is easier to manage data relationships.

Introduction to Ref Cursor


A REF CURSOR is basically a datatype. A variable created based on such a data type
is generally called a cursor variable. A cursor variable can be associated with
different queries at runtime. The primary advantage of using cursor variables is their
capability to pass result sets between subprograms (like stored procedures, functions
and packages)
An example for Ref Cursor is shown below:
DECLARE
TYPE Type r_cursor is REF CURSOR;
c_book

type_r_cursor;

Book_rec Book%rowtype;
BEGIN
OPEN c_book FOR

SELECT * FROM Book;

98
SQL Star International Ltd.

LOOP
FETCH

c_emp INTO book_rec;

EXIT WHEN

c_book%rowcount> 20 or c_book%notfound ;

DBMS_OUTPUT.PUT_LINE(book_Rec.cBookname || - ||
book_Rec.cAuthorName);
END LOOP;
CLOSE c_book;
END;
/
Type type_r_cursor is REF CURSOR;
The above statement simply defines a new data type called type_r_cursor, which is
of the type REF CURSOR. We declare a cursor variable named c_BOOK based on
the type type_r_cursor as follows:
c_book

type_r_cursor;

Every cursor variable must be opened with an associated SELECT statement as


follows:
OPEN c_book FOR SELECT * FROM Book;
Fetch is done with in a Loop to retrieve the records from the dynamic cursor. Once
the processing is done the cursor is closed before the block ends.
Then, what differentiates a cursor Variable from an Explicit Cursor?

A cursor variable can be associated with more than one query at runtime.

An explicit cursor is associated with one query at compilation time.

Summary
In this chapter, you have learnt that:
 Composite datatypes (also known as collections) store multiple data of different
datatypes due to its internal logical division.

99
SQL Star International Ltd.

 They are not pre-defined hence created by the user.


 PL/SQL records, INDEX BY TABLE, VARRAY, NESTED TABLE and REF cursor are
collectively called Composite Datatypes.
 PL/SQL records are used to store data fetched from multiple columns of a table
and %rowtype can be used to store an entire record using SELECT * statement.
 INDEX BY table holds data of one or more columns and INDEX BY table of
records hold entire database table data either from the vertical selection of columns
or entire table data.
 Varray stores multiple values for a particular column as per the limit and Nested
table can have an embedded table within a column specifying unlimited values.
 Cursor variable declared using REF Cursor can be associated with more than one
query dynamically.

100
SQL Star International Ltd.

Lab Exercises
1. Create a PL/SQL block to display the information about a given job, say
ST_CLERK.

Use a PL/SQL record, which is based on the structure of the Jobs


table.

Print the information about the job using DBMS_OUTPUT.PUT_LINE

The information should resemble as shown below:

2.

Write a PL/SQL block to print information about a given country using PL/SQL

Record.
a. Use the DEFINE command to provide the country ID. Pass the value to the
PL/SQL block through a iSQL*Plus substitution variable.
SET SERVEROUTPUT ON
SET VERIFY OFF
DEFINE p_countryid=CA
b. Use DBMS_OUTPUT.PUT_LINE to print information about country details.
C. Execute and test the PL/SQL block for the countries with the Ids CA,DE,UK,US.

3.

Create a PL/SQL block that will store the information of a retired employee

101
SQL Star International Ltd.

into a table called RetiredEmpsData.

Declare a PL/SQL record variable based on the structure of the Employees


table.

Supply the employee ID

Retrieve the record of the employee specified and store it in the variable
declared

Query the RetiredEmpsData table

[Note: Create the RetiredEmpsData table with the columns: EmpID, EName, Job,
MgrID, HireDate, RetiredDate, Sal, Comm, and DeptID]

4.

Create a PL/SQL block that uses INDEX BY table to retrieve the names of
cities of each location ID from the Locations table, and print the same on
the screen. The block should do the following:

Declare an INDEX BY table to store the names of the cities

Retrieve the names of all current cities from the Locations table into the
INDEX BY table using a loop. Assign value for the Location_ID column based
on the following counter values:

102
SQL Star International Ltd.

Use another loop to retrieve the city names from the INDEX BY table and
print them using the DBMS_OUTPUT.PUT_LINE

The output from the block should be as shown below

Roma
Venice
Tokyo
Hiroshima
Southlake
South San Francisco
South Brunswick
Seattle

103
SQL Star International Ltd.

Toronto
Whitehorse
Beijing
Bombay
Sydney
Singapore
London
Oxford
Stretford
Munich
Sao Paulo
Geneva
Bern
Utrecht
Mexico City

PL/SQL procedure successfully completed

5.

Create a table EMPPHDETAIL with following specification

EMPLOYEE_ID NUMBER(6),FIRST_NAME VARCHAR2(26).


Phone number should be a Varray column. Insert 3 phone numbers into each
employees detail. Select the data from this table.

6.

Create an Object type for employee ID. Use this in the creation of the Varray

which is restricted to 10.


Create a table EmpInDept that contains a column of the employee ID varray,
which holds Ids of the employees working in a department. Confirm whether the
table has been created.
Insert few values into this table and view them.

7.

Create a nested table column to hold the details of the projects handled by
each employee. Insert a few record into this table and confirm the records inserted
by querying them.

104
SQL Star International Ltd.

Chapter 5

Cursors and Advanced Cursors


Implicit and Explicit Cursors
Cursors and PL/SQL Records
Cursor FOR Loops
Ensuring Faster Updates
Subqueries in Cursors

105
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Differentiate between Implicit and Explicit Cursors
 Use explicit cursors
 Test the cursor status
 Write cursors using parameters
 Ensure faster update during cursor processing

106
SQL Star International Ltd.

What are Cursors?


A Cursor is an area of memory that the Oracle server opens to parse and execute an
SQL statement. There are two types of cursors: Implicit Cursors and Explicit Cursors.

Implicit Cursors
The Oracle server creates a work area implicitly when any SQL statement is issued
within its executable section. It is known as an Implicit Cursor. It is called implicit
because it is created and managed by the server automatically. The programmer
need not create it.
But what purpose do these implicit cursors serve?
On executing a SELECT statement within a PL/SQL block, there are a lot of issues the
programmer would be concerned of. For instance, if a SELECT statement within the
block does not retrieve any records, it displays the message stating no data found.
This situation can be handled by using some attributes that help programmers
evaluate what must have happened when the implicit cursor was used.
Implicit cursors are called SQL cursors, as the user does not specifically create them.
You cannot control the functioning of implicit cursors but can view the information
about the latest cursor by using implicit cursor attributes.

Attributes
SQL%ROWCOUNT

Purpose
Returns the number of rows retrieved by the most recently
Issued SELECT statement.

SQL%FOUND

Evaluates to TRUE if the recently issued SELECT statement


retrieves one or more rows.

SQL%NOTFOUND

Evaluates to TRUE if the recently issued SELECT statement


does not retrieve any rows.

SQL%ISOPEN

It always evaluates to FALSE because implicit cursors are


immediately closed after they execute the statement.

The use of these attributes will be clear after you try out these examples.
If the librarian wants to know the number of rows that got updated when he changed
the number of copies of Alien Legacy stored in branch 02CHSNJ and 03HAMNJ,
he will have to use SQL%ROWCOUNT as follows:

107
SQL Star International Ltd.

VARIABLE RowsUpd VARCHAR2(25)


DECLARE
vBrID1 Book.cBranchID%TYPE:=02CHSNJ;
vBrID2 Book.cBranchID%TYPE:=03HAMNJ;
BEGIN
UPDATE Book
SET nNoOfCopies=nNoOfCopies+2
WHERE cBranchID IN (vBrID1, vBrID2)
AND cBookName=Alien Legacy;
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE(Record Updated);
ELSIF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE(Record not Updated);
END IF;
:RowsUpd:=(SQL%ROWCOUNT || rows updated);
END;
/
Displays:
Record updated.
In the code, a bind variable RowsUpd has been declared using the VARIABLE
command. Its value is displayed by using the PRINT command.
PRINT RowsUpd
ROWSUPD
--------2 rows updated.

What are Explicit Cursors?


Explicit cursors have the same functionality as implicit cursors, but are explicitly
declared by the developer. When you place a SELECT statement within an explicit
cursor you have
more control over the way it functions. During the execution of any SQL statement,
an implicit cursor is used. However, in case you want to work on every row that the
SQL statement fetches, you should use explicit cursors.

108
SQL Star International Ltd.

Explicit cursors are used to:

Process and control the cursors in a PL/SQL block explicitly

Fetch and process more than one row returned by a query, one at a time

Keep track of the current row being processed

A difference between an implicit and explicit cursor is that although implicit cursor
performs an array fetch, the existence of a second row always raises the exception
TOO_MANY_ROWS. Explicit cursors can be used to fetch multiple rows and reexecute parsed queries in the work area.

Explicit Cursors
Developers use explicit cursors in order to process the results of the SQL statements
by manipulating individual records in the result set.
Follow these steps to use an explicit cursor:

Declare and define the cursor

Open the cursor

Fetch data from the cursor

Close the cursor

Declaring a Cursor
A cursor is declared in the declarative section of the PL/SQL block. Syntax for
declaring a cursor is:
CURSOR <cursor name> IS
SELECT statement

[This is not a complete syntax.]


Where,
cursor name is the name given to the explicit cursor declared.
SELECT statement is the statement that returns multiple rows to be processed by the
cursor.
Once the cursor has been declared, it is ready for use. However, only when the
cursor is opened, the SELECT statement defined within the cursor is executed and
parsed.
The following code shows how cursors are declared:

DECLARE

109
SQL Star International Ltd.

vBkName Book.cBookName%TYPE;
CURSOR curCategoryBooks IS
SELECT DISTINCT (cBookName)
FROM Book
WHERE cCategoryID=01FIC
AND cBranchID=02CHSNJ;
This code declares a cursor curCategoryBooks, which defines a SELECT statement
to retrieve books of fiction category at the Chester branch. A variable vBkName is
also declared which holds the values fetched by the cursor.
Remember two points while declaring cursors:

Variables used within the SELECT statement in the cursor must be declared
before the cursor is declared.

Do not use the INTO clause while declaring the cursor as it is used in the
FETCH statement.

Opening a Cursor
You invoke a cursor by opening it. A cursor comes into existence only when you open
it and not when you declare it. A cursor is opened in the executable section of a
PL/SQL block. The syntax to open a cursor is:
OPEN <cursor name>

[This is not a complete syntax.]


To open the cursor you have just declared, type the following code:
BEGIN
OPEN curCategoryBooks;
When the cursor is opened, the SQL query is executed and the rows are fetched. The
record pointer is positioned at the first row fetched. The activities that occur when a
cursor is opened are:
1. Memory is allocated to an area that will hold data for processing.
2. The SQL statement is parsed.
3. The memory addresses of the input variables are retrieved and values are set
to them.
4. The returned rows (active set) are identified.
5. The record pointer is positioned before the first row in the active set. If there
are no records in the active set, when the cursor is opened, PL/SQL does not
raise an exception. The status of the cursor is checked after the FETCH
statement.

110
SQL Star International Ltd.

Fetching Data in a Cursor


After a cursor is opened, the result set or active set of the executed SQL statement
is brought into the cursor work area.
The syntax for fetching the records into the cursor is:
FETCH <cursor_ name> INTO [ variable1, variable2,
variableN | record_name];
Where,
FETCH is the keyword used.
cursor_name is the name of the cursor that is already declared.
INTO keyword is used to put the retrieved records into the output variables.
variable1, variable2variableN are the output variables previously declared to store
the results.
record_name is the name of the row where the retrieved data is stored. Use the
%ROWTYPE attribute to declare the record variable.
The OPEN statement implicitly parses and executes the SQL statements. The parsing
happens till all the records in the cursor are processed. The FETCH statement can be
implemented on the explicit cursors with SELECT statements.
When you fetch data into a cursor, following changes take place:

The record pointer is moved to the next row in the active set.

The output variables are set with values retrieved from the cursor.

The records fetched are then processed using SQL and PL/SQL statements. The
values retrieved from the query, must have corresponding variables of compatible
datatypes in the INTO statement.
The FETCH statement works with two kinds of variables:

Stand-alone variables that are used to store the values of each column of the
record fetched by the cursor. The values are set depending on positional
specifications.

Declared records that contain the same set of attributes as the columns
defined by the cursor. The order of columns in the declared record type and
the columns defined in the cursor must be the same as the cursor values are
stored in the declared record type based on positional specification.

The cursor you have just declared and opened is ready to fetch rows one by one into
the variable declared. The following code will illustrate this:
LOOP
FETCH curCategoryBooks INTO vBkName;
Since the explicit cursor processes multiple rows by fetching one row at a time, you
need to therefore place the FETCH statement within a loop.

111
SQL Star International Ltd.

Points to remember when using the FETCH statement:

Ensure that the variables in the INTO clause of the FETCH statement is of the
same number and datatype as that of the SELECT statement.

The variables should match the columns in position.

If there are no rows left to process, then FETCH will not retrieve any values.

Closing the Cursor


Once you have processed the data fetched by the cursor it must be closed explicitly.
Closing the cursor releases the work area used by the cursor and thereby frees
system resources. You can exit the PL/SQL block without closing the cursor.
However, it is a good practice to close the cursor to free the system resources used
by the cursor. After you complete work with the records fetched by the cursor, you
should close the cursor using the CLOSE command. The syntax is:
CLOSE cursor_name;
[This is not a complete syntax.]
Once you have finished processing rows fetched by curCategoryBooks cursor, you
can close it by issuing the CLOSE statement as follows:
CLOSE curCategoryBooks;
You can reestablish connection with the active set once a cursor is closed, but do not
try to fetch data from a closed cursor. This causes the INVALID_CURSOR exception
to be raised. There is a default number set in the system parameters for each user
as to how many cursors can be opened by the user at one time. This default number
is 50. This number is decided by the OPEN_CURSORS parameter of the database
parameters.

Explicit Cursor Attributes


Attributes of cursors are essentially meant to check the status of the cursors. The
attributes with the implicit cursors are used to find out if the execution was
successful on the records returned by the SQL statement.
Attributes for explicit cursors are the same as those for implicit cursors. To refer to
the implicit cursor attribute you must prefix SQL to the attributes. For instance,
SQL%ROWCOUNT or SQL%NOTFOUND. When referring to these attributes in an
explicit cursor, prefix the attributes with the name of the cursor. For example,
cursor_name%ROWCOUNT
Use of Cursor Attributes:

112
SQL Star International Ltd.

%ISOPEN

:-

Checks whether the cursor is open or not.

%ROWCOUNT :-

Retrieves an exact number of rows affected.

%NOTFOUND :-

Determines when to exit the loop.

Now that you know how cursors are declared and what its various attributes are, you
need to complete the code wherein you had declared the cursor curCategoryBooks.
This is how it can be done:
DECLARE
vBkName Book.cBookName%TYPE;
CURSOR curCategoryBooks IS
SELECT cBookName FROM Book
WHERE cCategoryID = 01FIC
AND cBranchID = 02CHSNJ;
BEGIN

IF NOT curCategoryBooks%ISOpen THEN


OPEN curCategoryBooks;
END IF;
LOOP
FETCH curCategoryBooks INTO vBkName;
EXIT WHEN curCategoryBooks%ROWCOUNT>2 OR
curCategoryBooks%NOTFOUND
DBMS_OUTPUT.PUT_LINE (vBkName);
END LOOP;
CLOSE curCategoryBooks;
END;
/
The PL/SQL block is created to satisfy the requirement of a particular enquiry,
requiring the library desk officer to retrieve only two fiction books belonging to the
Chester branch.
You could use a simple SELECT statement to display the result. Would this work?
Oracle does not provide a clause with the SELECT statement to restrict the number
of rows to be retrieved after satisfying the WHERE condition and then perform the
manipulations required.

113
SQL Star International Ltd.

However, this requirement could be met using explicit cursors and its attributes.
Therefore, the library database developer decided to generate the above code.
In the above code, curCategoryBooks cursor retrieves all the fiction books that are
stored in the Chester branch. Out of the total number of fiction books in the Chester
branch, the requirement is to fetch only two of them. This is achieved by using the
%ROWCOUNT attribute.
On executing the above code you get the following result:
Triumph Of Katie Byrne
Woman Of Substance

Parameters in Cursors
Cursors get values in the SELECT statement from a table with hard-coded values.
However, at times you may have to reuse the cursors and it is quite tedious to
rewrite the cursor structure. Therefore, you need to use a method by which you can
specify where the data needs to be picked up from. This should be specified when
the cursor is opened. This method is implemented by the use of parameters in
cursors.
Parameters are variables used to pass values to a block during runtime. Parameters
pass values to the cursor when the latter is opened and the values are used in a
query. This allows you to open and close a cursor many times in a block. Each time
you pass a different set of values you get the appropriate results. Every parameter in
the cursor must have a corresponding parameter in the OPEN statement. Datatypes
for cursor parameters are the same as those for scalar variables, but you cannot
specify a size for the parameter when declaring it. The parameter names are used in
the query expression in the cursor for referencing.

The syntax of using parameters in the cursor is:


CURSOR <cursor_name>
[(<parameter_name> datatype, )]
IS
SELECT statement;
Where,
cursor_name is the name given to declare the cursor
parameter_name is the name of the parameter declared. You can have any number
of parameters.

114
SQL Star International Ltd.

datatype is the datatype for the parameter. Width should not be specified.
SELECT statement is the query associated with the cursor where the parameters are
used.
Values are passed to the cursor when you open it. You can use PL/SQL variables,
host variables or literals to pass values to the parameters. You can also set high and
low levels to parameters allowing the cursor to select data within the range.
Parameters in cursors also allow you to have more control over the processing of the
data.
For instance, the library desk officer is frequently faced with enquiries requiring him
to retrieve a list of all the books belonging to particular categories. To help him
perform this task the database developer generates the following code wherein the
desk officer is required to feed in only the category name based on which the list of
books belonging to the category specified are displayed. This code makes use of a
parameterized cursor where the parameter takes in the category name.
DECLARE
vBookName Book.cBookName%TYPE;
vCatgName Category.cCategoryName%TYPE:=&catname;
CURSOR curCategoryBooks (Catgname CHAR)IS
SELECT DISTINCT (cBookName)
FROM Book B,Category C
WHERE B.cCategoryID = C.cCategoryID
AND C.cCategoryName = vCatgName;
BEGIN
OPEN curCategoryBooks(vCatgName);
LOOP
FETCH curCategoryBooks INTO vBookName;
EXIT WHEN curCategoryBooks%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(vBookName);
END LOOP;
CLOSE curCategoryBooks;
END;
In this code a substitution variable catname is declared, which prompts the user for
a category name every time the code is executed. The value entered is stored in
vCatName and then passed into the cursor parameter Catgname when the cursor
is opened. Accordingly, the books belonging to the category entered are retrieved by
the cursor.

115
SQL Star International Ltd.

Enter value for catname: Science Fiction


Alien Legacy
Animal Farm
The Catchers Of Heaven: A Trilogy

Cursors and PL/SQL Records


Records can be declared to match the columns of a table or the list of selected
columns in the SELECT statement of an explicit cursor. This kind of declaration
makes it easy to store the active set as all you need to do is fetch the values into the
record and the values of the fetched row are directly set to the corresponding fields
of the record.
For instance, whenever a members membership is terminated his details are
entered into a NonMember table. To make this insertion an easier task, the
developer generates a code that prompts the user for the ID of the member whose
membership has been terminated and accordingly inserts his details into the
NonMember table. To accomplish this, the developer uses a cursor, which retrieves
details of members and then declares a record based on the cursor. Therefore after
having fetched rows from the cursor into the record, the record values can be used
to insert into the NonMember table.

DECLARE
vMemberID Member.cMemberID%TYPE:=&memid;
CURSOR curNonMember IS
SELECT cMemberID,cFirstName,cLastName,cPhone,
dMembershipDt,cBranchID
FROM Member;
recNonMember curNonMember%ROWTYPE;
BEGIN
OPEN curNonMember;
LOOP
FETCH curNonMember INTO recNonMember;
EXIT WHEN curNonMember%NOTFOUND;
IF recNonMember.cMemberID=vMemberID THEN
INSERT INTO NonMember

116
SQL Star International Ltd.

VALUES

(sqSerialNo.NEXTVAL,

recNonMember.cMemberID,
recNonMember.cFirstName,
recNonMember.cLastName,
recNonMember.cPhone,
recNonMember.dMembershipDt,
recNonMember.cBranchID);
END IF;
END LOOP;
CLOSE curNonMember;
END;
/
On execution of the code, you are prompted to enter the ID of the member whose
membership has been terminated in the following way:
Enter value for memid: APP039601
PL/SQL procedure successfully completed.
You can verify whether the details of member with ID APP039601 has been inserted
into the NonMember table by issuing a SELECT statement as follows:
SELECT *
FROM NonMember
WHERE cMemberID=APP039601';

The query displays the following result:


NSERIALNO CMEMBERID CFIRSTNAME

CLASTNAME CPHONE

DMEMBERSH CBRANCH

--------

--------- ------

--------- -------

27

--------- ---------APP039601

Perry

Paine

9875566643 12-MAR-96 01ADLNJ

Cursor FOR Loops


By now you know that some processing is done on each row of the active set fetched
by the cursor. This creates overhead on the server, as all the rows fetched by the
cursor must be processed in a loop. Instead, you can use a simple loop-exit
statement, where the code is written within the loop syntax and an exit test

117
SQL Star International Ltd.

condition defined explicitly. This could be slightly complicated as compared to the


easier techniques available. The
CURSOR FOR loop statement allows you to process groups of rows with some
specified set of operations done implicitly.

The implicit operations performed by CURSOR FOR LOOP are:

Opening

Parsing

Executing

Fetching rows of data from the cursor for each iteration

Declaring variables to handle the fetched data (thereby reducing the size of
the declaration section)

Handling the CursorName%NOTFOUND attribute and terminating the loop if


the value of this attribute is TRUE

The syntax of a CURSOR FOR loop is:


FOR <record_name> IN <cursor_name>
LOOP
statement1;
statement2;
. . .
END LOOP;
Where,
record_name is the name of the record declared implicitly.
cursor_name is the name of the explicit cursor declared.
Points to remember when using the CURSOR FOR loop:

Do not declare the record that controls the loop, as its scope is limited to
within the loop.

Test the status of the cursor attributes during the loop if required.

Use cursor parameters by defining them in parentheses after the cursor name
in the CURSOR FOR loop.

If the cursor operations need to be done manually, then do not use the
CUSOR FOR loop.

Specify the SELECT statement instead of the cursor name in the CURSOR FOR
loop. This SELECT statement is an explicit cursor without a name and hence
its attributes cannot be tested. The scope of the cursor in this case is limited
to the CURSOR FOR loop.

118
SQL Star International Ltd.

The usage of CURSOR FOR loop is to accomplish the three major processes (opening,
fetching and closing) of a cursor in a single step. Use CURSOR FOR loop to rewrite
the code written to retrieve fiction books belonging to the Chester branch.
DECLARE
CURSOR curCategoryBooks IS
SELECT cBookName FROM Book
WHERE cCategoryID=01FIC
AND cBranchID=02CHSNJ;
BEGIN
FOR recCatgBooks IN curCategoryBooks
LOOP
DBMS_OUTPUT.PUT_LINE (recCatgBooks.cBookName);
END LOOP;
END;
/
Wings Of The Storm
Triumph Of Katie Byrne
Woman Of Substance
PL/SQL procedure successfully completed.

Ensuring Faster Updates


There are some clauses that can be used to enhance the processing of a cursor.
Ensure that while processing records in a table, there is no inconsistency in the data
and all users must be able to see the correct and same version of the data. Ensure
that while one user is manipulating data and the data is being processed in the

119
SQL Star International Ltd.

cursor, no other user must be able to access it. Two clauses are used for this
purpose:

The FOR UPDATE clause

The WHERE CURRENT OF clause

The FOR UPDATE Clause


When you update records fetched by a cursor from a table, other users accessing the
table should not view incorrect data. Also the database should be able to handle
more than one user manipulating the same database. Hence, when you update data
using a cursor, you can lock the rows in the database that is being updated. Locking
records ensures that no other user can manipulate data in those records while they
are being updated by the cursor. Locking can be done by using the FOR UPDATE
clause. The syntax to use the clause is:
SELECT . . .
FROM
FOR UPDATE [OF column_reference][NOWAIT];

[This is not a complete syntax]


Where,
column_reference is the name of the column in the table that the query references
to.
The NOWAIT keyword returns an error message if the rows are locked by another
session.
The FOR UPDATE clause is always placed last in the SELECT statement.
When the FOR UPDATE refers to some columns in a table, the rows of that table are
locked. When the FOR UPDATE clause is used, rows in the active set are locked
exclusively before the OPEN returns values.
In prior releases, when the SELECT FOR UPDATE clause required a lock, and the
server could not obtain one, it had only two alternatives. One was to wait for the lock
to be released, and the other was using the NOWAIT clause, which would test and
return immediately with an error message that there was a failure in trying to get
locks for the loop.
Oracle9i had introduced another alternative to tackle this problem, where the user
can specify the time interval to wait for, before returning with the error message.
This alternative makes use of the FOR UPDATE WAIT clause. This clause:

Prevents indefinite wait on rows that are locked by other sessions

Gives applications more control over the wait time for locks

120
SQL Star International Ltd.

Benefits interactive applications because users


indeterminate time intervals for release of locks

would

not

appreciate

You can specify an integer along with the FOR UPDATE WAIT clause to specify the
number of seconds to wait for a lock. Once the number of seconds specified elapses,
and if the server is unable to obtain the lock within that time interval, it returns the
following error:
ORA-30006: resource busy; acquire with WAIT timeout expired.
The library database developer may need to generate a code for the purpose of
updating the number of copies of books. But to avoid the number of copies from
being updated by other users at the same time, he decides to lock it using the FOR
UPDATE OF clause in the following way:
DECLARE
CURSOR curBranchBkCopies IS
SELECT cBookName,nNoOfCopies
FROM Book
WHERE cBranchID=04RANNJ
FOR UPDATE OF nNoOfCopies WAIT 30;

In this code, the developer declares a cursor that selects the number of copies of
books stored in a specified branch. But the developer locks the rows retrieved by the
cursor as he intends to update the number of copies. This prevents others from
updating the same rows. In the code, the query waits for 30 seconds to obtain a
lock.

WHERE CURRENT OF Clause


Cursors point to the rows in the active set one at a time and move to the next row
retrieved. The row that the cursor marks is the current row in the active set. The
WHERE CURRENT OF clause can be used in these situations. This clause allows you
to perform operations on the row that is currently being referred to by the cursor
and not by using the pseudocolumn ROWID. Use the FOR UPDATE clause in the
cursor query so that the rows are locked for update when the cursor is opened.

121
SQL Star International Ltd.

The syntax to use the clause is:


WHERE CURRENT OF cursor;

[ This is not a complete syntax]


Using the WHERE CURRENT OF clause in place of FOR UPDATE OF clause in the
previous code, enables the user to update the number of copies currently fetched by the
cursor, prohibiting any other user from updating the column at the same time.
DECLARE
CURSOR curBranchBkCopies IS
SELECT cBookName,nNoOfCopies
FROM Book
WHERE cBranchID=04RANNJ
FOR UPDATE OF nNoOfCopies WAIT 30;
BEGIN
FOR recBranchBkCopies IN curBranchBkCopies
LOOP
UPDATE Book
SET nNoOfCopies = recBranchBkCopies.nNoOfCopies + 1
WHERE CURRENT OF curBranchBkCopies;
END LOOP;
COMMIT;
END;
/

Some points to remember when using the WHERE CURRENT OF clause are:

The cursor containing query statement should be declared with the FOR
UPDATE clause.

You can use the clause in the DELETE or UPDATE statements to refer to the
latest row processed by the FETCH command.

You do not have to refer to the rows using ROWID.

122
SQL Star International Ltd.

Subqueries in Cursors
Subqueries are generally used in the WHERE clause of a query. You can also use
them in the FROM clause. In the latter case, it results in a temporary data source for
the query. Subqueries in cursors function the same way as they do when used in a
query not involving cursors.
For example, the library management wishes to have a report that displays a list of
those members who have paid a fine amount more than the average fine received on
the same issue date as that of the member. This could be achieved by declaring the
following cursor:
DECLARE
CURSOR curIssDtFine IS
SELECT T.dIssueDt IssueDt,T.cMemberID MemberID,T.cBookID
BookID,T.nFine Fine, TR.FineAvg FineAvg
FROM Transaction T,(SELECT dIssueDt,
AVG (nFine) FineAvg
FROM Transaction
GROUP BY dIssueDt) TR
WHERE T.dIssueDt = TR.dIssueDt AND
T.nFine > TR.FineAvg;
recIssDtFine curIssDtFine%ROWTYPE;
BEGIN
OPEN curIssDtFine;
LOOP
FETCH curIssDtFine INTO recIssDtFine;
EXIT WHEN curIssDtFine%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(recIssDtFine.IssueDt||
||recIssDtFine.MemberID||
||recIssDtFine.BookID||
||recIssDtFine.Fine||
||recIssDtFine.FineAvg);
END LOOP;

123
SQL Star International Ltd.

CLOSE curIssDtFine;
END;
/
This code declares a cursor curIssDtFine, which selects the members IDs along
with the date on which the books are issued. But the cursor only selects those
members who have paid a fine amount greater than the average fine received by the
library on each issue date. This is achieved by using subquery in the FROM clause.
The subquery returns the average fine received on each issue date. The outer query
compares the fine amount of each member against the average fine calculated.

On executing the above code, you get the following result:


05-SEP-96 CRB038901 ROM030000013

6 4.5

15-JUN-98 BJB019503 ROM030003210

7.5 6

12-FEB-99 BKF079702 MAT020003334


08-AUG-00 AVE119705 CHI050001509

6 3.75
10.5 6.75

06-JAN-01 CKB109305 SFI050001993

12 6.75

14-APR-01 BKF079702 MEC020000103

6 4.5

PL/SQL procedure successfully completed.

Summary
In this chapter, you have learnt that:

 Cursors are private work areas used to execute SQL statements. They are of
two types.
1. Implicit cursor are created, maintained by the server and named internally as
SQL. Its status can be checked using the attributes SQL%ISOPEN,
SQL%FOUND, SQL%NOTFOUND and SQL%ROWCOUNT.

124
SQL Star International Ltd.

2. Explicit cursors defined and controlled by the user have same attributes as
implicit cursors but prefixed by name given by the user.
E.g. Emp_Cursor%NOTFOUND

 An Explicit cursor needs to be declared, opened, fetched and closed explicitly by


the user. One can practically automate the entire process of using Explicit
cursor through CURSOR FOR LOOP.

 Modification in the table through cursor is done through FOR UPDATE and
WHERE CURRENT OF clauses.

 FOR UPDATE clause locks the rows to be updated and WHERE CURRENT OF
points to the row currently being referred to, by the cursor.

125
SQL Star International Ltd.

Lab Exercises
1.Create a PL/SQL block that deletes the data from employees table based

on

the

department id passed. Save the code in the file.


a. Use the DEFINE command to provide the department ID.
SET VERIFY OFF
VARIABLE g_result VARCHAR2(40)
DEFINE p_deptno = 280
b. Pass the value to the PL/SQL block through a iSQL*plus substitution variable.
Print to the screen the number of rows affected.

2.Create a PL/SQL block that will retrieve the first 5 employees one after the other.
[Note: You need to create a table TopEarners. The

structure of the table should be

as shown below.]

3.Create a PL/SQL block that will determine the top 10 earners of the organization.
The block should:
Gather the salaries of the top 10 earners within a loop. Ensure that salaries
are not duplicated.
Store the salaries gathered in the TopEarners table
4.Create a PL/SQL block to retrieve the list of employees (their first name and
department number) working for the IT department (department ID is 60).

5.Create a PL/SQL block that will retrieve the first name, job ID, and salary of
employees belonging to department number 80. If the salary is less than $10,000
and if job ID is SA_REP then display the message <First name> is due for a salary
raise, or else display the message <First name> is not due for any salary raise as
shown below.

John is not due for any salary raise


Karen is not due for any salary raise
Alberto is not due for any salary raise

126
SQL Star International Ltd.

Gerald is not due for any salary raise


Eleni is not due for any salary raise
Peter is not due for any salary raise

PL/SQL procedure successfully completed.


6.
Write a PL/SQL code to create a parameterized cursor, which will display the
name and salary of all employees from the Employees table who have a salary less
than the specified value, which is passed as a parameter.

7.

Create a PL/SQL block that will retrieve the department ID and department
name of departments whose ID is less than 80. Pass the department IDs retrieved as
parameters to another cursor that would retrieve the first name, hire date, job ID
and salary of employees belonging to that department and whose employee ID is
less than 130. The block result should be as follows:

Dept No.: 10 Dept Name: Administration

Dept No.: 20 Dept Name: Marketing

Dept No.: 30 Dept Name: Purchasing


ID: 114 Name: Den Job: PU_MAN Hired on: 07-DEC-94 earns: 11000
ID: 115 Name: Alexander Job: PU_CLERK Hired on: 18-MAY-95 earns:
3100
..
Dept No.: 40 Dept Name: Human Resources

Dept No.: 50 Dept Name: Shipping


ID: 120 Name: Matthew Job: ST_MAN Hired on: 18-JUL-96 earns: 8000
`

ID: 121 Name: Adam Job: ST_MAN Hired on: 10-APR-97 earns: 8200

127
SQL Star International Ltd.

..
Dept No.: 60 Dept Name: IT
ID: 103 Name: Alexander Job: IT_PROG Hired on: 03-JAN-90 earns:
9000
ID: 104 Name: Bruce Job: IT_PROG Hired on: 21-MAY-91 earns: 6000

Dept No.: 70 Dept Name: Public Relations


PL/SQL procedure successfully completed.

8.

Create a PL/SQL block that will increase the salary by 10% of employees
whose salary is less than $7000 and who belong to department number 50. [Hint:
Use FOR UPDATE and WHERE CURRENT OF clause].

128
SQL Star International Ltd.

Chapter 6

Handling Exceptions in PL/SQL


Types of Exceptions
Raising and Handling Exceptions

129
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Describe the different types of Exceptions
 Raise and Handle Exceptions

130
SQL Star International Ltd.

Introduction
There can be situations when a PL/SQL code might generate a runtime error. This
error in PL/SQL is known as an Exception which is an identifier that is raised when it
encounters an unacceptable situation during the execution thereby causing the block
to terminate. The exceptions are handled by a special code called exception
handlers.

Working with Exceptions

The activities that are included when working with exceptions are:
 Declaring an exception
 Raising an Exception
 Trapping and Handling the Exception
 Propagating the Exception
Let us look into each of them in detail.
Declaring Exceptions
Exceptions are also known as Named Identifiers. These are declared in the
declarative section of the PL/SQL block.
Syntax for declaring Exceptions:
DECLARE
exception_name EXCEPTION;
Raising Exceptions
An exception is raised when unacceptable situations are encountered. It can be
raised implicitly or explicitly.
Syntax for raising an exception explicitly using the RAISE keyword:
BEGIN
.............
RAISE exception_name;
Trapping Exceptions
Exceptions are trapped when the control encounters a RAISE EXCEPTION in the
executable section of a PL/SQL code. Then the control is given to the corresponding

131
SQL Star International Ltd.

exception handler in the exception section of the block. After an exception is handled
it terminates the PL/SQL code and leaves the block.
The syntax to trap an exception is:

EXCEPTION
WHEN exception1 [or excpetionN. . .] THEN
statement1;
statement2;
[WHEN excetpion 2 [or exceptionN . . .] THEN
statement1;
statement2;
. . .]
[WHEN OTHERS THEN
statement1;
statement2;
. . .]

Where,
exception1 exceptionN are the names of the exceptions handler.
statement1 . . . are SQL or PL/SQL statements
OTHERS is a clause that traps unspecified exceptions. This is an optional clause.
Only the exceptions specified in the executable section of the block can be handled
by the exception handlers. The exceptions not specified in the executable block are
handled by the WHEN OTHERS clause. Hence, this clause is always placed last. All
untrapped exceptions are trapped with this clause.

Points to remember while writing code for trapping exceptions:

Begin that part of the PL/SQL block with the keyword EXCEPTION.

Define handlers within the block with their respective set of actions.

PL/SQL executes only one exception handler before terminating the block.

The OTHERS clause is to be placed after all the exception handlers are coded.
There can only be one OTHERS clause.

132
SQL Star International Ltd.

Propagating Exceptions

When the block that raised the exception handles it, the block terminates as per the
instructions in the handler. Then the END statement of the block is encountered and the
control is given back to the enclosing block for execution.
In case of Nested blocks, when an exception is raised and there is no corresponding
handler within the block, the control is shifted to successive enclosing blocks in order of
execution. When the control is passed to these blocks the executable transactions of that
block are skipped. The advantage of doing this is, you can code handlers for specific
exceptions within the block. The enclosing successive blocks can handle the general
exceptions. If the exception is not handled by any of these blocks then the control is
passed on to the calling or host environment. All the other blocks are terminated.
Given below is a list of calling environments and the messages they generate when they
have to handle an exception.

The SQL * PLUS environment displays the error number and its message on
screen.

The Procedure Builder also displays the error number and its message.

In Oracle Developer Forms, the error number and message is trapped by the
in-built functions ERROR_CODE and ERROR_TEXT in a trigger.

A pre-compiler application accesses the error number using the SQLCA data
structure.

Finally, an enclosing PL/SQL block traps the exception in a handler in its exception
handling section.

Types of Exceptions
The three types of exceptions are:

Predefined exceptions

User-defined exceptions

Non-predefined exceptions

Predefined Exceptions
While writing SELECT statements you would have encountered errors like too many
rows or no data found. This is an error generated by the Oracle server. Errors like
these are trapped and handled by the server itself. These errors are called
Predefined Exceptions, as these are already defined within the server. These errors
occur due to some common situations in a database. Instead of you invoking and
trapping these errors explicitly, they are done automatically. A list of some of the

133
SQL Star International Ltd.

common pre-defined exceptions is given below in a table, along with their error
number and what the error implies.

134
SQL Star International Ltd.

When predefined errors occur, if you want your code to execute in a different
manner then code must be included in the exception section. For instance, the desk
officer of the
New Jersey Central Library handles frequent enquiries pertaining to books written by
particular authors by querying the Book table. But to save him the task of querying
the table repeatedly, the library database developer decides to embed the requisite
query into a
PL/SQL block. The database developer writes a code that will retrieve books written
by the author whose name the desk officer is prompted to enter. But while writing
such a code, the database developer needs to handle commonly encountered
exceptions such as NO_DATA_FOUND or TOO_MANY_ROWS and word them in a way
that would make it easy for the desk officer to interpret. The following code
illustrates this:
DECLARE
vAuthorName Book.cAuthorName%TYPE:=&author;
vBkName Book.cBookName%TYPE;
BEGIN
SELECT cBookName
INTO vBkName
FROM Book

135
SQL Star International Ltd.

WHERE cAuthorName=vAuthorName;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE(more than one book written by
author||- ||vAuthorName);
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(no book written by author||||vAuthorName);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(An
the author

error

has

occurred:

check

name you have just entered);

END;
/

This code returns easily understandable error messages on being executed. Such as:

more than one book written by author-Shakespeare: If the author name


entered causes the embedded query to return more than one book written by
the same author

no book written by author-Jane Austen: If the author name entered


causes the query to return no books by the same author

An error has occurred: check the author name you have just entered: If
any other error such as misspelling an authors name or entering a name that
does not exist in the database table.

However, instead of displaying your own error message in the WHEN OTHERS clause,
you could easily identify the error code and error message associated with errors (other
than NO_DATA_FOUND and TOO_MANY_ROWS) using the following two functions:

SQLCODE, which returns the numeric value of an error

SQLERRM, which returns the message associated with the error number

Some of the SQLCODE values are:


0 if no exception is encountered
1 for user defined exceptions
+100 for NO_DATA_FOUND exception
negative number for any other Oracle server error

136
SQL Star International Ltd.

Using the two functions, the WHEN OTHERS clause of the above code could be
rewritten. But you need to declare two variables to hold the values returned by the
functions in the following way:

DECLARE
vErrCode NUMBER;
vErrMssg VARCHAR2(255);
vAuthorName Book.cAuthorName%TYPE:= &author;
vBkName Book.cBookName%TYPE;
BEGIN
SELECT cBookName
INTO vBkName
FROM Book
WHERE cAuthorName=vAuthorName;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE(more than one book written by
author||- ||vAuthorName);
WHEN OTHERS THEN
vErrCode:= SQLCODE;
vErrMssg:= SQLERRM;
DBMS_OUTPUT.PUT_LINE(vErrCode||-||vErrMssg);
END;
/

On executing the above code, the functions SQLCODE and SQLERRM return the error
number and error message respectively on encountering an authors name whose books
are not stored in the New Jersey Central Library.
Enter value for author: Jane Austen
100-ORA-01403: no data found
The Oracle server provides another method (other than using
DBMS_OUTPUT.PUT_LINE) to communicate predefined exceptions interactively. The
method uses RAISE_APPLICATION_ERROR procedure. This procedure returns a
non-standard error code and error message because they are user-defined rather than
137
SQL Star International Ltd.

system defined. Therefore, this procedure returns error messages that are consistent with
other Oracle server errors. The syntax for using RAISE_APPLICATION_ERROR is:
RAISE_APPLICATION_ERROR (error_number, error_message);
Where,
error_number is a number the user specifies for the exception. The number specified
must be between -20000 and 20999.
error_message is a message the user specifies for the exception. It must be a character
string and can be up to 2,048 bytes.
The above code could be rewritten using RAISE_APPLICATION_ERROR as follows:
DECLARE
vAuthorName Book.cAuthorName%TYPE:=&author;
vBkName Book.cBookName%TYPE;
BEGIN
SELECT cBookName
INTO vBkName
FROM Book
WHERE cAuthorName=vAuthorName;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20002,More than one book
written by author||||vAuthorName);
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20111,No book written by
author||vAuthorName);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE (An error has occurred: check
the

author name you have just entered);


END;
/

On executing the code, RAISE_APPLICATION_ERROR procedure displays the error


number and message defined within it.
Enter value for author: Shakespeare
138
SQL Star International Ltd.

ORA-20002: more than one book written by author


Shakespeare
User-defined Exceptions

Predefined exceptions are handled implicitly. However, you might want to handle an
exception explicitly. An exception that is raised and handled explicitly is called userdefined exception. User-defined exceptions are used to implement business rules that are
not trapped by the server. In case of user-defined exceptions, you need to write code in all
the sections of the block:

Declare the exception in the declarative section. The syntax for declaring an
exception is:
<exception_name> EXCEPTION, where exception_name is the name of the
exception.

In the execution section, write the code for the exception and raise it if the
test conditions are met. The syntax to raise an exception is:
RAISE exception_name, where exception_name is the name of the exception
declared.

In the exception handling section, include the WHEN clause, the name of the
exception and the code to handle it. When the exception is raised, control of
the block is transferred to this section. Include the WHEN OTHERS clause so
that it can catch all those exceptions that do not have a handler within the
code.

The following example shows how to handle exceptions that are not trapped by the
server.
A desk officer enters an invalid author name while updating the number of copies of their
books. While doing this, an exception is raised. To trap this exception, he would first
declare an exception, raise the exception and then handle it.
DECLARE
exInvalidAuthor EXCEPTION;
vAuthorName Book.cAuthorName%TYPE:=&author;
BEGIN
UPDATE Book
SET nNoOfCopies=nNoOfCopies+1
WHERE cAuthorName=vAuthorName;
IF SQL%NOTFOUND THEN
RAISE exInvalidAuthor;
END IF;

139
SQL Star International Ltd.

EXCEPTION
WHEN exInvalidAuthor THEN
DBMS_OUTPUT.PUT_LINE(The author name you have entered is
not

a valid one);
END;
/

On executing the code, if the desk officer enters a name not listed in the Book table, he
would get an error.
The error is handled by declaring a user-defined exception exInvalidAuthor in the
declarative section, which is raised when the embedded query returns no rows.
exInvalidAuthor raised is then handled in the exception section, which displays the
following error message:
The author name you have entered is not a valid one

The above code could be re-written using RAISE_APPLICATION_ERROR procedure.


You had seen the usage of this procedure in an earlier code. The procedure had been used
in the exception section of the code. You could also use it in the executable section as
shown:
DECLARE
vAuthorName Book.cAuthorName%TYPE:=&author;
BEGIN
UPDATE Book
SET nNoOfCopies=nNoOfCopies+1
WHERE cAuthorName=vAuthorName;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20201,the
have

author

name

you

entered is not a valid one);


END IF;
END;
/

On entering an invalid authors name, you get the following error message:
Enter value for author: Ayn Rand
ORA-20201: the author name you have entered is not a
valid one

140
SQL Star International Ltd.

Non-predefined Exceptions
You have seen how to handle an exception explicitly. You might want to customize
the predefined exceptions and thus extend the list. These errors are associated with
predefined errors. You can customize the predefined errors by assigning names to
the error number and writing code to handle these exceptions. You can implement
this by using the
PRAGMA EXCEPTION_INIT keyword. This statement is a compiler directive. It
instructs the compiler to associate the exception name given with the Oracle error
number. When you use this statement, you need not raise the exception. You need
to declare it and write exception handling code. Each time an error occurs during
execution, the control is shifted to the exception section and the error is handled.
There are situations where non-predefined exceptions(also known as Internal
Exceptions) need to be handled. For instance, whenever the database users of the
New Jersey Central Library try to delete any members details from the Member
table the server displays an error message, indicating an integrity constraint
violation. This is because the foreign key column in the Transaction table is
referencing the cMemberID column of the Member table. But the error message
displayed is not very descriptive. Also, there is no named predefined exception to
handle this. Therefore, the database developer should declare his own exception and
associate it with the Oracle error number for integrity constraint violation.
DECLARE
exMemberIssBks EXCEPTION;
PRAGMA EXCEPTION_INIT(ExMemberIssBks,-2292);
vMemberID Member.cMemberID%TYPE:=&memberid;
BEGIN
DELETE FROM Member
WHERE cMemberID = vMemberID;
COMMIT;
EXCEPTION
WHEN exMemberIssBks THEN
DBMS_OUTPUT.PUT_LINE (Cannot remove the details of member
with ID ||vMemberID||because books issued by him have not
yet been returned);
END;
/
Enter value for memberid: CDB028504

141
SQL Star International Ltd.

In this code, an exception exMemberIssBks is declared and associated with the Oracle
server error number 2292 (integrity constraint violation) using PRAGMA
EXCEPTION_INIT. Any attempt to delete details of a member raises the exception
implicitly. The control is passed to the exception handling section and the following
message is displayed:
Cannot remove the details of member with ID CDB028504 because
books issued by him have not yet been returned.

Handling Exceptions using DBMS_UTILTY Package


Usually, we give a User-defined message when we handle an exception. If we want
to see Oracle defined message along with User-defined message or display the line
number at which the error has occurred, we can use DBMS_UTILITY package
supplied by Oracle.
Let us see how to use this package while handling exceptions.
Below is an example cited, for Zero_divide predefined exception. This exception is
raised when we are trying to divide a number by 0.
CREATE OR REPLACE PROCEDURE my_proc
IS
v_num1 NUMBER:=1;
v_num2 NUMBER:=0;
v_result NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE(Performing Calculation);
v_result:= v_num1/v_num2;
EXCEPTION
WHEN ZERO_DIVIDE

THEN

DBMS_OUTPUT.PUT_LINE(Cannot divide by zero);


END;
Procedure created.
SQL> exec my_proc

142
SQL Star International Ltd.

Performing Calculation
Cannot divide by zero
PL/SQL procedure successfully completed.
The output gives you a message before handling the error and after handling the
error.
Let us now modify the code a bit. Suppose your requirement is to view the
information about all the oracle defined errors raised during the execution of this
program along with your messages, then this is solved by using
DBMS_UTILITY.FORMAT_ERROR_STACK. This package actually gives you the
information about the errors raised, which are stored in the form of Stack.
CREATE OR REPLACE PROCEDURE my_proc
IS
v_num1 NUMBER:=1;
v_num2 NUMBER:=0;
v_result NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE(Performing Calculation);
v_result:= v_num1/v_num2;
EXCEPTION
WHEN ZERO_DIVIDE

THEN

DBMS_OUTPUT.PUT_LINE(Cannot divide by zero);


DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
END;
/
Procedure created.
SQL> exec my_proc
Performing Calculation
Cannot divide by zero
ORA-01476: divisor is equal to zero
PL/SQL procedure successfully completed.
Moving further, if you want to know the line number at which an error has occurred,
then you can use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE which is a new
feature in
Oracle10g.
Let us see how it works, with an example.
1

CREATE OR REPLACE PROCEDURE my_proc

143
SQL Star International Ltd.

IS

v_num1 NUMBER:=1;

v_num2 NUMBER:=0;

v_result NUMBER;

BEGIN

DBMS_OUTPUT.PUT_LINE(calculation);

v_result:= v_num1/v_num2;

EXCEPTION

10

WHEN ZERO_DIVIDE

THEN

11

DBMS_OUTPUT.PUT_LINE(Cannot divide by zero);

12
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
13
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
14*

END;
/

Procedure created.
SQL> EXEC my_proc
Performing Calculation
Cannot divide by zero
ORA-01476: divisor is equal to zero
ORA-06512: at SCOTT.MY_PROC, line 8

PL/SQL procedure successfully completed.

144
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
 Exception are errors which arises during the program execution. They can be
handled in an exception block (EXCEPTION) or left unhandled.
 Smooth termination, customized error message and transaction intact are the
advantages of handling exceptions.

 Three different types of exceptions are:


1. Predefined: Named internally and raised automatically
2. Non-Predefined: Named by the user associated with a pre-defined
error number using PRAGMA EXCEPTION_INIT and raised implicitly
by Oracle server.

3. User-Defined: Named and raised by the user depending on the


application. Error information can be trapped using two functions
namely SQLCODE and SQLERRM.

 Customized message in oracle defined format can be shown using


RAISE_APPLICATION_ERROR procedure.

 Error messages along with line numbers can be identified using


DBMS_UTILITY.FORMAT_ERROR_BACKTRACE package.

145
SQL Star International Ltd.

Lab Exercises
1.
Create a PL/SQL block wherein a non-predefined error is displayed if a user
attempts to delete department number 40. This is because the department has employees
working in it.
2.
Create a PL/SQL block that declares a user-defined exception in case a user
updates the department name of a department that does not exist (that is, by passing a
department number that does not exist in the table).

146
SQL Star International Ltd.

Chapter 7

Creating Subprograms
Introduction to Subprograms
Invoking Subprograms
Creating Procedures
Effects of Handled and Unhandled Exceptions
Creating Functions

147
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:


Identify the need for stored codes

Use Procedures

Use Functions

148
SQL Star International Ltd.

What are Stored Codes?


You executed anonymous blocks either directly from iSQL*Plus or stored them in
files to keep the long codes intact. But, take a situation where you need to execute
the same logic n times a day. Each time you want to execute the logic, you would
have to run the script file that you had created and stored on the client side. This
increases the network traffic because the files have to be sent to the server and then
executed. There is also a possibility of the script file getting deleted.
All these generated the need for storing the codes in the database itself. Stored
codes (or subprograms) include:

Procedures

Functions

Packages

Introduction to Subprograms
Subprograms are named PL/SQL blocks, which are based on a standard PL/SQL block
structure that comprises of a declarative section, an executable section and an
optional exception section.
Subprograms are compiled and stored in the database. This helps reduce network
traffic. No file is required to be passed to the server to execute the program logic. All
that is required for the user is to refer to the name of the PL/SQL block and execute
it.
Subprograms provide modularity. Modularization refers to the process of breaking
large blocks of code into smaller groups of code known as modules. You can create
modules so that they can be used in more than one application.
The advantages of developing subprograms are as follows:

Controlling data security and integrity: Access to database objects can be


provided through subprograms, and users can invoke the subprograms only if
the required EXECUTE privilege is granted to them.

Improving performance: Codes are parsed at compile time and not runtime.
It uses the shared SQL area to avoid re-parsing the data. Since the
commands are grouped together, there will be lesser number of calls to the
database and in this process the network traffic also gets reduced.

149
SQL Star International Ltd.

Block structure for PL/SQL subprograms and anonymous PL/SQL blocks differ. This is
illustrated in the following diagram:

From the above diagram you will observe that subprograms are made up of two
parts: subprogram specification and subprogram body.
The subprogram specification contains:

<Header>: It determines how the program unit is to be invoked (or called).


The header specifies:
The subprogram type, that is, procedure, function or package
The subprogram name
The parameter list, if any
The RETURN clause (only in case of functions)

The mandatory IS or AS keyword

The subprogram body contains:

150
SQL Star International Ltd.

The declaration section between IS | AS and BEGIN keywords. The DECLARE


keyword otherwise used in anonymous blocks is not used here.

The mandatory executable section between the BEGIN and END keywords

The optional exception section between the EXCEPTION and END keywords

Steps to create a Subprogram:


1. Define your subprogram source code and save it in a script file. Use a text
editor to create a SQL script file.
2. From the iSQL*Plus browser window, select the Browse button to locate the
script file.
3. Click the Load Script button to load the file contents into the iSQL*Plus
buffer.
4. Click the Execute button to run the code.

Invoking Subprograms
To make a Subprogram work, you need to invoke it. Environments that allow you to
invoke subprograms include other subprograms, pre-compiler applications, Oracle
Developer, Oracle Discoverer, Oracle WebDB and other Oracle tools.
In the list below are the ways in which you can call a subprogram:

In the iSQL*Plus environment the syntax to execute a subprogram is:


EXECUTE <subprogram _name>

In Oracle Developer or Discoverer simply type the name of the subprogram


that is to be invoked.

When invoking from another subprogram, include the name of the


subprogram in the executable section of the invoking subprogram at the point
where you want the subprogram to execute.

Subprograms include both procedures and functions. Though the way in which they
are created is the same, they are slightly different in their functionality. You will
learn about these two individually in separate sections.

151
SQL Star International Ltd.

Procedures
Procedures are one of the subprograms used to perform some action. They may or
may not accept values from the users. They generally do not return a value.
A procedure has a header, a declarative section, an executable and an exception
handling section.
One of the main features promoted by procedures is reusability. Once a procedure is
validated, it can be used in any number of applications. In case the requirement
changes, you need to update only the procedure.

System Privileges for Creating Procedures


Database users according to their role and requirement are given permissions in the
database. Earlier in the course you learnt about the various privileges and
permissions that users require to access and use the objects in the database.
Similarly there are restrictions on creating procedures also. All users are not allowed
to see database structure and create procedures. You need to have the CREATE
PROCEDURE privilege.
The CREATE PROCEDURE privilege permits you to create a procedure and store it in
the server.
The DBA grants this right to a particular user with the following syntax:
GRANT CREATE PROCEDURE TO <user_name>;
The right to execute a procedure is given by the owner of the procedure or the DBA.
The EXECUTE privilege is granted to allow a user to execute a particular procedure.
The syntax is:
GRANT EXECUTE ON <procedure_name> TO <user_name>;

Creating Procedures
Once you have the permission to create a procedure use the following syntax to
create one:
CREATE [OR REPLACE] PROCEDURE <procedure_name>

152
SQL Star International Ltd.

(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .)


IS | AS
PL/SQL block;
Where,
CREATE [OR REPLACE] PROCEDURE are the keywords that either create a procedure
or replace an existing procedure.
<procedure_name> is the name you want to give to the procedure.
[(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .)] are the
parameters that can be passed to the procedure. (Will be covered in the next
section).
PL/SQL block is the set of SQL and PL/SQL statements that are to be executed to
perform the database activity.

The main steps involved in developing a stored procedure are:


1. Writing the code: Enter the code in a text editor or word processor and save it
as a SQL script file
2. Compiling the code: In iSQL*Plus, load and run the script file. Running the
code causes the stored code to be stored in the data dictionary even if the
procedure contains compilation errors. The source code is compiled into P
code, and then the procedure is created.
You cannot successfully invoke a procedure that contains compilation or
runtime errors. In iSQL*Plus, use SHOW ERRORS to see any compilation
errors. Fix the errors using the editor and recompile the code. To see the
errors in the procedure compiled previously you can also use:
SHOW ERRORS PROCEDURE Procedure_name
3. Executing the procedure to perform the desired action: After the successful
creation of a procedure, it can be executed any number of times using the
EXECUTE command from iSQL*Plus
Passing Parameters
To accept user values in procedures or any named PL/SQL block, we use parameters.
Parameters are variables of a certain data type, declared ,defined and used in the
code.
Parameters are of two types: formal and actual parameters
Formal parameters refer to those variables, which are declared in the parameter list
of a subprogram specification. One or more formal parameters can be declared, with
each parameter separated by a comma.
For example,
CREATE PROCEDURE procMember (vMemID CHAR, vAge NUMBER)
.......

153
SQL Star International Ltd.

END;
In the procedure procMember, the variables vMemID and vAge are formal
parameters.
Actual parameters refer to variables or expressions, which are referenced in the
parameter list of a subprogram call. For example, in the call procMember (vID, 26)
to the procedure procMember, the variable vID and 26 are actual parameters.

Points to remember:

During a subprogram call, actual parameters are evaluated and their results
are assigned to formal parameters.

Actual parameters can also have expressions such as procMember (vID,


nAge+2).

Use different names for formal as well as actual parameters. It is only a good
coding practice.

Ensure that formal and actual parameters are of compatible datatypes.

A parameter has a mode. It specifies what the parameter is used for.


Parameters can be passed in three modes:

IN: accepts a value from the calling environment and passes it to the
procedure. It can be assigned a default value. This parameter acts as a
constant. The values in it can be a constant or an expression. It is the default
mode.

OUT: passes a value from the procedure to the calling environment. It cannot
be assigned a Default value.

IN OUT: passes a value from the calling environment to the procedure and
returns a different value from the procedure into the environment using the
same parameter.

The datatype parameter is declared without a size specification. It can be specified:


As an explicit datatype
Using the %TYPE definition
Using the %ROWTYPE definition
The New Jersey Central Library requires some of its automated processes to be
stored in the server as they had a bad experience of all the files getting corrupted
and deleted a couple of times. One of their frequent requirements is the generation
of member detail reports. Hence, the following procedure can help them in getting
this data each time they go for generating the report.
CREATE OR REPLACE PROCEDURE prMemberDetails
(vMemberID IN Member.cMemberID%TYPE,
vFirstName OUT Member.cFirstName%TYPE,

154
SQL Star International Ltd.

vLastName OUT Member.cLastName%TYPE,


vAddress OUT Member.vAddress%TYPE)
IS
BEGIN
SELECT cFirstName, cLastName, vAddress
INTO vFirstName, vLastName, vAddress
FROM Member
WHERE cMemberID = vMemberID;
DBMS_OUTPUT.PUT_LINE(vFirstName|| ||vLastName||
||vAddress);
END;
/
In the example, the IN and the OUT parameters are used.
You may want to know how to use an IN OUT parameter. For this the simplest
example could be that of accepting some data from the user and displaying it in a
different format after modifying the same value.
When the clerk enters a phone number, he wants a more readable format from
which it is clear as to which are the country code, the area code and the number.
Let us create the following procedure to help him understand the phone number
better.
CREATE OR REPLACE PROCEDURE prFormatPhone
(vPhone IN OUT VARCHAR2)
IS
BEGIN
vPhone := (||SUBSTR(vPhone,1,3)||
)||SUBSTR(vPhone,4,3)||
-||SUBSTR(vPhone,7);
DBMS_OUTPUT.PUT_LINE(vPhone);
END;
/

155
SQL Star International Ltd.

Methods of Passing Parameters

There are three ways in which you can pass parameters to procedures. They are:

Positional: Here, the user has to specify the values according to the position
of the parameter in the parameter list

Named Association: Here, it accepts values in an order irrespective of the


parameter order, but you need to link each value with its parameter name
using the symbol =>

Combination: Here, the first values are listed based on the position and the
rest of the values are listed using the special syntax of the named method

DEFAULT option in FORMAL parameters.


CREATE OR REPLACE PROCEDURE prMemberDetails
(vMemberID IN Member.cMemberID%TYPE,
vFirstName IN Member.cFirstName%TYPE,
vAddress IN Member.vAddress%TYPE DEFAULT Unknown)
IS
BEGIN
INSERT INTO member(cMemberID,cFirstName,vAddress)
VALUES (vMemberID ,vFirstName, vAddress);
END;
/
There is a DEFAULT option mentioned in the above program.
How does it help?
Let me explain it. In the example, while invoking the procedure if address details are
not supplied by you then the procedure will insert a string Unknown inside the
vaddress column.
Invoking Procedures
You can invoke a procedure directly or call it from an anonymous block.
For example, to view the values of the OUT parameter, invoke prMemberDetails
procedure as follows:

1. Create host variables in iSQL*Plus

156
SQL Star International Ltd.

VARIABLE FName

CHAR(20)

VARIABLE LName

CHAR(20)

VARIABLE Address

VARCHAR2(50)

2. Invoke the procedure and supply the host variables as the OUT parameters.
EXECUTE prMemberDetails (CBW109702, :FName, :LName, :Address);

3. View the values passed to the calling environment


PRINT FName
PRINT Lname
PRINT Address

On performing these steps, the following result is displayed:

To view the values of the IN OUT parameter, invoke the prFormatPhone procedure
as follows:

1. Create a host variable


VARIABLE phone_no VARCHAR2(15)

2. Populate the host variable using an anonymous block and view the value entered
BEGIN

157
SQL Star International Ltd.

:phone_no := 0403730853;
END;
/
PRINT phone_no
3. Invoke the procedure and provide the host variable as the IN OUT parameter
EXECUTE prFormatPhone(:phone_no);

4. View the value passed back to the calling environment


PRINT phone_no
On performing these steps, the following result is displayed:

The procedure prFormatPhone can also be invoked from an anonymous block by


specifying the name of the procedure with its arguments in the executable section of
the block as shown below.
SET SERVEROUT ON
DECLARE
p VARCHAR2(15);
BEGIN
p:=1234567890';
prFormatPhone(p);
DBMS_OUTPUT.PUT_LINE(p);
END;

158
SQL Star International Ltd.

/
(123)456-7890
(123)456-7890
PL/SQL procedure successfully completed.

Calling a Procedure from a Named Block


In case you want to invoke procedures from another stored procedure, the method is
the same. You can directly include the name of the procedure in the declarative
section.
For example, in case there is a need to update the stock of a particular book, you
can create a procedure as follows:
CREATE PROCEDURE prUpdateBookQty
(vBookID IN Book.cBookID%TYPE,
vNum IN NUMBER)
IS
BEGIN
UPDATE Book
SET nNoOfCopies = nNoOfCopies + vNum
WHERE cBookID = vBookID;
COMMIT;
END;
/
Assume that the library needs to update all the books available with them. For this
you could create a procedure to update the entire book table. This procedure could
invoke the prUpdateBookQty procedure as shown below.
CREATE PROCEDURE prUpdateBranchStock
(vBranchID IN Book.cBranchID%TYPE)
IS
CURSOR curBranchBooks IS
SELECT * FROM Book
WHERE cBranchID = vBranchID;
BEGIN

159
SQL Star International Ltd.

FOR RecBranchBook IN curBranchBooks


LOOP
prUpdateBookQty(RecBranchBook.cBookID,1);
END LOOP;
END;
/

Effects of Handled and Unhandled Exceptions


When developing procedures that will be called from other procedures, you need to
be aware of the effects that handled and unhandled exceptions have on the
transactions and the calling procedures.
Handled Exceptions Affecting the Calling Procedure
When a called procedure raises an exception, the control goes to the exception
section of that block. Once the exception is handled, the block terminates and the
control returns to the calling procedure. The DML statements issued before the
exception is raised, remain as part of the transaction.
For example, look at the following code:
CREATE

OR

REPLACE

PROCEDURE

prInsBrLocation

(pLocID

NUMBER,

pLocName VARCHAR2)
IS
LocName VARCHAR2(30);
BEGIN
DBMS_OUTPUT.PUT_LINE

(Main

Procedure

Calling

Procedure);
INSERT INTO BrLocation (nBrLocID, vLocName)
VALUES (pLocID, pLocName);
SELECT vLocName
INTO LocName
FROM BrLocation
WHERE nBrLocID = pLocID;
DBMS_OUTPUT.PUT_LINE

(LocName||

inserted

into

BrLocation

table);

160
SQL Star International Ltd.

prInsBranch(pLocID);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(No such branch/ location for the
New Jersey Central Library
END;
/
CREATE OR REPLACE PROCEDURE prInsBranch
IS
vBrID NUMBER(4);
BEGIN
DBMS_OUTPUT.PUT_LINE(Called Procedure);
INSERT INTO Branch
VALUES(06CALNJ, Elizabeth

Library Branch, 26,

Elizabeth Rd, pLocID, 986754311, 30-JAN-2002);


SELECT cBranchID
INTO vBrID
FROM Book
WHERE cBookID = 11111122211;
END;
/

From the code, we can observe the following:

Procedure prInsBrLocation
BrLocation table.

Procedure prInsBranch inserts a new branch in the new location inserted


through prInsBrLocation.

Procedure prInsBrLocation invokes prInsBranch procedure.

The SELECT statement in procedure prInsBranch selects a branch ID for a


book that does not exist in the Book table. This raises an exception.

This exception is not handled in procedure prInsBranch.

inserts

new

branch

location

into

the

161
SQL Star International Ltd.

The control returns to the calling procedure prInsBrLocation, where the


exception is handled.

Since the exception is handled, the DML performed in prInsBranch is not


rolled back, and it continues to be part of the transaction of prInsBrLocation
procedure.

For the purpose of the above example, we have assumed that:


The library database maintains a table BrLocation, which has two columns
nBrLocID and vLocName
The Branch table does not have a cBranchLocation column, but instead has a
nBrLocID column
Unhandled Exceptions Affecting the Calling Procedure
In the previous example, when the called procedure prInsBranch raised an
exception, the control went to the exception section of that block. But since the
exception was not handled, the block terminated, and the control was passed to the
exception section of the calling procedure prInsBrLocation. The calling procedure
prInsBrLocation handled the exception, and hence all the DMLs in the calling as
well as the called procedure remained as part of the transaction.
However, if the calling procedure prInsBrLocation had failed to handle the
exception, then the calling procedure prInsBrLocation would have terminated. In
such a case,

The exception would propagate to the calling environment

The DML statements issued both in the calling as well as the called
procedures would be rolled back.

Removing Procedures
If you do not require any or all of your procedures, you can delete them just as it is
done for any other database object. You can drop the server-side procedures from
the iSQL*Plus environment by using the syntax:
DROP PROCEDURE <procedure_name>;
To remove the procedure prMemberDetails type the following command:
DROP PROCEDURE prMemberDetails;

Viewing Procedures in Data Dictionary


The source code for PL/SQL subprograms both successful and unsuccessful, is stored
in the data dictionary tables. To view the PL/SQL source code stored in the data
dictionary, execute a SELECT statement on the following tables:
USER_SOURCE table to display PL/SQL code that you own
ALL_SOURCE table to display PL/SQL code to which you have been granted the
EXECUTE right by the owner of that subprogram code.

162
SQL Star International Ltd.

SELECT TEXT
FROM USER_SOURCE
WHERE NAME=PRINSBRANCH;
To view the names of procedures presently existing in your schema, issue the following SELECT
statement.
SELECT OBJECT_NAME
FROM USER_OBJECTS
WHERE OBJECT_TYPE=PROCEDURE;

Functions
Functions are also stored subprograms and are usually used to calculate values. They
are created and invoked the same way as procedures but with a slight difference.
A function must compulsorily return a value to the calling environment.

163
SQL Star International Ltd.

Creating Functions
The syntax for creating functions is the same as that for procedures, except that the
keyword FUNCTIONS and RETURN are to be used. The syntax is:
CREATE [OR REPLACE] FUNCTION <function_name>
(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .)
RETURN datatype
IS | AS
PL/SQL block;
Where,
CREATE [OR REPLACE] FUNCTION are the keywords that enable you to create a
function or replace an existing one.
<function_name> is the name of the function you want to create.
(argument1 [mode1] datatype1, argument2 [mode2] datatype2,. . .) are the
parameters that the function accepts from the calling environment.
RETURN datatype is the clause that is mandatory, as a function must return a value.
Do not specify a size for the datatype.
PL/SQL block is the set of SQL and PL/SQL statements to execute the task to be
performed. You must include a RETURN datatype in the executable part of the block.
You can use multiple RETURN statements within an IF statement. In this case,
however, only one RETURN statement will be executed.

Let us look at a scenerio and create a functions based on it.


Due to a crisis on a certain day, the clerk collected the books returned by the
members. He does not have access to the database tables but needs to find out the
fines to be paid by the members who returned the books. The user DBA creates a
function to calculate the fine of a particular member.
CREATE OR REPLACE FUNCTION fnFineCal
(vMemberID Member.cMemberID%TYPE)
RETURN NUMBER
IS
vFine NUMBER;
vReturnDt DATE;

164
SQL Star International Ltd.

vActReturnDt DATE;
BEGIN
SELECT dReturnDt,dActualReturnDt
INTO vReturnDt,vActReturnDt
FROM Transaction
WHERE cMemberID=vMemberID;
IF vReturnDt >= vActReturnDt THEN
vFine:=0;
ELSE
vFine:=(vActReturnDt - vReturnDt)* 1.50;
END IF;
DBMS_OUTPUT.PUT_LINE (The member has to pay a fine of:
||vFine);
RETURN(vFine);
END;
/
Invoking Functions
Functions are invoked from the iSQL*Plus environment using the EXECUTE
command.
You need to declare a host variable to store the value returned by the function. The
value can then be printed by passing the host variable name with the PRINT
command after executing the function. Execute it with the following syntax:
EXECUTE :host_variable_name

:=

function_name(value);

For example:
VARIABLE P NUMBER
EXECUTE :P := fnFineCal (BJH029405);

You can also invoke functions from SQL statements in case any ad hoc queries are to
be made. For example, if you want to see the name of a particular member and
phone number and the fine the member has to pay you can execute the following
statement:
SELECT cFirstName, cLastName, cPhone, fnFineCal(BJH029405) Fine
FROM Member

165
SQL Star International Ltd.

WHERE cMemberID=BJH029405';

The result of the above SELECT statement will return the following result:
CFIRSTNAME

CLASTNAME

CPHONE

--------

------

-------

Jessica

Hatcher

9642211309

FINE
------0

Using a function from an SQL statement has the following advantages:

Queries can be made more efficient by performing functions in them than in


applications

You can perform calculations that are very complex and not available in SQL

You can enhance data independence by not retrieving data into an application
and processing complex data in the server

You can invoke functions as:

An item in the SELECT list

A part of a condition in the WHERE and HAVING clauses

As part of the CONNECT BY, START WITH, ORDER BY and GROUP BY clauses

A value in the VALUES clause of the INSERT statement

A part of the expression in the SET clause of the UPDATE statement

Restrictions on Calling Functions

Though there are several advantages of using functions in queries, they have some
restrictions. They are:

The function necessarily has to be a stored function. Stored procedures


cannot be called in this way.

The function must be a row function.

Accept only IN parameters with valid SQL datatype, not PLSQL specific types.

Return Valid SQL datatypes, not PLSQL specific types.

You cannot use these functions in the CHECK constraint of a CREATE or


ALTER table statement.

You need to have the EXECUTE privilege for the function so that you can
invoke it.

The function should not modify any table of the database with DML
statements. In other words, the function should not contain any DML
statements.

166
SQL Star International Ltd.

Removing Functions
Just as you can delete procedures, you can also delete functions. From the iSQL*Plus
environment you can delete functions by using the DROP command. In case you
want to delete the fnFineCal function that you created, execute the following
command.
DROP FUNCTION fnFineCal;

Viewing Function in Data Dictionary


To view the PL/SQL function code stored in the data dictionary, execute a SELECT
statement on the following tables where the TYPE column value is FUNCTION:
The USER_SOURCE table to display the PL/SQL code that you own
The ALL_SOURCE table to display the PL/SQL code to which you have been
granted
EXECUTE right by the owner of that subprogram code
SELECT TEXT
FROM USER_SOURCE
WHERE NAME=FNFINECAL
ORDER BY LINE;
To view the names of all function present in your schema
SELECT OBJECT_NAME
FROM USER_OBJECTS
WHERE OBJECT_TYPE=FUNCTION;
Since we have an in dept knowledge of both procedure and function now, let us
differentiate it.

167
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:

 Subprograms are named and compiled PL/SQL blocks stored within the
database. They are compiled once and invoked many times.

 Subprograms contribute to modularity, memory management and data security


features of a PL/SQL program.

 Procedures and functions are collective known as Subprograms.


 Data values are passed into and out of the block through parameter modes IN,
OUT and INOUT in both subprograms.

 Procedures are used to perform an action and may return a value.


 Functions are used to do calculations, which can be used in a SELECT
expression and must return a value through RETURN statement.

 Exceptions in Procedures if unhandled, rollback the transaction and remain


intact if handled.

168
SQL Star International Ltd.

 Source code of the procedure and the function can be viewed in USER_SOURCE
data dictionary.

169
SQL Star International Ltd.

Lab Exercises
Note: Save all programs as .sql files.
1.
Create a procedure called prInsJobs to insert a new row into the Jobs table.
The procedure should accept two parameters, one for job ID and the other for job
title. Compile the procedure and invoke it in iSQL*Plus with IT_ADMIN as the job ID
and System Administrator as the job title. View the row inserted into the Jobs
table.
2.
Create a procedure called prRaiseSal, which increases the salary by 10% for
the employee ID accepted as the input parameter. Invoke the procedure in
iSQL*Plus with 110 as the employee ID. Prior to invoking the procedure, view the
salary earned by employee 110. After the successful execution of the procedure,
view the updated salary of employee 110.
3.
Create a procedure called prViewEmp that returns the job ID, department ID
and salary for a specified employee ID. Use host variables to view the OUT
parameters of the procedure in iSQL*Plus.
4.
Create a procedure called prUpdJobs to update the job title. The procedure
should accept job ID and job title. The procedure should also have necessary
exception handling in case no update occurs. Invoke the procedure and update the
job title of IT_ADMIN to SysDate Administrator. To check the exception handling,
try and update a job that does not exist.
5.
Create a procedure called prEmpRaiseSal that invokes the prRaiseSal
procedure (created in Lab question 02). The procedure prEmpRaiseSal must
process all the records in the Employees table, and then pass each employee ID to
the procedure prRaiseSal.
6.
Create a function called fnDept that returns the department name
corresponding to a department number passed as the input parameter. The function
should return the department name to a host variable defined in iSQL*Plus.
7.
Create a function called fnAnnualInc that returns the annual salary earned
by accepting monthly salary and commission. The function should return an annual
salary even if both the values passed are NULL. Annual salary is calculated as
follows:
(Salary * 12) + (Salary * Commission * 12)
Use the function in a SELECT statement against the Employees table.

170
SQL Star International Ltd.

Chapter 8

Managing Subprograms
System and Object Privilege Requirements
Definers Rights and Invokers Rights
Detecting Compilation errors
Displaying Parameters Using DESCRIBE Command
Debugging Program Units

171
SQL Star International Ltd.

Objectives

At the end of this chapter, you will be able to:

 Identify the system and object privilege requirements


 Identify views in the data dictionary to manage stored objects
 Debug subprograms using the DBMS_OUTPUT package

172
SQL Star International Ltd.

System and Object Privilege Requirements


In the previous chapter, we have looked into the creation of functions and
procedures that are collectively known as Subprograms. As it is equally significant to
effectively maintain these subprograms, we will now be learning the various ways to
successfully manage them.
System privileges are those privileges that use the words CREATE or CREATE ANY,
such as GRANT CREATE ANY TABLE TO User1. There are more than 100 system
privileges. The user SYSTEM or SYS grants them.
Object privileges on the other hand are the privileges assigned to a specific object
within a schema. They always include the name of the object, for example, user
Paul can assign privileges to user Robert to alter his Member table as follows:
GRANT ALTER ON Member TO ROBERT;

In order to create PL/SQL subprograms, you must have been assigned the system
privilege CREATE PROCEDURE. Once this privilege has been assigned, you can alter,
drop or execute the subprogram without any further privileges being assigned. If the
keyword ANY has been used, then you can create, alter, drop and execute not only
your subprograms, but also those belonging to other schemas. The keyword ANY is
optional only for the CREATE PROCEDURE privilege.
To invoke a subprogram if you are not an owner and do not have the EXECUTE ANY
system privilege, then you must have the EXECUTE object privilege assigned to you.
By default
PL/SQL subprograms execute under the security domain of the owner.
If subprograms refer to objects not in the same schema, then access to these
objects must be assigned explicitly, and not through a role.

173
SQL Star International Ltd.

Definers Rights and Invokers Rights


Subprograms by default execute under the security domain of the owner. This is
known as the definers right. To understand the definers right, look at the
assumptions made:

The Member table is located within the Library schema.

The table is owned by user DBA.

There is a developer named Paul.

Paul creates a procedure procMember, which queries records from the


Member table.

There is an end user named Robert.

The requirement is that Robert should to be able to access the Member table only
through the procMember procedure that Paul created. This requires the granting of both
direct and indirect access.
Direct Access
A user can be given access permissions directly on an object. For instance, from the
Library schema object privileges on the Member table can be provided to Paul.
Based on the object privilege, Paul can create the procMember procedure, which
queries the Member table.
Indirect Access
A user can access an object by using another object on which he has access
permissions. For instance, Paul grants the EXECUTE object privilege on the
procMember procedure to Robert. Robert can now execute the procedure under
the security domain of the owner [this is the definers right]. Robert can retrieve
information from the Member table using the procMember procedure because Paul
has direct privileges to Member table and has created the procedure.

174
SQL Star International Ltd.

The following diagram depicts the direct as well as indirect access:

Executing a subprogram from the security of the executing user, and not the owner,
is referred to as Invokers rights. To implement invokers right, use AUTHID
CURRENT_USER. This ensures that the subprogram executes with the privileges of
its current user. The following diagram depicts the procedure procMember, being
executed with the privileges of the user Robert:

175
SQL Star International Ltd.

Access Methods Used to Manage Stored PL/SQL


Objects
Users can use different access methods to manage different kinds of stored information.
The different access methods and the kind of stored information they provide are listed
below.


Data Dictionary views:

USER_OBJECTS: contains general information of all the database


objects.

USER_SOURCE: contains the source code, that is the text of the


procedure.

USER_ERRORS: contains compilation errors, that is PL/SQL syntax


errors.

iSQL*Plus commands:

DESCRIBE: displays information pertaining to parameters, that is


whether the mode is IN/ OUT/ IN OUT and the parameter datatype.

SHOW ERRORS: displays compilation errors, that is PL/SQL syntax


errors.

Oracle supplied
information.

package,

DBMS_OUTPUT,

provides

run-time

debug

The following diagram shows the access methods:

176
SQL Star International Ltd.

USER_OBJECTS View

A user or developer, like you, would typically want general information about all objects
created such as the name of the object, its owner, when was it created or what type of
object it is.
The USER_OBJECTS is a view that stores such information.
On querying the view, the following column list is displayed (we are providing you with
the abridged column list):

Issue the following SELECT statement to view all the procedures you have created:
SELECT OBJECT_NAME, OBJECT_TYPE
FROM USER_OBJECTS
WHERE OBJECT_TYPE=PROCEDURE
ORDER BY OBJECT_NAME;
/

177
SQL Star International Ltd.

OBJECT_NAME

OBJECT_TYPE

---------------

----------

PRACCEPTMSSG

PROCEDURE

PRFORMATPHONE

PROCEDURE

PRMEMBERDETAILS

PROCEDURE

PRTRANSMITMSSG

PROCEDURE

PRUPDATEBOOKQTY

PROCEDURE

PRUPDATEBRANCHSTOCK

PROCEDURE

If you want to find information about objects created by the DBA or any other user, you
query the DBA_OBJECTS or ALL_OBJECTS. Both the views include the OWNER
column in addition to the ones mentioned in the USER_OBJECTS view.
USER_SOURCE View

To view the code of procedures or functions, you can query the USER_SOURCE data
dictionary view. The columns that this data dictionary contains are:

NAME that stores the name of the object.

TYPE stores the type of object, that is, whether it is a function, procedure,
package or package body.

LINE stores the line numbers of the source code.

TEXT stores the actual text of the code.

For example, to view the text of prUpdateBookQty procedure, use USER_SOURCE


data dictionary view as follows:
SELECT TEXT
FROM USER_SOURCE
WHERE NAME=PRUPDATEBOOKQTY;
Displays:
TEXT
-------------------PROCEDURE prUpdateBookQty

178
SQL Star International Ltd.

(vBookID IN Book.cBookID%TYPE, vNum IN NUMBER)


IS
BEGIN
UPDATE Book
SET nNoOfCopies = nNoOfCopies + vNum
WHERE cBookID = vBookID;
-- COMMIT;
END;
/
Detecting Compilation Errors

Compilation errors can be detected using either USER_ERRORS data dictionary view or
iSQL*Plus command SHOW ERRORS.
To be able to see the errors that you may get when compiling your object, query the
USER_ERRORS data dictionary view. You can view the error text message. The
columns that are included in this data dictionary are:

NAME stores the name of the object.

TYPE stores the type of object created. Whether it is package, function,


package body or procedure.

SEQUENCE contains the sequence number for ordering the error.

LINE stores the line number for the source code at which the error occurs.

POSITION holds information about the position in the line at which the error
occurs.

TEXT stores the text of the error message.

You can view the structure of USER_ERRORS using the DESCRIBE command as
follows:
DESC USER_ERRORS
This command displays the following output:
Name

Null?

Type

-----

------

------

NAME

NOT NULL

VARCHAR2(30)

TYPE

VARCHAR2(12)

179
SQL Star International Ltd.

SEQUENCE
LINE

NOT NULL

NUMBER

NOT NULL NUMBER

POSITION NOT NULL NUMBER


TEXT

NOT NULL VARCHAR2(4000)

The SHOW ERRORS command executed along with the name of the procedure displays
the line and column numbers of the errors along with the text of the error message. If you
execute the command without parameters, you get the error data of the last object that
you compiled.
The syntax to use this command is
SHOW ERRORS [FUNCTION | PROCEDURE | PACKAGE | PACKAGE
BODY | TRIGGER |
VIEW] [schema.] name]
For example, executing the following code would result in errors:
CREATE OR REPLACE PROCEDURE prErr
(MemID Member.cMemberID%TYPE)
IS
MemDt DATE;
BEGIN
SELECT dMembershipDt
INTO MemDt
FROM Member;
END;
/
Warning: Procedure created with compilation errors.

The errors could be rectified only when you know what the errors are. Hence, you could
view the compilation errors using SHOW ERRORS as follows:
SHOW ERRORS
Errors for PROCEDURE PRERR:
LINE/COL

ERROR

--------

-------------------------

7/6

PLS-00103: Encountered the symbol IN when expecting

one

of the following:

180
SQL Star International Ltd.

. ( , * @ % & - + / at mod rem <an identifier>


<a double-quoted delimited-identifier> <an exponent (**)>
as from into || bulk

The above error could also be viewed using USER_ERRORS data dictionary view as
follows:
SELECT LINE||-||POSITION LINE, TEXT
FROM USER_ERRORS
WHERE NAME=PRERR;

Displays:
LINE

TEXT

-------

-------

7-6

PLS-00103: Encountered the symbol IN

7-7

when expecting one of the following:


. ( , * @ % & - + / at mod rem <an
identifier>

<a

identifier>

<an

double-quoted
exponent

delimited-

(**)>

as

from

into || bulk

Displaying Parameters Using the DESCRIBE Command


You can view the structure of database objects using the iSQL*Plus command
DESCRIBE.
The structure of a subprogram includes the parameters used. The DESCRIBE
command along with the name of the subprogram displays the list of parameters in
the code, their datatype, the mode of parameter and the parameter has a default
value or not.
If you want to see the structure of prIncCopies procedure issue the following
DESCRIBE command:

DESCRIBE prIncCopies
PROCEDURE princcopies
Argument Name
-------------

Type
------

VBOOKID

In/Out Default?
-------------

CHAR(13)

IN
181

SQL Star International Ltd.

VCOPIES

NUMBER(2)

IN

Debugging Program Units


Subprograms are the main database objects that help you to automate your system.
To make the database system of a business area functional, you need to write and
build lots of codes. It is not possible for a person to write and execute all codes
without a single mistake. Imagine a situation where you wrote thousands of lines of
code and when executed, it does not perform the task you want, and better still, you
dont know what is wrong and where.
The process of trapping error in a code is known as Debugging. Oracle offers you a
built-in package DBMS_OUTPUT to help debug your subprograms.
DECLARE
Age Member.nAge%TYPE: = &age;
BEGIN
IF Age<5 THEN
DBMS_OUTPUT.PUT_LINE

(Membership

denied

Invalid

age);
ELSE
DBMS_OUTPUT.PUT_LINE (Membership denied Valid age);
END IF;
END;
/
Above Example takes an input Age from the user. To check where the control is
passed based on condition, DBMS_OUTPUT package is used.

More on this will be dealt in detail in Oracle supplied packages.

182
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
A system privilege is given to a user by the DBA to create any object.
 An object privilege is the privilege given by a user or a owner to perform an
operation on a particular object of a schema.
 To create a subprogram, one must have a CREATE PROCEDURE system
privilege.
 To modify (i.e., ALTER, DROP or EXECUTE) a subprogram that belongs to other
schemas, ANY keyword is used with the privilege.
E.g: GRANT ALTER ANY PROCEDURE TO user1;
By default the procedures are invoked by owners right. This is known as Definers
Right. To invoke the procedure by invokers right, specify AUTHID CURRENT_USER
in the subprogram.
 Data Dictionary such as USER_OBJECT, USER_SOURCE, USER_ERRORS are used
to give the information regarding created database object, code of the
subprograms, and any uncompiled errors respectively.
 SQL*PLUS command, SHOW ERROR is used to display uncompiled errors
immediately and DESCRIBE procedure_name shows the structure of the
procedure.


Debugging of the Code can be done using DBMS_OUTPUT.PUT_LINE


packaged procedure.

183
SQL Star International Ltd.

Lab Exercises
1.

Suppose you have lost the code for the prInsJobs procedure and the fnDept

function that you created in this chapter.


Create the iSQL*Plus spool file to query the appropriate data dictionary view to
regenerate the code.
(Hint:
SET

options ON|OFF

SELECT

statement(s) to extract the code

SET

reset options ON|OFF)

2.

USER_ERRORS data dictionary view contains errors related to:


a) PROCEDURES
b) TRIGGERS
c) FUNCTIONS
d) a and b
e) all of the above

3.

Who is the current user during subprogram execution?

4.

How external references are resolved in Invokers Rights Subprograms.

184
SQL Star International Ltd.

Chapter 9

Creating Packages
What are Packages?
Package Components
Package Constructs
Invoking Package Constructs
Referencing Variables
Persistent State of Variables and Cursors
Removing Packages
Viewing Packages in Data dictionary
Guidelines for Creating Packages

185
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Identify package components
 Use packages

186
SQL Star International Ltd.

Introducing Packages
As a child you must have played with games like Building Blocks or Lego. These
games taught you how to be innovative and creative and build many structures with
the same set of blocks and pieces. The creator of these blocks built pieces that could
be used in many ways. Imagine if programming were made as easy. Blocks or code
would be created and kept and all you had to do was put the codes together. Well it
is not as easy as building blocks but certainly close.

What are Packages?


You can define a package as a PL/SQL construct, which is a logical combination of
several program units.
One of the essential features of a block of code or module is its reusability.
Developers write code so that they can be utilized in future applications. This causes
thousands of
PL/SQL codes to be written and stored in the database. You as a developer usually
work in a team and it is not easy to keep track of every module that is built in the
database. Looking for specific modules is a tedious task. Hence, procedures and
functions are grouped together and made into packages. Grouping is done on a
logical basis or on the basis of functionality, dependency, application or any other.
The benefits of using packages are:

Encapsulation: The complex logic of the programs is hidden behind other


interactive and user friendly programs and interfaces. One module in the
package calls the other modules that perform smaller and specific tasks.

Easy application design: You can create and compile the specifications for a
package without doing the same for its body. The stored programs that
reference the package can be created and compiled later as and when
required till you have finally decided to complete your application.

Added functionality: The public variables and cursors that are packaged can
be used by other applications that are executing in that session and
environment.

Memory Management: The procedures and functions in a package all get


loaded into memory. Hence, when a call is made to another program it is
already in memory. Codes do not have to be recompiled when they are called.

Overloading: In a package you can create different subprograms with the


same name, but each using different number or type of parameters.

187
SQL Star International Ltd.

Package Components
You now know, why you might require packages in your applications. You got to put
together the Lego pieces to make your structure. To create a package you should
know the structure of a package and its components.

A package has two main parts:

Package specification

Package body

Package Specifications
Package specification is like the interface to an application. It declares the variables,
cursors, user-defined datatypes, functions and procedures that are part of the
package. A package specification is more like the list you find inside a Lego box,
telling you the number of Lego pieces available according to color and shape.
Package Body
Package body fully defines the functions and procedures that are declared in the
specification.
One point to remember is that the package functions and procedures are limited to
the scope of the package. However, they can be called from outside the package by
prefixing them with the package name.
Package Constructs
You know what are the contents of a package. But what is their functional scope? For
instance, not all the Lego pieces can be used everywhere in all structures that you
build. Similarly you need to know which subprograms of a package you can use and
where. Care must be taken to ensure that not all packaged subprograms are used by
all other applications. To ensure this you need to segregate the constructs you
create.
The constructs used in a package could be either Private or Public.
Look at the diagram shown below.

188
SQL Star International Ltd.

Public Constructs
A public construct is one that is declared in the package specification and described
in the package body. For instance, in the above diagram the procedure Public(), is
public and can be used by any database user. A user or application from any Oracle
server environment can call it. Public constructs include:

Public or global variables

Public procedures and functions

Public variables are also called global variables as they can be used by all other
applications in the session and thus have a global scope. For instance, vPublic (as
shown in the diagram) is a global variable.
Private Constructs
Private constructs are those that are included in the package body but not declared
in the package specifications. Only modules within the package can call them.

Private constructs include:

Private variables

Local variables

Private procedures and functions

Private and local variables are not visible to external users. For instance, vPrivate
(as shown in the diagram) is a private variable and vLocal is a local variable.
The difference in private and local variables is that, the scope of private variables is
limited to the package, but the scope of a local variable is limited to the procedure it
is declared in. Other modules in the same package cannot access these variables.

189
SQL Star International Ltd.

Private procedures and functions are those that are described and defined in the
package body. They can only be called from other modules in the same package. In
the diagram, Private() is a private procedure.

Using Packages
Finally you get to use the Lego pieces and build your structures. Using packages
include creating the packages, calling or invoking them and referencing the
variables.
Developing a package has the following three basic steps:
1. Create the package in iSQL*Plus environment
2. Write the code in the editor chosen
3. Compile the code

190
SQL Star International Ltd.

Creating Package Specification


The syntax to create a package specification is:
CREATE [OR REPLACE] PACKAGE <package_name> IS | AS
public variables
subprogram specifications
END [<package_name>];
Where,
package_name is the name of the package.
public variables are the names of all variables and their datatypes, cursors,
constants, exceptions and/or user-defined datatypes.
subprogram specifications are the definitions of all the procedures and functions with
the parameters they have, if any.

To help the library improve the functionality of the modules that you have already
created, you can design a package specification for them. The procedure to find out
the details of members and the function to find out the fine that they may have to
pay can be combined together as both are related to the library members.
CREATE OR REPLACE PACKAGE pkMemberInfo
IS
PROCEDURE prMemberDetails
(MemberID IN Member.cMemberID%TYPE,
FirstName OUT Member.cFirstName%TYPE,
LastName OUT Member.cLastName%TYPE,

191
SQL Star International Ltd.

Address OUT Member.vAddress%TYPE);


FUNCTION fnFineCal
(MemberID Member.cMemberID%TYPE)
RETURN NUMBER;
END;
/
On executing the code, you get the following message confirming the creation of the
package specification:
Package created.

Creating Package Body


Once the package specifications are created and compiled, you can concentrate on
building the package body to correspond to the specifications.
The syntax to create a package body is:
CREATE [OR REPLACE] PACKAGE BODY <package_name> IS | AS
private variables
subprograms bodies
END [<package_name>];
Where,
package_name is the name of the package already specified.
private variables is the variable and other items that are privately declared and
defined.
subprograms bodies are the bodies of procedures and functions that are declared
publicly or those that are not declared publicly

Let us create the package body for the package specification compiled earlier to find
out information pertaining to members and the calculation of their fines.
CREATE OR REPLACE PACKAGE BODY pkMemberInfo
IS

192
SQL Star International Ltd.

PROCEDURE prMemberDetails
(MemberID IN Member.cMemberID%TYPE,
FirstName OUT Member.cFirstName%TYPE,
LastName OUT Member.cLastName%TYPE,
Address OUT Member.vAddress%TYPE)
IS
BEGIN
SELECT cFirstName,cLastName,vAddress
INTO FirstName,LastName,Address
FROM Member
WHERE cMemberID=MemberID;
DBMS_OUTPUT.PUT_LINE(MemberID||:||FirstName||
||LastName||||Address);
END prMemberDetails;
FUNCTION fnFineCal
(MemberID Member.cMemberID%TYPE)
RETURN NUMBER
IS
vReturnDt DATE;
vFine NUMBER;
vActReturnDt DATE;
BEGIN
SELECT dReturnDt,dActualReturnDt
INTO vReturnDt,vActReturnDt
FROM Transaction
WHERE cMemberID=MemberID;
IF vReturnDt >= vActReturnDt THEN
vFine:=0;
ELSE

193
SQL Star International Ltd.

vFine:=(vActReturnDt-vReturnDt)*1.50;
END IF;
DBMS_OUTPUT.PUT_LINE (The member has to
pay a fine of Rs. ||vFine);
RETURN(vFine);
END fnFineCal;
END pkMemberInfo;
/
Once the package is created the body needs to be compiled. Packages can be
compiled either on the client-side or the server-side.
On compiling the package body, you get the message confirming the successful
compilation and creation of the package body:
Package body created.

Some issues to remember while compiling a package :

You must ensure that the package specification is compiled before the
package body.

The package body should contain the program codes for all units defined in
the specifications else it will not get compiled.

Private components that are called by some public components necessarily


have to be declared and defined, before the calling public components are
defined.

One good point about the package creation and compilation is that, the components
in the package do not have to be in the same order as they have been defined. In
case you have made changes in the code of any of the programs then you need to
only recompile the body. In case the change is in the number or type of the
parameters, then the specification also needs to be recompiled.
You can also create a package without a body. The only time when a package body is
unnecessary is when a specification declares only user-defined types, constants,
variables and user-defined exceptions.
For example, consider the following package specification:
CREATE OR REPLACE PACKAGE pkForConversions
IS
kilo_to_mile CONSTANT NUMBER := 0.6214;

194
SQL Star International Ltd.

mile_to_kilo CONSTANT

NUMBER := 1.6093;

END packForConversions;
/
Package created.
In the code, two identifiers kilo_to_mile and mile_to_kilo declared as constants
specify rates for converting kilometer to mile and mile to kilometer respectively.
A package body is not required for this package specification because the constructs
of the specification require no implementation details. You can directly execute the
package as follows:
EXECUTE DBMS_OUTPUT.PUT_LINE ( 15 kilometers = || 15*
pkForConversions.kilo_to_mile|| miles);

Invoking Package Constructs

To invoke the package constructs you need to prefix the name of the construct with the
name of the package. The syntax for it is:
<package_name>.<procedure or function_name>
In case you are calling a construct from within the package then you need not prefix
the package name.
To invoke a package construct from the iSQL*Plus environment use the EXECUTE
command followed by the package and procedure name.
When required you can also specify the schema to which the package belongs.
To invoke the function fnFineCal of the pkMemberInfo for calculating fine payable
by the members, you must first declare a variable to hold the value returned by the
function:
SQL>VARIABLE Fine NUMBER
After having declared a variable Fine, you can invoke the fnFineCal function using
the EXECUTE command.
SQL>EXECUTE :Fine :=pkMemberInfo.fnFineCal(BJH029405);
This displays the following result:

The member has to pay a fine of Rs.0


SQL>EXECUTE :Fine :=pkMemberInfo.fnFineCal(DDG019503);
The member has to pay a fine of Rs.3
Similarly you can invoke the prMemberDetails procedure from within the package.
But before you invoke the procedure, you must declare variables to hold the first
names, last names and the addresses of members in the following manner:
SQL>VARIABLE FNAME CHAR(20)
SQL>VARIABLE LNAME CHAR(20)

195
SQL Star International Ltd.

SQL>VARIABLE ADD VARCHAR2(50)


After having declared the variables, you can invoke the procedure using the
EXECUTE command as follows:
EXECUTE
pkMemberInfo.prMemberDetails(DDG019503,:FNAME,:LNAME,:ADD);

On executing the above procedure, you get the following result:


Darla

Green

49, Naughty Kids Apts,Far Away Street

Referencing Variables
Apart from invoking and calling the package procedures and functions, you can also
refer to the variables in a package. Referencing to package variables can be done
from within the package as well as from outside. When referencing from within the
package you need not prefix the package name. If you want the variables to be
referenced from outside the package from another stand-alone procedure, then you
have to ensure that you declare them publicly when creating the package.
For example, create a package with function to validate the age of a new member as
shown below.

CREATE OR REPLACE PACKAGE pkCheckAge


IS
gMemberAge NUMBER(2);
PROCEDURE prCheckAge
(MemberAge Member.nAge%TYPE);
END;
/
CREATE OR REPLACE PACKAGE BODY pkCheckAge
IS
FUNCTION fnValidateAge
(MemAge Member.nAge%TYPE)
RETURN BOOLEAN
IS
MinAge NUMBER;
BEGIN

196
SQL Star International Ltd.

SELECT MIN(nAge)
INTO MinAge
FROM Member;
IF MemAge<MinAge THEN
DBMS_OUTPUT.PUT_LINE (The member is too young);
RETURN(FALSE);
ELSE
DBMS_OUTPUT.PUT_LINE(This is the age of new
member);
RETURN(TRUE);
END IF;
END fnValidateAge;

PROCEDURE prCheckAge
(MemberAge Member.nAge%TYPE)
IS
BEGIN
IF fnValidateAge(MemberAge)THEN
gMemberAge:=MemberAge;
ELSE
RAISE_APPLICATION_ERROR(-20850, Invalid Member
Age);
END IF;
END prCheckAge;
END pkCheckAge;

/
On compiling the above package, you get the following confirmation message:
Package created.
Package body created.

197
SQL Star International Ltd.

This package contains a global variable, a public procedure and a private function.
The value passed as the global variable can be changed by directly referencing the
variable with the package name. It can also be overridden by the procedure within
the package.
To assign a value to the global variable directly, issue the following EXECUTE
command.
EXECUTE pkCheckAge.gMemberAge:= 6;
PL/SQL procedure successfully completed.
This initializes the global variable to 6. In case you want to check for another
members age, execute the procedure as follows:
EXECUTE pkCheckAge.prCheckAge(4);
Executing the above procedure results in the display of the following message:
The member is too young

Persistent State of Variables and Cursors


What do you mean by state of variables and cursors?
It is the value and position of the variables and cursors, that is, whether they are
initialized or called. A user can track the state of all variables and cursors in a
program during a session. The tracking starts when a user references the variable or
cursor and it lasts till he disconnects the session.
Tracking the state of variables include:

The initializing the variables when declared

The changes caused to the variables by the package programs

The value when the variable is released when you disconnect from the session

In the pkCheckAge package, you can track the value of the global variable
gMemberAge and the procedure variable MemberAge at every stage. This is

198
SQL Star International Ltd.

possible by inserting display messages after every assignment and calculation, in the
function and procedure.

Tracking the state of cursors include :

The result set identified when the cursor is opened

The result set after the cursor has fetched records

The result set when closing the cursor

In chapter 5, you created a cursor to retrieve names of books belonging to a certain


category that is specified at runtime. You can embed this cursor in a package to
customize the result set. The cursor displays all the books of the category specified
at one shot. You can control this by displaying one book at a time and the remaining
books at the second time.
The same cursor can be embedded within a package as follows:
CREATE OR REPLACE PACKAGE pkCatgBookDisplay
IS
CatgName Category.cCategoryName%TYPE:= &catgname;
CURSOR curCategoryBooks(Catgname CHAR)
IS
SELECT DISTINCT (cBookName)
FROM Book B,Category C
WHERE B.cCategoryID=C.cCategoryID
AND C.cCategoryName=CatgName;
PROCEDURE prFirstSet;
PROCEDURE prSecondSet;
END;
/
CREATE OR REPLACE PACKAGE BODY pkCatgBookDisplay
IS
BookName Book.cBookName%TYPE;
PROCEDURE prFirstSet
IS
BEGIN
OPEN curCategoryBooks(CatgName);

199
SQL Star International Ltd.

LOOP
FETCH curCategoryBooks INTO BookName;
DBMS_OUTPUT.PUT_LINE(BookName);
EXIT WHEN curCategoryBooks%ROWCOUNT>=1;
END LOOP;
END prFirstSet;
PROCEDURE prSecondSet
IS
BEGIN
LOOP
FETCH curCategoryBooks INTO BookName;
DBMS_OUTPUT.PUT_LINE(BookName);
EXIT WHEN curCategoryBooks%ROWCOUNT>=3;
END LOOP;
CLOSE curCategoryBooks;
END prSecondSet;
END pkCatgBookDisplay;
/

On compiling the above package code, you get the following message:
Enter value for catgname: Fiction
Package created.
Package body created.
Execution of this package causes the cursor to fetch all the rows whose display is
controlled by the two procedures. The first fiction book is displayed by prFirstSet.
The cursor is not closed and hence the control moves onto the second procedure. At
this point the cursor status is at the 2nd row. The second procedure prSecondSet
hence displays the names of the remaining books starting from the 2nd book.
EXECUTE pkCatgBookDisplay.prFirstSet;
Triumph Of Katie Byrne
PL/SQL procedure successfully completed.
EXECUTE pkCatgBookDisplay.prSecondSet;
Wings Of The Storm

200
SQL Star International Ltd.

Woman Of Substance

Three points to remember about the state of package cursors and variables are that:

They persist for transactions in a session. The cursor and variable states will
persist for all transactions in a session until you close the cursor or end the
package.

They change from one session to another for a single user. If a certain user
logs out of one session and starts another session invoking the same
package, the cursor and variable states is set to its initial value. In other
words, the cursor and variable states do not persist across sessions for the
same user.

They change from one user to another. If there are different users in different
sessions accessing the same package, the state of the cursors and variables
are all set to their initial values for both users.

Removing Packages
Package like any other database object can be removed when it is no longer
required. You must be sure that the database users no longer require the procedures
and functions in the package.
You can drop the entire package, that is, specification and body, by issuing the
DROP PACKAGE command, followed by the package name. The syntax is:
DROP PACKAGE <package_name>;
To drop the package pkCatgBookDisplay issue the following command:
DROP PACKAGE pkCatgBookDisplay;
Package dropped.
If you only want to drop the package body then use the following syntax:
DROP PACKAGE BODY <package_name> ;
To drop the pkCatgBookDisplay issue the following command:
DROP PACKAGE BODY pkCatgBookDisplay;

201
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
 Packages are schema objects, which enclose collection of related procedures and
functions, cursors, exception and variables.

 Package are made of two separate components sharing the same name. They
are Package specification and Package body.

 Package Specification is the main component. It has only the declaration of a


collection of procedures, functions, cursors, exceptions and variables which
have global accessibility.

Package Body is based on the specification. It includes private subprograms


declaration and definition, local variables, private variables and definition of
global subprograms.

The constructs within the package are invoked by prefixing package name with
the construct name.

The cursor and variable states will persist for all transactions in a session until
you close the cursor or end the package.

Persistence of a Package variable is within a session but not across a session.

 Package information can be retrieved using USER_SOURCE data dictionary view.

202
SQL Star International Ltd.

Lab Exercises

1.
Create a package specification and body called pkEmp that contains the
following:

Procedure called prInsEmp, which is a public construct. This procedure adds


a new employee to the Employees table. But, a new row is to be added only
if a function called fnValidateDeptID returns TRUE. In case the function
returns FALSE, the procedure should display an appropriate message. The
procedure should have input parameters for the following:


Last name

First name

Email ID

Job ID (give a default value ST_CLERK)

Manager ID (give a default value 122)

Salary (give a default value 1500)

Commission (give a default value 0)

Department ID (give a default value 80)

For employee ID use a sequence sqEmpNo

Function fnValidateDeptID, which is a private construct that checks whether


the department ID specified for the new employee exists in the Department
table. It returns a Boolean value.
Invoke prInsEmp procedure with 12 as the department ID. Check whether
the exception handler handles it.
Next, invoke the procedure with 80 as the department ID.

203
SQL Star International Ltd.

2.
Create a package specification and body called pkCheck that contains the
following two procedures (public):

Procedure prCheckHireDt checks whether a specified hire date is between


sysdate50 years and sysdate+2 months. If the date specified is invalid or
NULL, raise an application error with an appropriate message

Procedure prResetComm resets the prevailing commission. [In the package


specification declare a global variable and initialize it to say 0.10]. The
procedure invokes a private function fnValidateComm, which checks that
the commission value specified cannot be greater than the highest
commission available.
Test prCheckHireDt procedure by passing a date such as 01-MAR-45.

Similarly, check prResetComm procedure. Make the prevailing commission as 0.5.


3.

Create a bodiless package containing exceptions and PL/SQL tables.

204
SQL Star International Ltd.

Chapter 10

Using More Package Concepts


Object Oriented Features of Packages
Automatic One Time Procedure
Package Function in SELECT Expression
PL/SQL Wrapper Utility
Dependencies
Recompiling PL/SQL Blocks

205
SQL Star International Ltd.

Objectives

At the end of this chapter, you will be able to:





Implement the object oriented features with packages


Manage packages

206
SQL Star International Ltd.

Object Oriented Features of Packages


You need to be able to enhance packages so that they can be made more functional
and can exploit all the advanced features of PL/SQL. Implementing the objectoriented features like overloading and forward-declarations can make packages more
efficient.
Overloading
Overloading is a feature that allows you to create two or more procedures with the
same name but different parameters. The server recognizes the called procedure by
its signature, or in other words the number, and type of parameters it uses.
Points to remember while overloading procedures are:

Overload only local or packaged subprograms.

The parameters of the two procedures cannot differ only in name


and mode but should also differ in number and datatype.

The procedures cannot be overloaded if they differ only in


datatypes which are a subset of the same main type. Such as
VARCHAR and STRING are the subtypes of the main type, which is
VARCHAR2.

You cannot overload if only the return parameters in the two


functions are different even if they belong to different datatype
families.

When you execute overloaded programs, the Oracle compiler searches for the
declarations that match the subprograms. The search takes place first in the current
scope and then if required moves on to successive enclosing scopes. If the compiler
finds one or more subprogram signatures in which the subprogram name matches
that of the called program, then it compares the number, data type and order
between the actual and formal parameter.
The user DBA creates two procedures to add rows into the PublisherDetails table
and calls both the procedures prAddPublisher. He places both procedures in a
package. One procedure accepts the values of cPublisherID and cPublisherName.
The other procedure accepts cPublisherID, cPublisherName and
cPublisherAddress.
CREATE OR REPLACE PACKAGE pkLoad

207
SQL Star International Ltd.

IS
PROCEDURE prAddPublisher
(PublisherID IN PublisherDetails.cPublisherID%TYPE,
PublisherName IN PublisherDetails.cPublisherName%TYPE);
PROCEDURE prAddPublisher
(PublisherID IN PublisherDetails.cPublisherID%TYPE,
PublisherName IN PublisherDetails.cPublisherName%TYPE,
Address IN PublisherDetails.vPublisherAddress%TYPE);
END pkload;
/
CREATE OR REPLACE PACKAGE BODY pkLoad
IS
PROCEDURE prAddPublisher
(PublisherID IN PublisherDetails.cPublisherID%TYPE,
PublisherName IN PublisherDetails.cPublisherName%TYPE)
IS
BEGIN
INSERT

INTO

PublisherDetails(cPublisherID,cPublisherName)
VALUES(PublisherID,PublisherName);
END prAddPublisher;
PROCEDURE prAddPublisher
(PublisherID IN PublisherDetails.cPublisherID%TYPE,
PublisherName IN PublisherDetails.cPublisherName%TYPE,
Address IN PublisherDetails.vPublisherAddress%TYPE)
IS
BEGIN
INSERT INTO PublisherDetails(cPublisherID,
cPublisherName,vPublisherAddress)

208
SQL Star International Ltd.

VALUES(PublisherID,PublisherName,Address);
END prAddPublisher;
END pkLoad;
/

You get the following message on compiling the code:


Package created.
Package body created.

To determine which of these procedures will execute, issue the following EXECUTE
statements:
EXECUTE pkLoad.prAddPublisher(RQ0865,Raj Quartet);
PL/SQL procedure successfully completed.
EXECUTE

pkLoad.prAddPublisher(BW0780,Booksware,New

Orleans);
PL/SQL procedure successfully completed.

To verify whether the values have been inserted into the PublisherDetails table, issue a
SELECT statement as shown below.
SELECT *
FROM PublisherDetails
WHERE cPublisherID IN (RQ0865,BW0780');

The query displays the following result set:


CPUBLI

CPUBLISHERNAME

VPUBLISHERADDRESS

-------

---------------

-----------------

BW0780

Booksware

RQ0865

Raj Quartet

New Orleans

Forward Declarations

PL/SQL does not support forward references. That is, an identifier or a subprogram must
be declared before calling it.
Look at the example given below.
209
SQL Star International Ltd.

CREATE OR REPLACE PACKAGE BODY pkCheckAge


IS
PROCEDURE prCheckAge
(MemberAge Member.nAge%TYPE)
IS
BEGIN
IF fnValidateAge(MemberAge)--illegal reference
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
END prCheckAge;

FUNCTION fnValidateAge
(MemAge Member.nAge%TYPE)
. . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . .
END fnValidateAge;
END pkCheckAge;
In the example, you cannot reference the function fnValidateAge because it has not
yet been declared. Reversing the order of the two programs could solve such an
illegal reference.
But such a solution does not always work, especially if the programs are calling one
another, or you want the programs to be defined in some order, such as in
alphabetical order.
To solve this kind of problem you can implement forward declarations. Forward
declarations require the subprograms specification declarations to be ended using a
semicolon. For example, in case you want to define your programs in an alphabetical
order then mention only the name and arguments of the private program before the
public one and define the private program later according to its alphabetic
occurrence.
When compiling this package the server will read the public procedure, encounter the
name of the private procedure, and search for the same in the package body above
the public procedure. When it finds the procedure name, it calls the actual procedure
body that is defined later in the package body.

210
SQL Star International Ltd.

Using forward declarations you can:

Define the programs in a set order, be it logical or alphabetical

Define mutually recursive programs

Group programs into a package

The best example to illustrate forward declaration is the pkCheckAge package created
earlier. The prCheckAge procedure within the package references the private function
fnValidateAge only after the function has been declared.
CREATE OR REPLACE PACKAGE BODY pkCheckAge
IS
FUNCTION fnValidateAge
(MemAge Member.nAge%TYPE); -- Forward declaration
PROCEDURE prCheckAge
(MemberAge Member.nAge%TYPE)
IS
BEGIN
IF fnValidateAge(MemberAge)
. . . . . . . . . . . . . . . .
END prCheckAge;
FUNCTION fnValidateAge
(MemAge Member.nAge%TYPE)
. . . . . . . . . . . . . .
BEGIN
. . . . . . . . . . . . . . .
END fnValidateAge;

211
SQL Star International Ltd.

END pkCheckAge;
/

Automatic One-time Procedure


A package definition is similar to a procedure and function definition, except that it
has no parameters. Similarly, the bodies of packages and procedures are also alike.
Hence, it is possible to include a PL/SQL code directly inside a package. This code will
execute as soon as you submit the package. In other words it is an automatic onetime procedure that executes only once when the package is invoked in a user
session. You can use this type of procedure to initialize variables if their derivation is
very complex. Here, you need not initialize the variables in the package
specifications, as it will be initialized by the automatic procedure.
The following example shows a one-time-only procedure:
CREATE OR REPLACE PACKAGE pkFine
IS
vFine NUMBER;
... -public procedures/functions
declarations
END pkFine;
/
CREATE OR REPLACE PACKAGE BODY pkFine
IS
... - declaration of private variables
... - definition of public/private
procedures/functions
BEGIN
SELECT nFine
INTO vFine

Automatic initialization of

FROM Transaction

vFine variable

WHERE cBookID = ...;


END pkFine;

212
SQL Star International Ltd.

In the example, current value for vFine is set with the value of nfine from the
Transaction table the first time the package pkFine is referenced.

Using Package Function in SELECT expression


We have seen earlier how functions can be a part of SELECT expression. This feature
mainly differentiates functions from the procedures. Now let us see, how the
packaged function is accessed by the user in the SELECT expression.
Isnt it better if we look at an example. Here it is.

CREATE OR REPLACE PACKAGE pkincrfine


IS
FUNCTION fnincrfine
(vfine IN NUMBER) RETURN NUMBER;
END pkincrfine;
/
CREATE OR REPLACE PACKAGE BODY

pkincrfine

IS
FUNCTION fnincrfine (vfine IN NUMBER)
RETURN NUMBER
IS
BEGIN
RETURN vfine*.01;
END fnincrfine;
END pkincrfine;
/
Package created.
Package Body created.
In order to access the above package following statement has to be executed.
SELECT
Transaction;

cmemberid,

nfine,

pkincrfine.fnincrfine(nfine)

FROM

If this package is granted to SCOTT, then he can access it by prefixing USERNAME


with PACKAGENAME.FUNCTIONNAME. In this case it is
SCOTT.pkincrfine.fnincrfine(nfine).
The same restrictions that were applied to standalone functions, are also followed by
functions within the packages.

213
SQL Star International Ltd.

PL/SQL WRAPPER UTILITY


In real time scenario, it becomes necessary to protect our code from misuse. To
tackle this we can encrypt our code which makes it unreadable. Here, wrapper utility
comes to our rescue. We can use the Wrapper to deliver PL/SQL applications without
exposing our source code.
Sounds Interesting? Let us see, what a wrapper is all about.
Wrapper is a standalone utility that converts PL/SQL source code into intermediate
form of portable object code.
WRAP is the command to be entered in the DOS prompt and has the following
syntax.
WRAP INAME=input_file_name [ONAME=output_file_name]
INAME indicates the input file containing the code to be wrapped.
ONAME indicates the file name where the unreadable code is stored. By default the
extension given to these files is .plb. One can execute this file to store the wrapped
version of the source code. It is optional. If omitted, it takes the same name as the
input filename.
STEPS TO USE THE WRAPPER UTITILTY
1.
Prepare the source code for the package and save it with .Sql extension.
2.
Execute this file. The package will be created and stored in the database.
3.
To confirm, you can query user_source.
4.
To convert this code from readable code to unreadable code, run the wrapper
utility in the DOS command prompt.
WRAP INAME=d:\source_code\PRFNINCRFINE.SQL
ONAME=d:\source_code\wrapprfnincrfine.plb
5. Leave no space around the equal signs because spaces delimit individual
arguments
6. Drop unwrapped package body PRFNINCRFINE.
7. Now, execute d:\source_code\wrapprfnincrfine.plb file in SQL*PLUS.
8. If you query this package body again in the data dictionary, you will see the
unreadable format of the code.

Instructions for Wrapping


1. Wrap only the Package body since it contains the business logic thereby hiding its
information from unauthorized users.

214
SQL Star International Ltd.

2. Input File ignores semantic errors. However .plb file containing the wrapped code
reports the error once executed. Alternatively, one can compile a package body and
then wrap to avoid this.
3. Changes made to the original source have to be wrapped again.

Managing Packages
Managing packages involve the privileges needed to access them, tracking their
execution, and ensuring that they do not violate data integrity rules already defined.
To be able to manage packages you can:
 Access the source codes of the procedures using the USER-SOURCE
data dictionary view.
 View their parameters using the DESCRIBE command.

Compile time errors can be tracked and viewed with the USER_ERRORS data
dictionary view and the command SHOW ERRORS.
Some database objects are dependent on other database objects. The objects that
require data or support from other database objects are called dependant objects
and the objects that are referred to are called referenced objects.

Dependencies
When managing packages and other independent subprograms you need to include
all dependency issues. Changing the structure of a referenced object may cause a
dependent to stop functioning.
The Oracle server manages all dependencies, and to do so, it takes the help of the
Status column of the data dictionary view USER_OBJECTS. The status of an object
at any time can either be VALID or INVALID.
The difference in the two is that a VALID object has been compiled and is ready for
use, whereas an INVALID object has to be compiled before it is used.

215
SQL Star International Ltd.

Direct and Indirect Dependencies


A reference to an object can be made directly or indirectly. A procedure or function
can directly and indirectly refer to tables, views, sequences, procedures, functions,
and packaged procedures and functions. In case of indirect referencing, it does so by
using intermediate views, procedures, functions, or packaged procedures and
functions.
USER_DEPENDECIES
You can view direct dependencies by using the data dictionary view
USER_DEPENDENCIES. In case you need to manually recompile any database object
then you can query this data dictionary to find the required dependencies
information.
DESC USER_DEPENDENCIES
NAME

NULL?

TYPE

---------

-----------

-------------

NAME

NOT NULL

VARCHAR2(30)

TYPE

VARCHAR2(12)

REFERENCED_OWNER

VARCHAR2(30)

REFERENCED_NAME

VARCHAR2(64)

REFERENCED_TYPE

VARCHAR2(12)

REFERENCED_LINK_NAME

VARCHAR2(128)

SCHEMAID

NUMBER

DEPENDENCY_TYPE

VARCHAR2(4)

The data that you can query from this view are:

Names of the dependent objects from the NAME column

Type of the dependent object, whether it is a procedure, or a function, from


the TYPE column

The schema of the referenced object from the REFERENCED_OWNER column

Name of the referenced object from the REFERENCED_NAME column

The type of object referenced from the REFERENCED_TYPE column

The database link used


REFERENCED_LINK_NAME

to

reference

the

object

from

the

In the ALL_DEPENDENCIES and DBA_DEPENDENCIES there is an extra column called


OWNER, which displays the name of the owner of the object.

216
SQL Star International Ltd.

UTLDTREE
Another method of viewing dependencies is to query the DEPTREE view, which lists
the dependency information that is stored in an underlying table called
DEPTREE_TEMPTAB, and the IDEPTREE view, which lists dependency information
such that dependent objects are shown below the objects they are dependent on,
and they are indented.
To query the views, you need to first run the Oracle provided script file UTLDTREE
(found in the rdbms/admin subdirectory under the Oracle software home directory).
You need to then execute a procedure called DEPTREE_FILL, which populates the
underlying table for your use.
The syntax to execute the procedure is:
EXECUTE DEPTREE_FILL( <object_type>, <object_owner>,
<object_name>);
The above syntax is used to execute the procedure to populate the table
DEPTREE_TEMPTAB with information of a referenced object. The three parameters
passed are the type of the object being referred, the owner of the object and the
name of the object. For example,
EXECUTE DEPTREE_FILL(TABLE,SCOTT,TRANSACTION);
PL/SQL procedure successfully completed.
Query the DEPTREE view as shown below.
SELECT NESTED_LEVEL, TYPE, NAME
FROM DEPTREE
ORDER BY SEQ#;
Displays:
NESTED_LEVEL

TYPE

-------------- ----

NAME
------

TABLE

TRANSACTION

VIEW

TI

PACKAGE BODY

PKMEMBERINFO

Local and Remote Dependencies


Dependencies can be classified into two kinds, local and remote.
Local Dependencies
Dependencies are said to be local when the database objects are all on the same
node of the same database. In this case when there is a change in the definition of a

217
SQL Star International Ltd.

referred object, the dependent object becomes invalid. The next time the invalid
object is called, it is recompiled by the server automatically and hence, refers to the
modified referenced object.
If you change the name of the referenced object then it will invalidate your
dependent object. Even if you have two different types of objects with the same
name, check to see which object will your dependent object refer to.
Remote Dependencies
Dependencies are said to be remote when the database objects are residing on
different nodes in the database. Dependencies among remote objects are not
handled automatically, but the server handles local-procedure-to-remote-procedure
dependencies. If there is a change in the structure of a referenced object, then all its
dependent objects will be invalid. They are not automatically recompiled when called
the next time.
Points to remember with regards to remote dependencies are:

Check the status of the objects with the USER_OBJECTS view to


ensure that the recompilation of local (implicit) and remote
(explicit) procedures has happened successfully.

Do not rely on the automatic recompilation of local dependent


objects. If the recompilation fails then the status is invalid and a
runtime error is generated. To prevent disruptions, manually
recompile local dependent objects.

Two concepts of remote dependencies are:

TIMESTAMP checking: The time when a PL/SQL program is created


and recompiled is always noted for all PL/SQL programs. If a
program is altered, all its dependent modules become invalid and
need to be recompiled before they can be executed.

SIGNATURE checking: Along with the timestamp the signature of


every PL/SQL unit is recorded. The signature contains the type of
construct, the number of parameters, their base types and their
modes.

Whenever there is a call from a local procedure to a remote procedure a timestamp


check takes place. If the timestamps match then the calls are executed without a
problem. In case there is a timestamp mismatch, the signatures are matched to
verify that the calls are safe and legal. If these are compatible and neither of them is
changed, then execution continues, else an error message is displayed.

There are some parameters to set the remote dependency mode. This can be done
at three levels as listed below.

As an init.ora parameter with the following syntax:

REMOTE_DEPENDENCIES_MODE=value

At the system level with:

218
SQL Star International Ltd.

ALTER SYSTEM SET


REMOTE_DEPENDENCIES_MODE=value

At the session level with:

ALTER SESSION SET


REMOTE_DEPENDENCIES_MODE=value
Where,
value is either TIMESTAMP or SIGNATURE.
Some points to remember are:

The remote dependency mechanism is not the same as the local one. When a
remote recompiled procedure is invoked, you will get an error and the local
program is invalidated. The next time it is invoked, it gets implicitly
recompiled.

If a local procedure that calls a remote procedure is compiled before the


remote procedure, then it is invalidated.

When a procedure is compiled, the time stamps of both, the local procedure
and that of the remote procedure it calls are recorded in the p-code of the
local procedure. When you invoke the local procedure it displays an error
message saying that the timestamp of the remote procedure has changed. To
rectify this, re-invoke the local procedure.

If timestamps do not match, the local procedure is invalidated and a runtime


error is generated. When you invoke the local procedure for a second time,
the server recompiles it.

Procedure Dependencies
Dependency of database objects essentially comes into focus when you need to recompile
the objects due to some alteration that you may have done on the blocks.
This requires you to recompile your blocks. There are some issues that you should know
while programming and developing and later implementing procedures and functions.
Recompiling PL/SQL Blocks
Blocks can be explicitly or implicitly recompiled. When recompiling an object, the server
will first recompile any invalid object on which the former is dependent.

Any object that depends on a recompiled procedure becomes invalid.

Recompiled packages cause objects dependent on it to become invalid. Even


the package body depends on its package specification. Packages are
recompiled using the COMPILE PACKAGE syntax. If you want to recompile
only the body, use the COMPILE BODY syntax.

219
SQL Star International Ltd.

Triggers are enabled or validated by default and when invalidated, it does not
fire when the triggering statement is executed.

Recompiling dependent blocks does not happen if the:

Referenced object is dropped

Datatype of the referenced column is changed

Columns of a referenced view changes and the required columns are not part
of the new view

Parameters of a referenced procedure change

Recompiling dependent blocks is not hampered if:

New columns are added to the referenced table

Datatype of the referenced column has not changed and no new column is
NOT NULL

If the referenced object is a private table that is dropped and a public table
exists. When the private table is dropped, the dependent object becomes
invalid. But if a public table is created with the same name and structure,
then the object refers to the public table and is recompiled.

You can reduce recompilation errors if you:

Declare records with %ROWTYPE

Declare variables with %TYPE

Perform queries using SELECT *

Include the column list when inserting values with INSERT

Group the blocks into packages

Package Dependencies
Package dependency includes the package body being dependent on the package
specifications. When you create and compile a package, the details of both the
specification and the body are found in the USER_OBJECTS view.
Some points to remember about compilation are:

The package body compilation will remain successful as long as the package
specification does not change. In case there is some alteration made to a
package function and the package body is recompiled, the recompilation is
successful. This procedure independence is very useful, as you need not
recompile all the dependent procedures and functions, only the altered
procedure needs to be recompiled.

In case there is any change in the package specification, the recompilation of


the body will certainly fail. The body can only recompile successfully if the
specification is valid.

220
SQL Star International Ltd.

In case a package function is referring to an external package function, then


the latter should be compiled before the former. There is no order for
compiling package specifications.

In case there is a syntax error in the calling procedure body and it is


compiled, the package is invalidated. The referenced package procedure is
still valid. When a call to the latter package is made from the former, the
procedure is rendered invalid. This is a delayed compilation effect.

In case of a syntax error in a package procedure and the package


specification is compiled, then the referenced procedure is also invalid. This is
an immediate compilation effect.

Cyclic dependencies are also not a problem because, when one package is
getting compiled it will check the specification of the referenced package for
the referenced procedure.

Forward referencing is a good way to take care of cyclic dependency in a


package.

221
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:


Packages are said to be overloaded when they have functions and procedures
with same name, but differ in the argument datatype and number.
Forward Declaration in package body helps compiling of those subprograms,
which are invoked first and later defined. By doing so, an ordered subprogram
can be maintained in the package body.
ONE-TIME ONLY procedure helps to dynamically initialize the package variable.

PL/SQL wrapper utility is used to convert the source code into unreadable
format for security reasons.

Oracle server manages dependencies by taking the help of the Status column
of the data dictionary view USER_OBJECTS. The status of an object at any
time can either be VALID or INVALID.

Dependencies may be direct or indirect dependencies. Its related information


can be viewed in USER_DEPENDENCIES, DEPTREE and IDEPTREE.

LOCAL dependencies and REMOTE dependencies are classified based on the


objects present on the same and different servers respectively.

222
SQL Star International Ltd.

Lab Exercises
1.
Create a package called pkOverLoadDisplay. The package contains two
functions having the same name fnDisplay.

The first function accepts a date (in DD-MON-YY format) and displays it in
fmDdspth, Month, YYYY format.

The second function accepts a number in character format and displays it as a


number.

Test both the versions of the function.


2.

Convert the following PL/SQL code into portable object code so that you can

deliver a PL/SQL procedure without exposing its source code.

CREATE PROCEDURE raise_salary (emp_id INTEGER, amount NUMBER)


AS
BEGIN
UPDATE Employees
SET salary = salary + amount
WHERE employee_id = emp_id;
END;
/

223
SQL Star International Ltd.

Chapter 11

Oracle Supplied Packages


DBMS_SQL Package
EXECUTE IMMEDIATE Statement
DBMS_OUTPUT Package
HTP Package
Scheduling Jobs using DBMS_SCHEDULER
UTL_FILE Package
DBMS_METADATA Package
Compile Time Warnings

224
SQL Star International Ltd.

Objective
At the end of this chapter, you will be able to:
 Use Oracle supplied packages

225
SQL Star International Ltd.

Oracle Supplied Packages


So far you have learnt how to create your own procedures and packages. You also used a
few of the Oracle supplied packages and procedures. There are other Oracle supplied
packages that will aid PL/SQL to access SQL features to enhance the functionality of the
blocks.
The list of the packages with their usage is as follows:

DBMS_ALERT displays notifications of database events.

DBMS_JOB schedules the periodic execution of the PL/SQL blocks.

DBMS_APPLICATION_INFO is used by applications to inform the database of


the activities they are currently being performed.

DBMS_DDL recompiles subprograms and analyzes indexes, tables and


clusters.

DBMS_LOCK performs lock management related issues like, requesting for


locks, converting and releasing them.

DBMS_DESCRIBE describes the arguments of a specific stored procedure.

DBMS_OUTPUT displays any messages from triggers and other PL/SQL blocks.

DBMS_SQL access the database using dynamic SQL.

DBMS_SESSION gives you access to session information and SQL ALTER


SESSION statements.

DBMS_TRANSACTION controls and improves the performance of logical, short


and non-distributed transactions by making them discrete.

DBMS_SHARED_POOL maintains objects in shared memory so that they will


not be exhausted with the normal LRU mechanism.

DBMS_UTILITY performs all utility tasks like, analyzing objects of a certain


schema, checking whether the server is running and returns the time.

UTL_FILE gives PL/SQL input and output capabilities.

UTL_MAIL is used to send email. This is the enhanced feature of UTL_SMTP in


Oracle10g.

UTL_COMPRESS is used to compress and decompress Binary Data (RAW,


BLOB, BFILE), which is a new feature in Oracle10g.

Some of these packages are used often and you need to know why and how they are used.
You will learn about the following packages in detail.

DBMS_SQL

DBMS_OUTPUT

226
SQL Star International Ltd.

HTP

UTL_FILE

DBMS_SCHEDULER

DBMS_METADATA

DBMS_WARNING

We will also discuss about EXECUTE IMMEDIATE statement which works similar to
DBMS_SQL.

DBMS_SQL
This package enables you to write PL/SQL blocks using dynamic SQL. Dynamic SQL
comprises statements that are not coded into the block but are built by the block during
runtime. That is, SQL statements can be created dynamically at runtime using variables.
For example, you can perform DML operations on tables accepting the name of the table
at runtime. You can also issue DDL statements using this package. For example you can
drop a table from within a procedure.
To use SQL statements in PL/SQL you need to use cursors. The regular flow of a
program using SQL statements and cursors is as follows:
1. Open the cursor to process the SQL statement. The cursor returns the cursor ID
number.
2. The SQL statements are parsed to check for syntax and privileges. A private SQL
area is allotted for the statement.
3. Each data input at runtime must have a bind variable to supply the values to a
placeholder. Use BIND_VARIABLE for this purpose.
4. Use DEFINE_COLUMN to specify the variables that will store the values from
the SELECT statement.
5. The SQL statements need to be executed.
6. The rows that satisfy the condition will be fetched with the FETCH_ROWS
statement.
7. Verify the values of the columns or the variables fetched by using the
COLUMN_VALUE or the VARIABLE_VALUE statements respectively.
8. Close the cursor with the CLOSE_CURSOR statement to de-allocate the memory
space.

The features of the DBMS_SQL package are:

SQL statements need not be included in PL/SQL blocks. They can be stored as
strings and passed to the program at runtime. This allows the programmer to
write generic modules that can be reused.

227
SQL Star International Ltd.

The package allows parsing, hence DDL statements can be parsed when used
in PL/SQL.

These operations are performed under the current user and hence if there is
an anonymous block calling the subprogram then the privileges of the current
user prevails. If the call is from a stored procedure then the privileges of its
owner are applied.

This package implements bind by values when executing.

The DBMS_SQL procedures are used in the executable section of a block.


You can create a procedure that uses dynamic SQL to delete rows from a specified table
as follows:
CREATE OR REPLACE PROCEDURE prDeleteTableRows
(vTableName IN VARCHAR2, vRowsDeleted OUT NUMBER)
IS
vCursorName

INTEGER;

BEGIN
vCursorName := DBMS_SQL.OPEN_CURSOR;
-- opens a cursor and assigns an ID
DBMS_SQL.PARSE (vCursorName, DELETE
FROM || vtableName, DBMS_SQL.NATIVE);
-- parses the DML/DDL statements
vRowsDeleted := DBMS_SQL.EXECUTE
(vCursorName); -- executes the SQL statements and returns a
message of how many rows were processed
DBMS_SQL.CLOSE_CURSOR (vCursorName); -- closes the
specified cursor

228
SQL Star International Ltd.

END;
/

The code can be tested as follows in iSQL*Plus:


VARIABLE NoOfRowsDeleted NUMBER
EXECUTE prDeleteTableRows (Member, :NoOfRowsDeleted);
PRINT NoOfRowsDeleted

For better performance of native dynamic SQL, use the EXECUTE IMMEDIATE
statement. Its syntax is as follows:
EXECUTE IMMEDIATE dynamicSQL_string
[INTO {define_variable [, define_variable] | record}]
[USING [IN | OUT | IN OUT] bind_argument,
[IN | OUT | IN OUT] bind_argument] ];
[{RETURNING | RETURN} INTO bind_argument [,
bind_argument]...];

Where,
dynamicSQL_string is a string expression representing a dynamic SQL statement (no
terminator) or a PL/SQL block (terminator)
define_variable is a variable to store the selected column value
record is a %ROWTYPE or user-defined record to store a selected row
input bind_argument : is the expression whose value is passed to the dynamic SQL
statement
output bind_argument : is a variable in which values returned by the dynamic SQL
statement is stored.

bind_argument is an expression whose value is passed to the dynamic SQL statement or


the PL/SQL block
At runtime, placeholders in the dynamic SQL are replaced by bind arguments. Hence,
every placeholder must be associated with a bind argument in the USING clause. Bind
arguments can be numeric, character or string literals, but they cannot be Boolean literals.

The procedure prDeleteTableRows can be re-written as follows:


CREATE OR REPLACE PROCEDURE prDeleteTableRows
(vTableName IN VARCHAR2, vRowsDeleted OUT NUMBER)

229
SQL Star International Ltd.

IS
BEGIN
EXECUTE IMMEDIATE DELETE FROM ||vTableName;
vRowsDeleted := SQL%ROWCOUNT;
END;
/

In the code, the EXECUTE IMMEDIATE statement parses and immediately executes the
dynamic SQL statement.

Another illustration for EXECUTE IMMEDIATE statement using DML commands.


CREATE OR REPLACE PROCEDURE prAdd_rows (table_name VARCHAR2, id
NUMBER, name VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE INSERT INTO ||table_name || VALUES
(:1,:2) USING ID, NAME;
END;
/
The above code inserts 2 column values into the table that is passed dynamically at
runtime.
Example of Dynamic SQL with DDL Statement:
CREATE OR REPLACE PROCEDURE prcreate_table(table_name VARCHAR2,
col_spec VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE CREATE TABLE ||table_name || ( ||
col_spec ||);
END;
/
Issue the following statements to invoke the above procedure prcreate_table.
BEGIN
prCreate_table(EMPLOYEES,NAME VARCHAR2(20),DEPTNO
Number(5));
END;
An example to illustrate compilation of PL/SQL objects with Native Dynamic SQL:
CREATE OR REPLACE PROCEDURE prcomple_plsql
(name VARCHAR2,plsql_type VARCHAR2,options VARCHAR2:= NULL)

230
SQL Star International Ltd.

IS
stmt VARCHAR2(100):=ALTER
||COMPILE;

||plsql_type||

||

name

BEGIN
IF options IS NOT NULL THEN
stmt := stmt || ||options;
END IF;
EXECUTE IMMEDIATE stmt;
END;
/

Need for Native Dynamic SQL


The situations in which you need dynamic SQL are as follows:

To execute SQL data definition statements, data control statements, or session


control statements (like ALTER SESSION), because in PL/SQL these statements cannot
be executed statically

To make programs flexible. For instance, you may want to postpone your choice
of schema objects until runtime, or you may want the program to build different search
conditions for the WHERE clause of SQL statement.

DBMS_OUTPUT Package
We have been using this package for displaying the output. Let us have an indept
knowledge about this package now.
The DBMS_OUTPUT package is made up of the following subprograms.
PUT, NEW_LINE, PUT_LINE, GET_LINE AND GET_LINES.

PUT( ) AND PUT_LINE( )


PUT () procedure puts the information in the output buffer and PUT_LINE ()
procedure puts the data as well as displays it from the buffer on your screen. A code
snippet written by the programmer, using the two procedures in the function
fnFineCal is as follows:
The following example illustrates this package further:
CREATE OR REPLACE FUNCTION fnFineCal
(MemberID Member.cMemberID%TYPE)
RETURN NUMBER
IS
vReturnDt DATE;

231
SQL Star International Ltd.

vFine NUMBER;
vActReturnDt DATE;
BEGIN
SELECT dReturnDt,dActualReturnDt
INTO vReturnDt,vActReturnDt
FROM Transaction
WHERE cMemberID=MemberID;
IF vReturnDt=vActReturnDt OR vReturnDt>vActReturnDt THEN
vFine:=0;
ELSE
vFine:=(vActReturnDt-vReturnDt)*1.50;
END IF;
DBMS_OUTPUT.PUT(vFine);
DBMS_OUTPUT.PUT_LINE (The member has to pay a fine of Rs.
||vFine);
RETURN(vFine);
END;
/
Function created.
On executing the function,
EXECUTE :FN:=fnFineCal(BJH029405);
You get the following result:
0 The member has to pay a fine of Rs. 0
In the code, DBMS_OUTPUT.PUT (vFine) puts the fine amount into the buffer. This
amount is then displayed along with the other text from the buffer by the use of
DBMS_OUTPUT.PUT_LINE().

NEW_LINE ()
This procedure puts information into the output buffer with the carriage return line
feed. This character is represented as CHR(10) in an SQL SELECT statement.
This procedure does not take any parameters.
DBMS_OUTPUT.PUT_LINE() is a combination of put and New_line.
ENABLE () and DISABLE ()

232
SQL Star International Ltd.

The ENABLE () procedure allows the code to access the buffer so that the output of
the code can be written within an iSQL*Plus session. One parameter value that you
need to provide is the size of the buffer to be allocated. On the other hand if you
dont want the code to be written to the buffer, use the procedure DISABLE (). This
does not accept any parameters.
A point to be remembered is that the buffer size that you pass should be large
enough to hold all the data coming through. In case there is data over and above the
size you passed then that data is discarded automatically. The maximum size of the
buffer is 1,0000,000 bytes. Default size is 2000.
Earlier, programmer for the library database, created a function to calculate the fine
if any, to be paid by a member when he returns a book he borrowed. You already
know how to create a function. All you need to do is include the procedure ENABLE ()
in the body of the code at the beginning before the transaction starts. Include
DISABLE () at the end of all the transaction statements.
GET_LINE ()
It retrieves data from the output buffer and returns it into the variables passed as its
parameters. It accepts all data, converts them into VARCHAR2 data and displays it.
Two out parameters are passed with this procedure. One that displays each line of
information from the buffer and the other is a counter or status parameter that
checks if the procedure was successful and if there are any more lines for display in
the buffer. You need to declare variables for the parameters to use this procedure.
However, to display the values passed into the parameter variables, you need to use
PUT_LINE () procedure.
For example, to display the content put in the buffer when compiling fnFineCal, you
use the GET_LINE () procedure as follows:
CREATE OR REPLACE FUNCTION fnFineCal
(MemberID Member.cMemberID%TYPE)
RETURN NUMBER
IS
vReturnDt DATE;
vFine NUMBER;
vActReturnDt DATE;
vLineStatus NUMBER;

BEGIN
SELECT dReturnDt,dActualReturnDt
INTO vReturnDt,vActReturnDt
FROM Transaction
WHERE cMemberID=MemberID;
IF vReturnDt=vActReturnDt OR vReturnDt>vActReturnDt THEN
vFine:=0;
ELSE
vFine:=(vActReturnDt-vReturnDt)*1.50;

233
SQL Star International Ltd.

END IF;
DBMS_OUTPUT.GET_LINE(vFine,vLineStatus);
DBMS_OUTPUT.PUT_LINE (vFine);
DBMS_OUTPUT.PUT_LINE (vLineStatus);
RETURN(vFine);
END;
/
The result obtained on executing the above function is:
EXECUTE :FN:=fnFineCal(DDG019503);
3
1
The above result displays the fine amount to be paid by member having ID
DDG019503 and also whether the GET_LINE () procedure was successful or not.

GET_LINES ()
In case you need to retrieve more than a line from the buffer then the GET_LINE ()
will not serve the purpose. You need to use the GET_LINES () procedure. This
procedure also takes in two parameters. One is a character array called CHARARR,
which stores the lines to be displayed. The other is of NUMBER type that stores the
number of lines to be displayed.

DBMS_DDL
This package provides you with access to some DDL statements. The procedures in this
package include:


ALTER_COMPILE that recompiles subprograms

ANALYZE_OBJECT that analyzes objects like indexes, tables and clusters.

You cannot use this package in triggers and procedure calls from Forms Builder or
remote sessions.

234
SQL Star International Ltd.

HTP Package
HTP Package is a web-driven version of DBMS_OUTPUT that helps us to create
well-formed HTML tags. It originates from PL/SQL toolkit which contains packages for
developing web applications.
HTP package is made up of the following procedures.
1. Anchor: Generates an anchor tag.
2. Print: Prints any value passed as a parameter.
3. Wrapper Procedure:
HTML tag.

contains a collection of procedures that help us to create

Steps to use this package:


In SQL*Plus, you must manually execute:


SET SERVEROUTPUT ON command

The OWA_UTIL.SHOWPAGE procedure to display the buffer contents

Let us create a HTML web page using this package in an iSQLPlus interface.
BEGIN
HTP.HTMLOPEN;
HTP.HEADOPEN;
HTP.TITLE(Welcome to New Jersey Library);
HTP.HEADCLOSE;
HTP.BODYOPEN;
HTP.PRINT(Home page for this library under
HTP.BODYCLOSE;

HTP.HTMLCLOSE;

END;
/
SQL> Execute

OWA_UTIL.SHOWPAGE;

Output will be:<HTML>


<HEAD>
<TITLE>Welcome to New Jersey Library</TITLE>
</HEAD>
<BODY>
Home page for this library is under construction

235
SQL Star International Ltd.

</BODY>
</HTML>
To specify the special features, the text must be enclosed in the HTML tag pair. With
the help of Htp.print the HTML tag can be generated thereafter.
For example
HTP.PRINT(<I> SqlStar <I>);

DBMS_SCHEDULER
Job Scheduling has been enhanced in Oracle10g through this package. It aims at
creating, scheduling and managing the jobs in a modular fashion thereby simplifying
the task. Collectively the subprograms within the package are called Scheduler,
which can be invoked from any PL/SQL program.

Architectural components of the Scheduler:


A job is made of a program and a schedule. Arguments required by the program can
be provided with the program or the job. All job names are of the form
[schema.]name. When you create a job, you specify the job name, a program, a
schedule and (optionally) job characteristics that can be provided through a job
class.

PROGRAM:- A program indicates what action has to be performed, for instance,


whether it should run a PLSQL Block or a stored procedure etc.
A program provides metadata about a particular executable and may require a list of
arguments.
SCHEDULER:- Indicates when and how many times a job is to be executed.

CREATING A JOB
DBMS_SCHEDULER.CREATE_JOB procedure is used to create job, which is in disable
state initially. A job becomes active and scheduled when it is explicitly enabled.
To create a job:
You need to have CREATE JOB privilege.
You need to specify a job name prefixed by schema name. Schema name is

236
SQL Star International Ltd.

optional.

A job can be created in two ways.


1. Creating a job with in-line parameters
2. Defining Program and scheduling components as a separate unit
The second method is much more flexible and reusable.

Creating a Job with In-Line Parameters


In this method, all the job parameters and scheduling arguments are given in a
single program.
It takes the following parameters:
JOB_NAME: indicates the name of the job to be created.
JOB_TYPE:- indicates what has to be executed. Options are PLSQL_BLOCK,
STORED_PROCEDURE, EXECUTABLE(executable command-line OS application).
JOB_ACTION: indicates the action to be performed.
START_DATE: indicates when the action has to begin.
REPEAT_INTERVAL: indicates the time interval for which the action has to be
repeated.
END_DATE: indicates when the action has to end.
Example to create a job and schedule it.
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
JOB_NAME=>Job_1',
JOB_TYPE=>PLSQL_BLOCK,
JOB_ACTION=>BEGIN INSERT INTO Countertab
VALUES(SEQ.NEXTVAL);END;,
START_DATE=>SYSTIMESTAMP,
REPEAT_INTERVAL=>FREQUENCY=MINUTELY;INTERVAL=1',
ENABLED=>TRUE);

237
SQL Star International Ltd.

END;
/
The above code creates a job Job_1 which inserts values into a table using sequence
SEQ every minute.

Creating a Job using Program Name and Schedule components


This method follows a modular approach. It involves:
1. Creating program components using DBMS_SCHEDULER.CREATE_PROGRAM
2. Creating schedule components using DBMS_SCHEDULER.CREATE_SCHEDULE
two

3. Creating Job using DBMS_SCHEDULER.CREATE_JOB, by integrating the above


components.

Let us now create a job using the above modular approach.


1. Creating program components:

BEGIN
DBMS_SCHEDULER.CREATE_PROGRAM(
PROGRAM_NAME=>SEQ_GEN,
PROGRAM_TYPE=>PLSQL_BLOCK,
PROGRAM_ACTION=>BEGIN INSERT INTO Countertab VALUES
(SEQ.NEXTVAL);END;);
END;
/
The above code creates a program SEQ_GEN which inserts values into Countertab
table.
2. Creating Schedule Components.
BEGIN
DBMS_SCHEDULER.CREATE_SCHEDULE(SCHED_NAME,
START_DATE=>SYSTIMESTAMP,
REPEAT_INTERVAL=>FREQ=DAILY,

238
SQL Star International Ltd.

END_DATE=>SYSTIMESTAMP+15);
END;
/
The above code creates a schedule SCHED_NAME which runs for 15 days only.
Following string expressions can be specified for REPEAT_INTERVAL:
REPEAT_INTERVAL=>FREQ=HOURLY;INTERVAL=2'
hours

job runs every

REPEAT_INTERVAL=>FREQ=DAILY job runs every day


REPEAT_INTERVAL=>FREQ=MINUTELY;INTERVAL=3' job runs every 15
mins.
REPEAT_INTERVAL=>FREQ=YEARLY
BYMONTH=JAN,MAR,JUN;BYMONTHDAY=25' job runs every 25th in the
specified month, each year.
PL/SQL expression for REPEAT_INTERVAL can be given as follows:
REPEAT_INTERVAL=>SYSDATE+1'
3. Now, we can integrate the above created program and the schedule to create a
job as below.
BEGIN
DBMS_SCHEDULER.CREATE_JOB(JOB_2,
PROGRAM_NAME=>SEQ_GEN,
SCHEDULE_NAME=>SCHED_NAME,
ENABLED=>TRUE);
END;
/
Managing Jobs
In order to manage Jobs, following built-ins can be used:
Run a Job:
DBMS_SCHEDULER.RUN_JOB(SCHEMA.JOB_NAME);
Stop a Job:
DBMS_SCHEDULER.STOP_JOB(SCHEMA.JOB_NAME);
Drop a job while it is running:
DBMS_SCHEDULER.DROP_JOB(JOB_NAME,TRUE);

Querying Data Dictionary Views for Jobs

239
SQL Star International Ltd.

To view the state of your jobs, execute the following query:


SELECT JOB_NAME, PROGRAM_NAME, JOB_TYPE, STATE
FROM USER_SCHEDULER_JOBS;
To determine which instance a job is running on, execute the following query:
SELECT OWNER, JOB_NAME, RUNNING_INSTANCE,
RESOURCE_CONSUMER_GROUP
FROM DBA_SCHEDULER_RUNNING_JOBS;

UTL_FILE Package
The UTL_FILE package extends I/O to text files within PL/SQL. This package
implements:
Client-side security using normal operating system file permission checking
Server-side security through restrictions on the directories that can be accessed
The UTL_FILE package provides security for the directories on the server through the
init.ora file, by setting the initialization parameter UTL_FILE_DIR to the accessible
directories desired.
You can use the procedures and functions of the package to:

Open files

Get text from files

Put text into files

Close files

The package also contains seven exceptions to account for possible errors that may rise
during execution.
The file processing involved when using UTL_FILE package is diagrammatically
represented below.

Before you use the UTL_FILE package to read from or write to a text file, ensure
whether the text file is open by using the IS_OPEN function. If the file is not open, open
240
SQL Star International Ltd.

the file using FOPEN function. You can then read from the file or write to the file until
the processing is done. After the file processing is done, close the file using FCLOSE
procedure.
UTL_FILE Procedures, Functions and Exceptions

The table below lists the procedures, functions and exceptions that are specific to the
UTL_FILE package.

241
SQL Star International Ltd.

The syntax for functions FOPEN and IS_OPEN are:


FOPEN Syntax
FUNCTION FOPEN
(location IN VARCHAR2,
filename IN VARCHAR2,
open_mode IN VARCHAR2)
RETURN UTL_FILE.FILE_TYPE;

Where,
location is the operating system specific string, which specifies the area (or directory) in
which the file has to be opened.
filename is the name of the file along with its extension. The path information is not
specified.
open_mode is a string that specifies how a file is to be opened. For instance,
r- read text (use GET_LINE)
w- write text (use PUT, PUT_LINE, NEW_LINE,
PUTF, FFLUSH)
a- append text (use PUT, PUT_LINE, NEW_LINE,
242
SQL Star International Ltd.

PUTF, FFLUSH)
IS_OPEN Syntax
FUNCTION IS_OPEN
(file_handle IN FILE_TYPE)
RETURN BOOLEAN;

This function tests a file handle to see if it identifies an opened file.

DBMS_METADATA
DBMS_METADATA is the PL/SQL package that implements Metadata API(application
programming interface). It allows us to:


Retrieve an objects metadata as XML

Transform this XML in a variety of ways, including SQL DDL

Submit this XML to re-create the extracted object

Though this package was introduced in Oracle 9i, the actual use was introduced in
Oracle10g in the data pump to load and unload metadata.

Let us extract the information about Member table in SCOTT SCHEMA using
DBMS_METADATA.

CREATE OR REPLACE FUNCTION get_table RETURN CLOB


IS
h NUMBER;
t1 NUMBER;
t2 NUMBER;

243
SQL Star International Ltd.

DOC CLOB;
BEGIN
h:=DBMS_METADATA.OPEN(TABLE);
DBMS_METADATA.SET_FILTER(h, SCHEMA, SCOTT);
DBMS_METADATA.SET_FILTER(H, NAME, MEMBER);
t1:= DBMS_METADATA.ADD_TRANSFORM(h, DDL);
DOC:=DBMS_METADATA.FETCH_CLOB(H);
DBMS_METADATA.CLOSE(H);
RETURN DOC;
END;
/

Browsing APIs
Browsing APIs are designed for casual use within SQL clients such as SQL*Plus.
Using these functions, metadata for objects can be fetched with a single call. Object
type definition can be extracted based on XML version or DDL using the appropriate
functions.
More than one function can also be used for certain objects. For example, GET_XXX
can be used to fetch an index by name and the same index can be fetched by using
GET_DEPENDENT_XXX by specifying the table on which it is defined.
When you invoke the above functions in iSQL*Plus to retrieve complete,
uninterrupted output, SET LONG and SET PAGESIZE commands must be used.
SET LONG 2000000 SET PAGESIZE 300
An example for using DBMS_METADATA:
SELECT DBMS_METADATA.GET_XML(TABLE,MEMBER,SCOTT)
FROM DUAL;
Issue the following statement to fetch the DDL for all the object grants on
SCOTT.MEMBER:
SELECT DBMS_METADATA.GET_DEPENDENT_DDL OBJECT_GRANT,MEMBER,SCOTT)
FROM DUAL;

Compile Time Warnings


In order to make PL/SQL programs more efficient and error free at runtime, you can
enable or disable certain warning conditions based on the requirement of business
logic.

244
SQL Star International Ltd.

The warnings issued by the Oracle PL/SQL compiler can be selectively enabled and
disabled by using
1)PLSQL_WARNINGS initialization paramerter
2)DBMS_WARNINGS PACKAGE
Warnings can be Categorized as

SEVERE

PERFORMANCE

INFORMATIONAL

ALL

Using compiler warnings we can:


Make our programs more robust and avoid problems at runtime
Identify problems that are a hindrance to the performance
Know the reason for generating undesired output.

Setting Compiler Warning Levels


Using PLSQL_WARNINGS setting, one can enable or disable the errors based on its
characterisitic. Also, one can specify which warnings are to be shown as errors.
It can be set at system level or session level. To set at system level one can use
Init.ora file or ALTER SYSTEM command.
Issue the following statements:
PLSQL_WARNINGS=ENABLE:SEVERE,DISABLE:INFORMATIONAL;
PLSQL_WARNINGS=DISABLE:ALL;
PLSQL_WARNINGS=DISABLE:4000',ENABLE:4001',ERROR:4002';
PLSQL_WARNINGS=ENABLE:(4000,4001),DISABLE:(5000);
To set at session level Use ALTER SESSION.
By default, the value is set to DISABLE:ALL.

DBMS_WARNING Package:
This package helps us to read and change the setting done by PLSQL_WARNINGS
programitically. It provides useful subprograms to select, modify and delete current
system or session level settings.
A list of subprograms are listed below.
ADD_WARNING_SETTING_CAT(w_category,w_Value,scope): Modifies the
current session or system warning settings of the warning_category previously
supplied.

245
SQL Star International Ltd.

ADD_WARNING_SETTING_NUM(w_number,w_value,scope): Modifies the


current session or system warning settings of the warning_number previously
supplied.
SET_WARNING_SETTING_STRING(W_value,scope): Replaces previous settings
with the new value.
Here,
wr_category: Permissible values are SEVERE, PERFORMANCE, INFORMATIONAL
and ALL.
wr_value

: Permissible values are ENABLE, DISABLE and ERROR.

s_scope

: Permissible values are SESSION and SYSTEM.

For example:DBMS_WARNING.SET_WARNING_SETTING_STRING(ENABLE:ALL,SESSION);
DBMS_WARNING.ADD_WARNING_SETTING_CAT(PERFORMANCE,DISABLE);

GET_CATEGORY: Returns the category name for the given message number.

GET_WARNING_SETTING_CAT: Returns the current session warning setting for


the specified category.

GET_WARNING_SETTING_NUM: Returns the current session warning setting for


the specified message number.

GET_WARNING_SETTING_STRING: Returns the entire warning string for the


current session.
Example :EXECUTE
DBMS_OUTPUT.PUT_LINE(DBMS_WARNING.GET_WARNING_SETTING_STRING);
Below is an example to set the warnings at the session level using
PLSQL_WARNINGS parameter.
Example :-

ALTER SESSION SET


PLSQL_WARNINGS=ENABLE:SEVERE,DISABLE:PERFORMANCE,
DISABLE:INFORMATIONAL;

246
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:

 Oracle supplied packages help PL/SQL to enhance the functionality of the


blocks.
For instance:

 DBMS_SQL: Using this package, one can write codes using DDL and DCL
statements within a block which is normally not allowed.
 EXECUTE IMMEDIATE: This statement is also used to execute DML, DDL and
DCL statements dynamically.
 DBMS_OUTPUT: This package help to show the output to the users console.
 DBMS_DDL: Through this package, one can dynamically compile the objects
and analyze the database objects.
 HTP : HTML code can be generated to develop web pages using this package.
 DBMS_SCHEDULER: This allows us to schedule the jobs and manage them in a
247
SQL Star International Ltd.

modular fashion.
 UTL_FILE: Input-output facilities are given by using this package.
 DBMS_METADATA: Database objects metadata can be retrieved as XML code
output or DDL code output using this package.
 DBMS_WARNING: This helps to dynamically enable or disable the warning
settings at session level or at system level.

248
SQL Star International Ltd.

Lab Exercises
1.
Create a procedure prDeleteRows that will dynamically delete rows from a
specified table. Use an OUT parameter to display the number of rows deleted as a result
of the execution of dynamic SQL.
To test the procedure, create a table Employee, which is a copy of the Employees table.
Now, execute the prDeleteRows procedure to delete rows from the table created.
2.
Use EXECUTE IMMEDIATE to perform the same dynamic SQL, as done with
DBMS_SQL package in question 4. [Note: Remember to drop the table Employee and
re-create it for the purpose of this question]
3.
Create a procedure prAnalyzeDBObjects, which analyzes the object specified as
the input parameter. The procedure tables two parameters, one for type of type of object
and the other the name of the object.
Test the procedure on the Employees table. Query the USER_TABLES to verify that the
procedure has run.

249
SQL Star International Ltd.

[Note: Use DBMS_DDL package and COMPUTE method]


4.

Write a procedure that generates a HTML page that prints Hello, World!:

5.

Write down the steps for installing UTL_MAIL.

6.

Schedule a job named job_tab_stat in SCOTT schema to gather table statistics.

7.

Get XML representation of SCOTT.EMP using DBMS_METADATA.

250
SQL Star International Ltd.

Chapter 12

PL/SQL Performance Enhancements


Standardizing Constants and Exceptions
Autonomous Transactions
Bulk Bind Enhancements
Bulk Dynamic SQL

251
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Describe the Native Compilation of PL/SQL programs
 Use Autonomous Transactions
 Identify Bulk Bind Enhancements of Oracle10g
 Use Table Functions

252
SQL Star International Ltd.

Native Compilation
Oracle supports the following different languages:

PL/SQL

C, using the Oracle Call Interface (OCI)

C or C++, using Pro*C / C++ pre-compiler

COBOL, using Pro*COBOL pre-compiler

Visual Basic, using Oracle Objects for OLE (OO4O)

Java, using JDBC Application Programmers Interface (API)

Each of these languages provides different advantages, such as ease of use, the need for
portability, or the existence of programmers with specific expertise. For instance,
PL/SQL is a powerful tool, which is specialized for SQL transaction processing, C
language is effective in executing some computation-intensive tasks and Java offers
portability coupled with security.
How would you, therefore, choose between these different implementation possibilities?
Your choice would depend on how your application intends to work with Oracle.
Only PL/SQL and Java methods / functions run within the address space of the server.
C / C++ methods / functions are dispatched as external procedures. External procedures
are procedures stored in a Dynamic Link Library (DLL) [that is, stored as a library in the
file system]. This indicates that they run on the server machine, but outside the address
space of the database server.
Prior to Oracle8.0, the Oracle database supported SQL and the stored procedure language
PL/SQL. Oracle8.0, introduced external procedures, which offered the capability of
writing C functions as PL/SQL bodies. These functions could be called from PL/SQL and
SQL. With 8.1, Oracle offered the capability of calling these external procedures from
other languages using a special-purpose interface called call specification. You could
register these external procedures with any base language (language that can call these
procedures, such as
PL/SQL, Java or C), and then call it to perform any special purpose processing. For
instance, when you register a C function with PL/SQL, the PL/SQL language loads the
library (the DLL file that contains all the C functions) dynamically at runtime, and then
calls the specified C function as if it were a PL/SQL subprogram.
In order to set external procedures written in C to use, you (as a DBA) need to take the
following steps:
1. Setup the environment in order to call external procedures by adding entries to
tnsname.ora and listener.ora files. You need to enter the agent that would handle the
external procedures. The agent is named extproc and runs on the same database server as
your application.
253
SQL Star International Ltd.

2. Identify the DLL, that is the dynamically loadable Operating System file, which stores
the external procedures. The DBA controls access to the DLL by creating a schema
object LIBRARY, which represents the DLL.
CREATE LIBRARY <library_name>
AS <file_path>;
In the LIBRARY object, you must specify the full path to the DLL. The DBA can then
grant the EXECUTE privilege on the LIBRARY object to any user.
3. Publish external procedures in a PL/SQL program through a call specification. Call
specification maps names, parameter types, and return types for the C or Java external
procedures to their SQL counterparts. The PL/SQL program is written like any other
PL/SQL stored subprogram except that, instead of declarations and BEGINEND block,
you code the AS LANGUAGE clause. For example, the code of a PL/SQL standalone
function that publishes a C function is written as follows:
CREATE OR REPLACE FUNCTION fnCallsCFunction
(/* the function finds the greatest common divisor of x and y/*
x BINARY_INTEGER,
y BINARY_INTEGER)
RETURN BINARY_INTEGER
AS LANGUAGE C
LIBRARY <library_name>
NAME <function_nametobecalled>;

The following diagram illustrates how external procedures are used:

There are certain disadvantages associated with external procedures. They are:

The developer has to write the C or Java program, store them in DLL files
(which have to be created explicitly), and then call them from PL/SQL
subprograms. This proves to be quite cumbersome especially if a person is
not proficient in C or Java languages.

254
SQL Star International Ltd.

PL/SQL library units are compiled into bytecode (code, which is machine
dependent), which is then loaded into the library cache part of the shared
pool in the System Global Area (SGA). Compilation of PL/SQL program units
into bytecode reduces their execution speed.

To overcome the limitations of external procedures, Oracle9i introduced the concept of


native compilation which has been enhanced in Oracle10g.
The Oracle10g database supports the compilation of PL/SQL library units as native code.
When PL/SQL library units are natively compiled, they are stored as shared library (DLL
files) in the file system. That is, in native compilation, PL/SQL library units are translated
into C code, which is then compiled into native code.

When a PL/SQL library unit is referenced by a user session, its corresponding shared
library is mapped to the virtual address space of the Oracle process. This indicates that
the compiled program is located in the PGA. When multiple users connected to an Oracle
instance reference the same natively compiled PL/SQL library unit, the shared library is
mapped to the virtual address space of each Oracle process. Since it is a shared library,
there is a single copy of the compiled PL/SQL program in the physical memory.
Benefits of Native Compilation of PL/SQL

There are two phases involved in natively compiling PL/SQL program units. These
phases are:

Translation of programs to C code

Compilation of C code to native code

Native compilation improves the program execution speed due to the following reasons:

Generation of native C code instead of bytecode results in faster execution of


PL/SQL programs

Elimination of overhead, otherwise associated with interpreting bytecode

Faster control flow in native code than in interpreted code

Compiled code of a PL/SQL program is mapped to the PGA instead of the SGA
into which the bytecode is loaded. This results in less contention for SGA

255
SQL Star International Ltd.

PL/SQL code not containing SQL reference is 2-10 times faster. However, it
does not speed up SQL execution

Enabling Native Compilation

To enable native compilation, you need to set the following parameters:

PLSQL_COMPILER_FLAGS: This parameter specifies a list of compiler flags


represented as a comma-separated list of strings. The PL/SQL compiler uses
this parameter. If one of the flags is set as INTERPRETED (the default mode
in releases prior to Oracle9i), it indicates that the PL/SQL programs will be
compiled into bytecode format, and will be executed by the PL/SQL
interpreter engine. If one of the flags is set as NATIVE, then PL/SQL programs
will be compiled to native (machine) code, and will be executed natively
without incurring interpreter overhead. This parameter can be set at system
level using the ALTER SYSTEM command, or at session level using the ALTER
SESSION command as follows:
ALTER SESSION
SET PLSQL_COMPILER_FLAGS = NATIVE;

PLSQL_NATIVE_LIBRARY_DIR: The database administrator can choose the


location (that is, create directories) of the shared objects (DLL) produced by
the native compiler for each PL/SQL program. The parameter
PLSQL_NATIVE_LIBRARY_DIR specifies the name of the directory where the
shared objects produced by the native compiler are stored. The PL/SQL
compiler uses this parameter. This parameter can only be set by a DBA
through an ALTER SYSTEM command or by setting a value in the INIT.ORA
file before the instance is started.
ALTER SYSTEM
SET PLSQL_NATIVE_LIBRARY_DIR = D:/nativePLSQL;

Native compilation is switched ON and OFF using the PLSQL_CODE_TYPE parameter


which can be set at instance and session level using the ALTER SYSTEM and ALTER
SESSION commands respectively.

256
SQL Star International Ltd.

Restrictions to Native Compilation

There are certain restrictions to native compilation of PL/SQL. They are:

Native compilation is performed for a library unit, that is, for a package
specification, a package body, a top-level procedure or function. You cannot
natively compile individual procedures and functions in a package. Also,
package body and type specification must be compiled in the same mode
(that is, INTERPRETED or NATIVE).

Anonymous blocks are not natively compiled. They are always compiled for
interpreted execution. This is to avoid complications with regards to naming
the anonymous DLLs, or cleaning up these DLLs in case of a server crash.

Standardizing Constants and Exceptions


Here is an attempt to create a generic code rather than a specific code. It is done by
standardizing constants and exceptions.
The advantages of standardizing constants and exceptions are:


We can use a consistent approach to handle errors across the entire


application

Reusability of the code due to its generic nature

Easy Maintenance

To implement naming and coding standards, commonly used constants and


exceptions is a good place to start with.

257
SQL Star International Ltd.

Standardizing Exceptions
Create a Standalized error handling package that includes all
programmer defined exceptions to be used in an application.

named

and

CREATE OR REPLACE PACKAGE error_pkg


IS
fk_err EXCEPTION;
seq_nbr_err EXCEPTION;
PRAGMA EXCEPTION_INIT(fk_err,-2292);
PRAGMA EXCEPTION_INIT(seq_nbr_err,-2277);
END error_pkg;
/
In the example above , the error_pkg package is a standardized exception package.
It declares a set of programmer-defined exception identifiers. Because many of the
Oracle database predefined exceptions do not have identifying names,
PRAGMA EXCEPTION_INIT directive is used in the example package to associate
selected exception names with an Oracle database error number. This enables you to
refer to any of the exceptions in a standard way in your applications. The package
can then be implemented in the following way.
BEGIN
DELETE FROM departments
WHERE department_id = deptno;
...
EXCEPTION
WHEN error_pkg.fk_err THEN
...
WHEN OTHERS THEN
...
END;

Standardizing Constants
According to definition, variable is one whose value varies, whereas a constant is one
whose value can never change. If we want local variables used in our programs not
to vary, we can convert them into constants. This makes maintenance and
debugging much easier.
Let us create a package containing constant variable and see how to access it.
CREATE OR REPLACE PACKAGE CONSTANT_PKG
IS

258
SQL Star International Ltd.

cfine CONSTANT NUMBER:=5.0;


c_dmembershipdt CONSTANT DATE:=01-Jan-2007'
END CONSTANT_PKG;
/
Now, you can access the the package variable using this method.
EXEC DBMS_OUTPUT.PUT_LINE(The Fine to be payable by Derian
is||constant_pkg.cfine);

Autonomous Transactions
The word Autonomous means independent and transaction is a set of statements
that does a logical unit of work.
AUTONOMOUS_TRANSACTION is a pragma (a compiler directive), enabling PL/SQL
program units to maintain their own transaction states.
But how does it work?
An autonomous transaction starts within the context of another transaction, known
as parent transaction, though it is independent of it. This feature allows developers
to handle transactions with more ease and finer granularity. These transactions can
be committed or rolled back without affecting the parent one. i.e., in an existing
transaction, an autonomous transaction commit or roll back changes without
affecting the outcome of the main transaction.
To make the concept clear we will go through the following code snippet:
CREATE OR REPLACE PROCEDURE prautomonous
IS
cmemberid VARCHAR2(20);
BEGIN
cmember_id:=CDB028504';
COMMIT;
INSERT .......................
prindependent;
DELETE.................
COMMIT;
END;

CREATE OR REPLACE PROCEDURE PRINDEPENDENT


IS
PRAGMA AUTONOMOUS_TRANSACTION;
CTRANID VARCHAR2(20):=T0000300100';
BEGIN
UPDATE.........
INSERT ..................
COMMIT;
END PRINDEPENDENT ;

259
SQL Star International Ltd.

Following occurs during an autonomous transaction.


1. The main or the parent transaction begins.
2. A prindependent procedure is called to start the autonomous transaction.
3. The main transaction is suspended.
4. The autonomous transactional operation begins.
5. The autonomous transaction ends with a commit or roll back operation.
6. The main transaction is resumed.
7. The main transaction ends.
Autonomous transactions have the following features.


They branch out of the main transaction, complete processing and then
branches in to resume the main or the calling transaction.

When an autonomous transaction commits, changes made by it are seen by


other transactions.

When the main transaction rollback, autonomous transaction doesnt roll


back.

Any number of autonomous transactions can be called recursively.

An autonomous transaction has to be committed or rolled back explicitly, else


it will give an error when attempting to return to the calling transaction.

PRAGMA cannot be used to mark all subroutines in a package as


autonomous. Only individual routines can be marked autonomous.

Bulk Bind Enhancements


The Oracle server uses two engines to execute PL/SQL blocks and subprograms. One is
the PL/SQL engine, which runs procedural statements, and the other is the SQL engine,
which runs SQL statements.

260
SQL Star International Ltd.

The following figure depicts the execution of a PL/SQL block:

Context Switches
The figure illustrates that the PL/SQL engine executes the procedural statements, but
sends the SQL statements to the SQL engine. Therefore, every SQL statement
encountered during execution would cause a switch (known as context switch) between
the two engines. Each context switch would add to the performance overhead.
Performance overhead occurs when SQL statements are executed inside a loop using
collections (collections include varrays, nested tables or index by tables).
For instance, in the following code snippet, the DELETE statement is sent to the SQL
engine with each iteration of the FOR loop:
DECLARE
TYPE deptList IS VARRAY(20) OF NUMBER;
depts deptList := deptList(10, 30, 70);
--department numbers
BEGIN
...
FOR i IN depts.FIRST..depts.LAST
LOOP
DELETE FROM Emp WHERE deptno = depts(i);
END LOOP;
END;

261
SQL Star International Ltd.

What is Bulk Bind?


You can improve performance by reducing the number of context switches needed to run
a particular block or subprogram.
If the SQL statements embedded within PL/SQL blocks affect four or more database
rows, you can improve performance by using bulk binds.
Binding implies assigning of values to PL/SQL variables in SQL statements. The binding
of an entire collection at once is known as bulk binding. Bulk binding improves
performance by reducing the number of context switches between the engines. Bulk
binds pass the entire collections back and forth instead of just the individual elements.
The code snippet (shown above) could be rewritten as follows so as to make use of bulk
binds:
DECLARE
TYPE deptList IS VARRAY (20) OF NUMBER;
depts deptList := deptList (10, 30, 70);
-- department numbers
BEGIN
...
FORALL i IN depts.FIRST..depts.LAST
LOOP
DELETE FROM Emp WHERE deptno = depts(i);
END LOOP;
END;

In the code snippet, the FORALL statement is used. On encountering the FORALL
keyword, the PL/SQL engine bulk-binds collections before passing them to the SQL
engine. Its syntax is:
FORALL index IN lower_bound..upper_bound
Sql_statement;

262
SQL Star International Ltd.

You can compare the performance benefit achieved by using bulk binds. To do so, first
create a table Test, then create a PL/SQL block to insert 9000 records using a FOR loop
as well as a FORALL statement. Compare the time taken to execute the INSERT
statements.
CREATE TABLE test
(TestID NUMBER (4),
TestName VARCHAR2 (15));
Table created.

DECLARE
TYPE IDTab is TABLE OF NUMBER (4)INDEX BY BINARY_INTEGER;
TYPE
BINARY_INTEGER;

NameTab

IS

TABLE

OF

VARCHAR2(15)

INDEX

BY

vID IDTab;
vName NameTab;
vTime1 NUMBER (5);
vTime2 NUMBER (5);
vTime3 NUMBER (5);

PROCEDURE prGetTime (t OUT NUMBER)


IS
BEGIN
SELECT TO_CHAR (SYSDATE, SSSSS)
INTO t
FROM DUAL;
END;
BEGIN
FOR j IN 1..9000
-loading 9000 names and IDs INTO INDEX-BY TABLE
LOOP
vID(j):=j;
vName(j):=Part No.||TO_CHAR(j);
END LOOP;
prGetTime (vTime1);

263
SQL Star International Ltd.

FOR i in 1..9000
LOOP
INSERT INTO test
VALUES (vID(i), vName(i));

END LOOP;
prGetTime (vTime2);
FORALL i IN 1..9000
INSERT INTO test
VALUES (vID(i), vName(i));
prGetTime (vTime3);
DBMS_OUTPUT.PUT_LINE(Execution
Time Taken(in secs));
DBMS_OUTPUT.PUT_LINE();
DBMS_OUTPUT.PUT_LINE(FOR loop:
|| TO_CHAR(vTime2 - vTime1));
DBMS_OUTPUT.PUT_LINE(FORALL:

|| TO_CHAR(vTime3 - vTime2));
END;
/

In the example, 9000 names and IDs are loaded into index-by tables. Next, all table
elements are inserted into the database table Test twice. First, they are inserted using the
FOR loop, which takes 5 seconds. Next, they are bulk inserted using a FORALL
statement, which takes just 1 second.
On executing the block, the following result is displayed:
Execution Time Taken (in secs)
--------------------------------FOR loop: 5
FORALL:

PL/SQL procedure successfully completed.

FORALL Statement and Unhandled Exceptions


Prior to the release of Oracle9i, if the execution of an SQL statement within the FORALL
statement raised an unhandled exception, all changes made by the previous executions
264
SQL Star International Ltd.

were rolled back. For instance, you created a table Product that stored product number
and product name as follows:
CREATE TABLE Product
(ProdNo NUMBER(2),
ProdName VARCHAR2(15));
Table created.
You inserted few records into the table, such as:
PRODNO PRODNAME
------ ---------10
Mouse (5 characters length)
20

Printer (7 characters length)

30

Processor (9 characters length)

40

Monitor (7 characters length)

You updated certain records by appending the 9 character string -Reorder to


certain product names as follows:
DECLARE
TYPE ProdNumLst IS TABLE OF NUMBER;
ProdNum ProdNumLst := ProdNumLst(20, 30,
40);
BEGIN
FORALL i IN ProdNum.FIRST..ProdNum.LAST
UPDATE Product SET ProdName = ProdName
|| -Reorder
WHERE ProdNo = ProdNum(i);
END;
/

In the block, the UPDATE statement would be executed thrice by the SQL engine, once
for each index number in the specified range. That is, once for product number 20, once
for product number 30, and once for product number 40. The first execution would be
successful, but the second execution would fail because the value Processor Reorder is
large for the product name column. In such a case, only the second execution would be
rolled back, and the third execution would never be done.

265
SQL Star International Ltd.

Implementing Error Handling Mechanism for Bulk Binds

Oracle10g has incorporated FORALL Support for Non-Consecutive Indexes for more
convenient and efficient bulk bind operations.
Earlier, Oracle9i had incorporated an error handling mechanism so that exceptions raised
during a bulk bind operation are collected and returned together once the operation is
complete.
That is, it enables bulk-bind operation to save information regarding exceptions and
continue processing.
In order to complete the bulk bind operation despite errors, add the keywords SAVE
EXCEPTIONS in the FORALL statement. The syntax is as follows:
FORALL index IN lowerbound..upperbound
[SAVE EXCEPTIONS]
sql_statements;
All errors occurring during the execution are saved in a new cursor attribute
%BULK_EXCEPTIONS. %BULK_EXCEPTIONS store values that always refer to the
most recently executed FORALL statement. This attribute stores a collection of records,
where each record has two fields:
The first field is %BULK_EXCEPTIONS (i). ERROR_INDEX, stores the iteration of the
FORALL statement during which the exception occurred.
The second field is %BULK_EXCEPTIONS (i). ERROR_CODE, which stores the
corresponding Oracle error code, SQLCODE. You can get the corresponding error
message by calling SQLERRM with SQL error code as the parameter.
The number of exceptions that have occurred during the execution of the FORALL
statement is saved in the count attribute of %BULK_EXCEPTIONS (that is,
%BULK_EXCEPTIONS.COUNT). Its values range from 1 to COUNT.

The following code shows the use of the cursor attribute %BULK_EXCEPTIONS:
DECLARE

266
SQL Star International Ltd.

TYPE NumTyp IS TABLE OF NUMBER;


NumTab NumTyp := NumTyp(10,12,0,20,15,0);
vCntErrors NUMBER;
BEGIN
FORALL i IN Numtab.FIRST..Numtab.LAST
SAVE EXCEPTIONS
DELETE
FROM TRANSACTION
WHERE nFine>200/NumTab(i);
EXCEPTION
WHEN OTHERS THEN
vCntErrors:=SQL%BULK_EXCEPTIONS.COUNT;
DBMS_OUTPUT.PUT_LINE(No. of errors
is:||vCntErrors);
FOR i in 1..vCntErrors
LOOP
DBMS_OUTPUT.PUT_LINE(Error||i|| occured during||
iteration||SQL%BULK_EXCEPTIONS(i). ERROR_INDEX);
DBMS_OUTPUT.PUT_LINE(The Oracle error is:
||SQLERRM(SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
END LOOP;
END;
/

In the code, PL/SQL raises the ZERO_DIVIDE exception when i=0. The exception
occurs when the iteration equals 3 and 6. But since SAVE EXCEPTIONS keyword is
used, the bulk bind operation gets completed. Once the bulk bind operation is complete,
SQL%BULK_EXCEPTIONS.COUNT returns 2, and SQL%BULK_EXCEPTIONS
contains the information about the exception such as (3, 1476) and (6, 1476). The output
on executing the code is:
No. of errors is: 2
Error 1 occured during iteration 3
The Oracle error is: -1476: non-ORACLE exception
Error 2 occured during iteration 6
The Oracle error is: -1476: non-ORACLE exception

267
SQL Star International Ltd.

FORALL Support for Non-Consecutive Indexes


Oracle10g introduces support for the FORALL syntax with non-consecutive indexes in
collections. The INDICES OF clause allows the FORALL syntax to be used with sparse
collections. The following example shows its usage:

--Example for indices in Oracle10g.


DECLARE
TYPE lib_mem IS TABLE OF NUMBER;
s_no lib_mem:= lib_mem(1,2,3,4,5,6,7,8,9,10);
BEGIN
FOR i IN s_no.FIRST..s_no.LAST
LOOP
INSERT INTO lib_id VALUES(s_no(i));
END LOOP;
s_no.DELETE(2);
s_no.DELETE(4);
FORALL i IN

INDICES OF s_no

INSERT INTO lib_id VALUES (s_no(i));


END;
In the above code, s_no.DELETE(2) and s_no.DELETE(4) deletes the data at index
numbers 2 and 4 respectively, thereby, creating a Sparse Data.
This discontinuous collection of data is inserted again successfully due to INDICES OF
clause.

VALUES OF clause in FORALL statement


This clause can be used to point to one collection based on the other collection during a
DML operation.
For example consider a scenario were there are two INDEX BY tables.
1. c_memberid
2. v_memberid
c_memberid stores the Member id of the members of library.
v_memberid stores the index at which these member id are stored in c_memberid
INDEX BY table.

268
SQL Star International Ltd.

The following code explains the above scenario.


DECLARE
TYPE member_tab IS TABLE OF member.cmemberid%TYPE
INDEX BY PLS_INTEGER;
c_memberid

member_tab;

TYPE value_tab IS TABLE OF PLS_INTEGER


INDEX BY PLS_INTEGER;
v_memberid value_tab;
BEGIN
c_memberid(10):='CDB028504';
c_memberid(12):='BAD109001';
c_memberid(14):='ACS099903';
v_memberid(1):=10;
v_memberid(2):=12;
v_memberid(3):=14;
FORALL l_index IN VALUES OF v_memberid
UPDATE Member
SET cmaritalstatus='Y'
WHERE cmemberid=c_memberid(l_index);
END;
/

The c_memberid INDEX BY table is used to stored values of the member id at the index
number 10, 12 and 14 respectively.
The v_memberid INDEX BY table, holds the index number of the c_memberid Index
by table, but at the index number 1,2 and 3.
Now, using a VALUES OF clause along with v_memberid Index by table in the
FORALL statement, passes the value to the c_memberid index by table used in the
WHERE clause of the UPDATE statement thereby updating the member id.

269
SQL Star International Ltd.

Bulk Dynamic SQL


In the previous chapter, we had learnt about Dynamic SQL. It can further be enhanced by
using Bulk Binding.
For example, you can use the EXECUTE IMMEDIATE statement to retrieve several
member IDs with one call to the database using BULK COLLECT keyword as shown
below.
DECLARE
TYPE tyIDList IS TABLE OF CHAR(9);
memberids tyIDList;
BEGIN
EXECUTE

IMMEDIATE

SELECT

cMemberID

FROM

Member

WHERE

ROWNUM<=15
BULK COLLECT INTO memberids;
END;
/

You can bulk bind input variables of a SQL statement using the FORALL statement and
the USING clause as shown below.
DECLARE
TYPE tyIDsList IS TABLE OF CHAR(5);
TYPE tyNameList IS TABLE OF CHAR(15);
IDs tyIDsList:=tyIDsList(02CRI,02SOC,02ADM);
Names tyNameList:=tyNameList(Criminology,
Sociology,Administration)
BEGIN
FORALL i IN 1..3
EXECUTE IMMEDIATE
INSERT INTO Category
VALUES (:1,:2)
USING IDs(i),Names(i);
END;
/

In the code, bulk binding is used to insert records into the Category table. Within the
FORALL statement the EXECUTE IMMEDIATE statement is used to execute an
INSERT statement. Two input variables IDs and Names containing the category IDs and
Category names respectively are passed to the INSERT statement using the USING
270
SQL Star International Ltd.

clause. This way 3 rows are created using one INSERT statement with one call to the
database.
You can bulk bind output variables by using the RETURNING BULK COLLECT INTO
clause as shown below.
DECLARE
TYPE tyAge IS TABLE OF NUMBER;
AgeTab tyAge;
vSqlStmt VARCHAR2(200);
vBrID CHAR(7):=01ADLNJ;
BEGIN
vSqlStmt:=UPDATE Member
SET nAge=nAge+1
WHERE cBranchID=:1
RETURNING nAge INTO :2';
EXECUTE IMMEDIATE vSqlStmt
USING vBrID
RETURNING BULK COLLECT INTO AgeTab;
END;
/

In the code, the output variable AgeTab is bound through the RETURNING BULK
COLLECT INTO clause. The EXECUTE IMMEDIATE statement is used to update the
ages of members belonging to branch 01ADLNJ. An input variable vBrID is used to pass
the branch ID that is to be used as the criteria for the update. All the updated ages are
then returned using AgeTab collection variable.

271
SQL Star International Ltd.

Table Functions
To improve the performance of a program, it is necessary to decrease the response
time. This can be achieved using Table Functions.
It does the following:
-> Accept a stream of rows as inputs
-> Pipeline the output
-> Parallelize the evaluation of table functions
You can define table functions in PL/SQL using native PL/SQL interface, or in Java or
C using the Oracle Data Cartridge Interface (ODCI).

Creating Table Functions


There are certain steps to be followed to create table functions. They are:
1. Create an object type objtype
CREATE OR REPLACE TYPE objtype
AS OBJECT (IDs CHAR(9));
Type created.

2. Create a type objtypetable, which is a table of type objtype


CREATE OR REPLACE TYPE objtypetable
AS TABLE OF objtype;
Type created.

3. Create a package pk1 with package variables of record type and ref cursor type
CREATE OR REPLACE package pk1
AS
TYPE recMember IS RECORD (cMemberID CHAR(9), cLastName
CHAR(20));
TYPE refcurMember IS REF CURSOR RETURN recMember;
END;
4. Create a function fn to transform each record corresponding to the cursor passed
as the parameter, and return the corresponding row to the caller.
CREATE OR REPLACE FUNCTION fn
(vParam IN pk1.refcurMember)
RETURN objtypeTable
PARALLEL_ENABLE(PARTITION vParam BY HASH(cMemberID))

272
SQL Star International Ltd.

PIPELINED IS
var1 pk1.recMember;
BEGIN
LOOP
FETCH vParam INTO var1;
EXIT WHEN vParam%NOTFOUND;
PIPE ROW(objtype(var1.cMemberID));
END LOOP;
RETURN;
END fn;
In the code, the function returns one record as soon as one row is processed. The
function uses the new PIPELINED instruction, which causes:
->A single result row to be returned
->The execution of the function to be suspended
->The function to restart when the caller requests the next row
In the code an optimization hint (optional) PARALLEL_ENABLE is used, which
indicates that you can execute the function in parallel. To do so, you must ensure
that the function does not use session state, such as package variables because
these variables cannot be shared among parallel execution servers.
In the code, PARTITION argument BY clause is used. It is an optional clause, and is
used with functions that have REF CURSOR type argument. With this clause you can
define the partitioning of inputs from the REF CURSOR argument to the function. This
affects the way the query is parallelized when the function is used in the FROM
clause of the query.
5. Execute the following SELECT statement:
SELECT * FROM
Member)));

TABLE(fn(CURSOR(SELECT

cMemberID,

cLastName

FROM

The pipelined function is used in the FROM clause of the SELECT statement. From the
above-mentioned steps, it can be concluded that:

The producer function (in our case, fn) uses the PIPELINED keyword in its
declaration.

The producer function uses an OUT parameter. It is of a record type, which


corresponds to a row in the result set.

When each record is processed, it is sent to the consumer function (or the SQL
statement) using the PIPE ROW keyword.

273
SQL Star International Ltd.

The producer function has a RETURN statement that does not return any values

The consumer function (or the SQL statement) uses the TABLE keyword, which
treats the resulting rows like a regular table

Benefits of Table Functions


Table functions offer the following advantages:

Table functions pipe the results as soon as they are produced. This reduces
the response time.

Table functions eliminate the need for buffering the produced rows.

With each invocation, table functions can return multiple rows.

Summary
In this chapter, you have learnt that:


In native compilation, the programs are translated to C code instead of Byte


code and then compiled to native code. This enhances the execution speed of
the program.

 Two parameters used for Native compilation are:


PLSQL_NATIVE_LIBRARY_DIR which specifies the location where the output of
the Native compiler is to be stored, and PLSQL_CODE_TYPE parameter, which
is used to switch the Native compilation ON and OFF.
 User-defined Exceptions can be associated with Oracle pre-defined Error
numbers using PRAGMA EXCEPTION_INIT directive, thereby, making the
output more readable.
 Committing or Rolling back an Autonomous Transaction will not affect the
parent transaction though it started within the context of the parent
transaction. The compiler directive used for this is
AUTONOMOUS_TRANSACTION.

274
SQL Star International Ltd.

In Bulk Binding, a set of SQL statements are bound as a collection and sent to
the SQL engine rather than as individual elements. This reduces performance
overhead.

Use of FORALL statement has been improved with clauses like SAVE
EXCEPTION (used to complete bulk binding despite errors), INDICES OF clause
(introduced in Oracle10g to handle Non-Consecutive Indexes), and VALUES OF
clause.

To improve the performance, bulk binding is combined with Dynamic SQL


where, a collection of values is passed together at runtime and is called Bulk
Dynamic SQL.

Table Functions accept a stream of rows as input and pipeline the output. This
decreases the response time thereby improving the performance.

Lab Exercises
1.
Use bulk dynamic SQL to insert the following four sets of values into the
Countries table:

Country ID: DU

FI
MA
PH

Country Name: Dubai

Finland
Malaysia
Philippines
Verify the rows inserted into the Countries table.

2.
Experiment the usage of Autonomous Transaction through the following series
of questions.
a. Verify that you are logged into your SCOTT schema by using show
command in SQL *Plus:
b. Create a procedures as given below

275
SQL Star International Ltd.

CREATE OR REPLACE PROCEDURE dml_locations_countries(LOC_ID


NUMBER,V_CITY VARCHAR2)
IS
BEGIN
INSERT INTO LOCATIONS(LOCATION_ID,city)
VALUES(LOC_id,v_city);
END;
/
Create and Save the script in a file.
Create another procedure based on this specification.

CREATE OR REPLACE procedure modify_dept_emp


IS
BEGIN
UPDATE departments
SET

department_name=UPPER(department_name);

UPDATE

employees

SET last_name=UPPER(last_name);

COMMIT;

END modify_dept_emp;
/
Create and Save the script in a file.
Run the script to create the procedures.

c. Are either of these procedures defined as autonomous transactions?

d. Examine the contents of the LAST_NAME column in the EMPLOYEES table


and the DEPARTMENT_NAME of the DEPARTMENTS table. What case is
the text?

e. Execute the procedure dml_locations_countries with following values


3300,Milan

f. Examine the contents of LAST_NAME column in the EMPLOYEES table and


the DEPARTMENT_NAME of the DEPARTMENTS table. What is the case of the
text? Was the row inserted in the LOCATIONS table?

276
SQL Star International Ltd.

g. Rollback the transaction.

h. Add the pragma AUTONOMOUS_TRANSACTION to modify_dept_emp


procedure.
Uncomment the COMMIT statement in the modify_dept_emp procedure.

i. Execute the procedure dml_locations_countries again with the values


3301,Milan
Execute the procedure modify_dept_emp
Now rollback the transaction.

j. What are the results? Which statements are committed and which
statements are rolled back?

3.

Create a PL/SQL composite variable and assign few employee names into it. Delete
the 3rd indexed data. Now, insert the data into database table Emp_Names.
Handle the errors using Bulk save exception clause and display them.

4.

Create a table function that will return the employee ID from the Employees table.

277
SQL Star International Ltd.

Chapter 13

Large Objects
LONG vs. LOB
LOB Datatypes
Migration of LONG to LOB
Using LOBs
DBMS_LOB Package
Removing LOBs

278
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 List the different datatypes for large objects
 Use LOBs
 Migrate from LONG to LOBs


Manipulating LOBs using DBMS_LOB package

279
SQL Star International Ltd.

Large Objects
One of the reasons for using databases is that it can store large amount of data.
Special database objects were built to store huge chunks of data. You know of
abstract datatypes that store data according to your requirement. Now you will use
objects referred to as Large Objects or LOBs. These are also datatypes but should
not be confused with object types learnt earlier. With the use of LOBs, you can use
large amount of data that need not necessarily be stored inside the database.

LONG Vs LOB
LONG and the LONG RAW datatypes are used to store large unstructured data. These
datatypes could hold binary data, images or documents. Then one might wonder why
LOBs were introduced. The reasons why LOBs are used bring out the following
differences between LONG and LOB.

A table is allowed only one LONG datatype column, but as many LOB
columns.

LONG datatypes have a maximum storage capacity of 2 GB whereas LOBs can


go up to 4 GB in 9i and more than 4 GB in the Oracle10g.

While querying, LONG returns the data but LOBs return the data locators.

Data stored as LONG is stored within the same datablock in the database.
LOBs store the locator of the data if the data is more than 4000 bytes. The
data is stored in separate host files or different tablespaces.

Object types cannot have LONG attributes but can have LOB attributes.

Data access is sequential in the case of LONG data and random in the case of
LOB data. In the latter, data is accessed through a file-like interface.

LOB Datatypes
LOBs are categorized based on the way they are stored and the way they are
interpreted by the Oracle server. They are also classified depending on the kind of
data they hold. In Oracle10g the maximum size limit for LOBs has been increased
from 4 GB to 8 - 128 terabytes, depending on your database block size.
Given below are the four LOB datatypes:

BLOB or Binary LOB can store binary data in the database.

CLOB or Character LOB stores character data in the database.

BFILE or Binary File is a file that stores data outside the database up to a limit
specified by the operating system. Data in BFILE is stored as read-only data.

NCLOB is a National Character LOB that stores a multi-byte character set.

Based on their storage they are classified as:

280
SQL Star International Ltd.

Internal LOBs that are stored in the database. They include:




BLOB, which are bitstream data (like LONG RAW)

CLOB that is a single byte character stream, and

NCLOB is a fixed-length, multi-byte character stream.

External LOBs that are stored outside the database, like BFILEs.

Internal LOBs
The following can be assigned as internal LOBs:

Columns of table

Attributes of an abstract datatype

Bind or host variables

PL/SQL variables, parameters and RETURN statement

281
SQL Star International Ltd.

Managing Internal LOBs


You can interact with LOBs through an interface provided by the PL/SQL package
DBMS_LOB and Oracle Call Interface (OCI). Oracle also provides support to manage
the LOBs.
Steps that you need to follow when using LOBs are:
1. Creating and populating the tables using the LOB datatypes.
2. Declaring and initializing LOB locators in the PL/SQL blocks.
3. Lock the rows containing the LOB locator using SELECT FOR UPDATE, when
you want to update the LOB value. The locator value in the LOB column is
stored in a variable for the purpose.
4. Use the DBMS_LOB package or OCI calls to perform any operations with the
LOB data.
5. Save the changes to the database with the COMMIT statement.

External LOBs
External LOBs refer to data, which are often known as BFILEs. BFILEs are large
objects, which may be of formats such as GIF, JPEG, MPEG or text and are stored in
operating system files outside the database tablespaces. The external LOBs may be
located on hard disks or CDROMs, but a single external LOB cannot extend from one
device to another.
The BFILE datatype enables database users to access the external file system.
The BFILE datatype contains a locator that points to the file in the file system that
holds the main data. The locator stores the directory alias and the file name.
The Oracle server was faced with the issue of securing data due to the unauthorized
access to files on a server. To overcome this problem, a security mechanism was
introduced in Oracle9i, wherein, it shields the operating system from unauthorized
access, and does away with the need to manage additional user accounts on an
enterprise computer system. Users can read from BFILEs the same way as they
would from an internal LOB. But there are certain restrictions imposed on the file
itself, which are with regard to:

Access permissions

OS maximum file size

Space limits of file system

Non-Oracle manipulation of files

DIRECTORY: A New Database Object


In order to associate an operating system file to a BFILE object, you need to first
create a DIRECTORY object, which would provide an alias for the pathname to the
operating system file. A DIRECTORY object is a non-schema database object that
administers the access and usage of BFILEs in the Oracle server.

282
SQL Star International Ltd.

The DIRECTORY object specifies an alias for the directory on the file system of the
server within which a BFILE is located. You can provide secure access to files within
the corresponding directories on a user-by-user basis by granting the appropriate
privileges. For instance, you can make some directories read-only or inaccessible.
DIRECTORY object privileges can be granted and also revoked.
The DIRECTORY object is owned by SYS. The DBA or any user with the CREATE ANY
DIRECTORY privilege can create the DIRECTORY object.
Points to remember while creating DIRECTORY objects are:

DIRECTORY objects should not be created on the path of database files,


because doing so could corrupt the database.

Grant the system privileges CREATE ANY DIRECTORY and DROP ANY
DIRECTORY to users discriminately.

In case you migrate the database to a different OS, you would have to
change the path value of the DIRECTORY object.

Managing BFILEs
You interact with BFILEs in the same way as with any other LOB. The steps you need
to follow to work with BFILE and DIRECTORY objects are:
1. Creating the directories in the operating system and setting their permissions
so that Oracle can read them.
2. Loading files into the directories created.
3. Creating a table with the BFILE datatype column in the Oracle server.
4. Creating the DIRECTORY objects with READ permission.
5. Inserting data into the table with the BFILENAME function. The function takes
2 parameters.
1. Directory Name
2. OS Filename
6. Declaring and initializing LOB locators.
7. Set the row and column with the BFILE value into the locator.
8. Read the BFILE data using the OCI or the DBMS_LOB package interface.

283
SQL Star International Ltd.

Migrating LONG to LOB


The internal LOB data types (CLOB and BLOB) introduced in Oracle8 offered many
advantages in comparison to those provided by older datatypes such as LONG and
LONG RAW. Hence, it was recommended that applications using the LONG datatype
be converted to use LOBs so as to take advantage of the added functionality of LOB
datatypes. In order to facilitate migration of data and applications to the LOB
datatypes, the Oracle9i database introduced the LONG API for LOBs.
The LONG API is a term used to refer to a group of features (some of which were
introduced in prior versions) that assist the manipulation of large character data and
raw binary data. The LONG API ensures that when you change your LONG columns
to LOBs, you will be required to make few changes, if any, to your existing
applications.
Support for LONG-to-LOB Migration
In Oracle8i, an operator named TO_LOB was used to convert a LONG column to a
LOB column. In Oracle10g, you can perform this operation using the ALTER TABLE
command. The syntax for the ALTER TABLE command is as follows:
ALTER TABLE [<schema>.] <table_name>
MODIFY (<LONG_col_name> { CLOB | BLOB | NCLOB};
Using this syntax, you can either modify a:

LONG column to a CLOB or NCLOB column

LONG RAW column to a BLOB column

For example, a table having a LONG column LongCol as shown below:


CREATE TABLE LongTab
(TabID NUMBER,
LongCol LONG);
This can be converted into a LOB column as follows:
ALTER TABLE LonTab
MODIFY (LongCol CLOB);
Any trigger and constraints (the only allowed constraints are NULL and NOT NULL)
on the old LONG columns are maintained for the new LOB column. The default value
specified for the LONG column is also maintained, unless a new default value is
specified in the ALTER TABLE statement.
With LONG API, you can reference data from CLOB and BLOB columns by regular
SQL and PL/SQL statements. LONG API supports the following with respect to LONGto-LOB migration:

284
SQL Star International Ltd.

Implicit conversion: The API supports the assigning of a CLOB (BLOB)


variable to a LONG (LONG RAW) or a VARCHAR2 (RAW) variable, and vice
versa.

Explicit conversion: To convert other datatypes to CLOB and BLOB, two


explicit conversion functions are used. They are:

TO_CLOB (), which converts LONG, VARCHAR2 and CHAR to CLOB

TO_BLOB (), which converts, LONG RAW and RAW to BLOB

Function and procedure parameter passing: All user-defined functions and


procedures can use CLOBs and BLOBs as actual parameters where the formal
parameters are VARCHAR2, LONG, LONG RAW, and RAW, and vice versa.

Passing of LOBs to SQL and PL/SQL built-in functions and operator: API
supports the passing of a CLOB variable to SQL and PL/SQL VARCHAR2 builtin functions behaving just like a VARCHAR2. Similarly, VARCHAR2 variables
can be passed into DBMS_LOB package acting like a LOB locator.

Limitations of LOB Migration


There are certain restrictions on LOB migration. They are:

Clustered tables do not support LOB columns (LONG and LONG RAW columns
are allowed in clustered tables). Hence, the LONG and LONG RAW columns of
a table that is part of a cluster cannot be changed to CLOB or BLOB.

If the LONG columns of a replicated table or materialized view are converted


to LOB, in that case the replicated tables or materialized views would have to
be rebuilt manually.

The UPDATE OF list of an update trigger does not support LOB columns.
Therefore, when a LONG column is converted to a LOB column, triggers that
were intended to fire on the update of a LONG column becomes invalid.

INSTEAD OF trigger does not support LOB columns. Therefore INSTEAD OF


triggers on views with LOB columns will fail to perform inserts and updates.

All indexes on columns being migrated to LOBs need to be rebuilt manually.

Before migrating to LOB columns, domain indexes on LONG and LONG RAW
columns must be dropped.

285
SQL Star International Ltd.

Structure of LOBs
A LOB has two parts. One is the value of the LOB. This is the actual data that is held
by the LOB. The other is the locator, which is a pointer to the data stored by the
LOB. LOBs does not store the data values directly, but stores a pointer to the data
instead.

If you write a program to manipulate or access LOBs, you need to declare a pointer
or a LOB locator within it. An internal LOB data is stored in a LOB segment and the
LOB column value for that row is the pointer or locator to the data. If the LOB data is
stored externally, then only the pointer is stored in the table. Programs to access
and manipulate LOB data use these pointers.

Using LOBs
LOBs are used just like any other datatype. The difference is in the amount and
method in which the data is stored.
Creating LOB Columns
There is no special syntax for creating columns of LOB datatypes. Like any other
datatype, they are assigned to columns at the time of table creation.
Returning to the library database, a librarian at the main branch thought it would be
a good idea to store a short description of each book and have a picture of its cover
page. They will have to include new columns in the Book table. Instead, they
thought they could first create a table for the purpose and see how useful it is.
Hence, the following table was created:

286
SQL Star International Ltd.

CREATE TABLE BookDesc


(BookID CHAR(13) REFERENCES Book(cBookID),
blCoverPage BLOB,
bfMovieClips BFILE,
clBookReviews CLOB);
Table created.
This creates a table with book ID as a foreign key, and two columns of BLOB and
BFILE type to store large data of graphics and text. The BLOB data is administered
by the same database integrity and concurrency rules, and is stored within the
database. The BFILE data is stored outside the database and is not included in the
database concurrency and integrity checks.
Data being stored outside the table whether in a space within the database or in a
separate file, it is referred to as out-of-line data. Storing data as such helps improve
database functionality, as the data need not be scanned each time a row is read.
When records in a table are read, only the locators stored in a LOB column is read
and the actual data from a LOB is accessed only when it is required.
Storage Specifications
You can specify storage parameters for LOB data when creating a table to hold it.
These parameters define the area that is used by the LOB to store its data.
The two clauses that are used with the CREATE TABLE statement are STORAGE and
TABLESPACE. STORAGE specifies the manner in which the data is stored and
TABLESPACE specifies the name of the tablespace where you want the data to be
stored.
For instance, if you want to specify the location parameters for storing the cover
page of the books and the description of its story then the code written above to
create the table BookDesc can be re-written as:
CREATE TABLE BookDesc
(BookID CHAR(13) REFERENCES Book(cBookID),
blCoverPage BLOB,
bfMovieClips BFILE,
clBookReviews CLOB)
STORAGE (INITIAL 50K NEXT 50K PCTINCREASE

0)

TABLESPACE BookDesc;

287
SQL Star International Ltd.

Inserting Values
You insert values into LOB datatypes directly using variables. You can perform
updates on LOB columns by setting the value to be modified to another LOB value or
to an empty locator using the EMPTY_CLOB () or EMPTY_BLOB () functions. Syntax
to use this function is:
INSERT INTO <table_name>(column1,column2,CLOBcolumn,
BLOBcolumn,)
VALUES (value1,value2,EMPTY_CLOB(),EMPTY_BLOB(),);
Where,
CLOBcolumn, BLOBcolumn are two LOB columns in a table
EMPTY_CLOB(),EMPTY_BLOB() are the functions to set empty locators to the two
LOB columns respectively
A point to remember is that, before manipulating the value the row must be locked.
You lock a row by using the FOR UPDATE clause.
Ensure that LOB columns must have either empty locators or a value.
To insert values into LOBs in PL/SQL, you use one variable to set the LOB locator and
another variable to set the length of the text to be inserted into the LOB. Use the
DBMS_LOB package to manipulate LOBs.
For instance, to insert data into the CLOB for book reviews execute the following
code:
DECLARE
clLocator CLOB;
vData VARCHAR2(32766);
nDataLength NUMBER;
iDataOffset INTEGER;
BkID CHAR(13):=&bkid;
BEGIN
vData:=text to be entered into the LOB;

288
SQL Star International Ltd.

SELECT clBookReviews
INTO clLocator
FROM BookDesc
WHERE BookID=BkID
FOR UPDATE;
iDataOffset:=DBMS_LOB.GETLENGTH(clLocator)+1;
nDataLength:=LENGTH(vData);
DBMS_LOB.WRITE(clLocator, nDataLength, iDataOffset, vData);
COMMIT;
DBMS_OUTPUT.PUT_LINE(TO_CHAR(nDataLength)||bytes have been
written to the LOB);
END;
/
In the code, the four variables declared are to respectively store the locator of the
data, the data, the size of the data and the point at which the data should start. The
SELECT INTO clause enters the locator value for a particular record in the table,
depending on the condition.
The DBMS_OUTPUT.GETLENGTH () procedure finds out the point in the block from
where the LOB data can be written and stores this in a variable.

The DBMS_LOB.WRITE () procedure passes the four variables and writes the data to
the LOB at the offset specified.
The write is committed. DBMS_OUTPUT.PUT_LINE () procedure displays a message
on the screen as to how much data has been inserted.

To insert empty locators into the LOB columns in the table, use the functions
EMPTY_CLOB () or EMPTY_BLOB () according to the datatype. Entering an empty
locator is not the same as entering a NULL value. In case the value of a locator is
NULL, you need to update it with the empty locator to change its status to NOT
NULL.

289
SQL Star International Ltd.

Updating LOBs
Updates to LOBs are done in the same manner that the traditional datatypes are
updated. The UPDATESET statement is used to perform the required
modifications. In case Peter, a librarian wants to modify some of the review
comments that have been entered about a book, he can execute the following
statement to first assign an empty locator to the column and then update the value.
UPDATE BookDesc
SET clBookReviews=EMPTY_CLOB()
WHERE BookID=CLS050005777';
This assigns the empty locator and now he may use the UPDATE statement to enter
a new value.

DBMS_LOB Package
You can modify values stored in columns of LOB datatypes using the DBMS_LOB
package as already dealt with when inserting values into LOBs. There are a set of
functions and procedures to select and modify data from a LOB.
These functions and procedures are classified into two types. They are:

Mutators, which can modify LOB values: WRITE, APPEND, COPY, TRIM,
ERASE, FILECLOSE, FILECLOSEALL and FILEOPEN

Observers, which can only read LOB values: READ, GETLENGTH, COMPARE,
FILEGETNAME, FILEEXISTS, FILEISOPEN, INSTR and SUBSTR

The procedures include:

OPEN: To open a LOB in either a read-only mode or read-write mode.

READ: To read LOB data. For this it uses, the locator, size of data to be read,
the offset (starting point of read) and an output variable to hold the output,
as parameters.

TRIM: Performs the same task as the RTRIM function in SQL. You can use this
function if you want to reduce the size of your LOB. You need to pass the
locator and the new required size of the LOB as parameters.

WRITE: To write data into a LOB. The parameters to be passed with this
procedure are the locator value, the number of characters to be written, the

290
SQL Star International Ltd.

offset to start the write and the buffer variable assigned to the data being
added.

APPEND: To add the contents of one LOB value to another in case one LOB
requires data from another. For this you pass the two LOB locators as
parameters, one for the source and the other for the destination.

WRITEAPPEND: To write data into a LOB from the point where the current
data ends.

CREATETEMPORARY: Creates a temporary BLOB or CLOB and index in the


users tablespace.

FREETEMPORARY: Frees the temporary LOB created using the previous


procedure.

ERASE: Removes a part of or the entire LOB. You pass the locator, amount of
data to be erased, and the offset where the erase needs to start.

COPY: A procedure to copy contents from one file to another. It uses the
locators of the two LOB files, the size of the data to be copied and the offsets
for the two files. The offset of the source file indicates from where the copying
should start and that of the destination file specifies from where the data
should be pasted.

These procedures can be used when you want to perform a set of transactions and
require working with lot of files. You can open a file during execution of the PL/SQL
block. You can check whether the file is a temporary one and if so then the set of
statements to perform operations on it can be different.
The functions to manipulate LOB values include:

COMPARE: Compares two LOB values. There parameters that are used are,
the locators of the two files, the number of characters to be compared, and
the offsets of the two files.

GETCHUNKSIZE: Determines the space used in a LOB.

GETLENGTH: Determines the length of the data. It is the same as the SQL
LENGTH function. This function takes the locator as a parameter.

INSTR: Performs the same function on a LOB that the INSTR function does in
SQL. It has four parameters of the locator, the pattern to be checked, the
offset or starting point of the read, and the occurrence of the pattern. This
function can help you locate particular information within the large text files.

SUBSTR: Performs the same function on a LOB that the SUBSTR function
does in SQL. It takes the parameters of locator, the number of characters to
be read, and the offset.

ISOPEN: Checks whether a LOB is already open.

ISTEMPORARY: Checks whether the LOB was created using


CREATETEMPORARY.

These functions allow you to make modifications to the data that has been read from
the file. These modifications happen before the user reads the data.

291
SQL Star International Ltd.

There are some functions and procedures specific to BFILEs. These include:

FILEOPEN: A procedure to open files for reading

FILECLOSE: A procedure to close specific files. Every FILEOPEN must have a


corresponding FILECLOSE call.

FILECLOSEALL: Closes all files that are open.

FILEGETNAME: Gets the name of the external BFILE to which the locator
points.

LOADFROMFILE: Allows you to load the contents of a BFILE to a CLOB or


BLOB

FILEEXISTS: A function that determines whether the external BFILE being


referenced by a locator exists.

FILEISOPEN: Checks whether the external file is open.

These procedures are used in PL/SQL blocks written to perform operations with
LOBs. Variables are declared and they are used to either store results of the
functions or used as parameters to perform operations.
All the functions and procedures need to be preceded by the keyword DBMS_LOB.
All the DBMS_LOB functions return NULL if any of the input parameters is NULL. All
the DBMS_LOB procedures, which can modify LOB values, raise an exception in case
the destination of BFILE is input as NULL.
You can implement one of the procedures to know how the rest of them need to be
used. If you want to add the review comments of a critic to the review comments of
a book with id CLS040005776, you use the APPEND procedure as shown.
DECLARE
clDestination CLOB;
clSource CLOB;
BEGIN
SELECT clBookReviews
INTO clDestination
FROM BookDesc
WHERE BookID = CLS040005776
FOR UPDATE;
SELECT clBookReviews
INTO clSource
FROM BookDesc
WHERE BookID = CLS050005777;

292
SQL Star International Ltd.

DBMS_LOB.APPEND(clDestination, clSource);
COMMIT;
END;
/

Removing LOBs
LOB values can be removed by using the DELETE statement. This will, as you know
delete the record from the table. In case you want to keep the record and only
remove the LOB value, then replace the LOB value (the locator) in the table with a
NULL or an empty locator. The latter is possible by using the
EMPTY_BLOB/CLOB/NCLOB () function. It must be noted that, these two options do
not amount to the same value in the column. NULL is a null value, but using the
empty locator ensures that there is nothing stored in the column for that record, not
even a NULL value.
Deleting a record, truncating or dropping a table will result in the LOB getting
destroyed.
In case of a BFILE, the process is the same when working on the database table, but
the external BFILE needs to be explicitly removed from the operating system using
the OS commands.

293
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:
 Large Objects (LOBs) store Large chunks of data. They can store more than 4GB
of binary, character, image data etc.
 BLOB(that stores Binary data), CLOB(that stores Character data), BFILE( Binary
file stored outside the database) and NCLOB(National Character Large Object to
store multibyte character set) are the different types of LOBs.
 BLOB, CLOB and NCLOB are called Internal LOBs as they are stored inside the
database. BFILE is called External LOB since it is stored outside the database.
 To facilitate migration from LONG to LOB, Oracle9i introduced the LONG API for
LOBs.
 To convert other datatypes to CLOB and BLOB, TO_CLOB() and TO_BLOB()
functions are used.
 LOBs do not store values directly but store a locator or a pointer to the data
instead.
 DBMS_LOB package has a set of procedures and functions to select and modify
data from an LOB.

294
SQL Star International Ltd.

Lab Exercises
1.
Create a table called EmpPersonalDetails. The structure of the table should
be as follows:

2.
Insert two rows into the EmpPersonalDetails table. The rows must have IDs
as 501 and 502 respectively. For CLOB use the empty function and for BLOB provide
NULL.
3.

Create a table EmpReview, whose structure is as follows:

Insert review details of employee 501 and 502 as follows:

4.

Populate the CLOB column of EmpPersonalDetails table as follows:




Populate the first row of CLOB column Review by using the UPDATE
statement

Populate the second row of CLOB column Review by using PL/SQL

Query the EmpPersonalDetails table to retrieve the employee number, last name
and review details.

295
SQL Star International Ltd.

Chapter 14

Database Triggers
Permissions to Create Triggers
Guidelines to Design Triggers
DML Trigger Components
Trigger Execution Mechanism
Creating Statement Level Triggers
Creating Row Level Triggers
Creating INSTEAD OF Trigger

296
SQL Star International Ltd.

Objectives
At the end of this chapter, you will be able to:
 Identify trigger components
 Create database triggers

297
SQL Star International Ltd.

Introducing Triggers
You know how a gun works. Pull the trigger and the bullet fires. Meaning, the action
of pulling the trigger causes something to happen. Similarly, there are some
transactions that need to be performed as a result of another transaction. This kind
of functionality is implemented by using a database object called trigger.

Database Triggers
Database triggers can be defined as a block of PL/SQL code that gets executed
automatically based on an event in an application. An event refers to any DML on a
table (such as INSERT, UPDATE or DELETE triggering statements), or DDL
statements such as CREATE and ALTER. Triggers are also executed implicitly on the
occurrence of some user actions or database system actions. Examples of such
situations are when a user logs on, or when the database is shutdown by the DBA.
Database triggers can be classified as system triggers on a database and system
triggers on a schema. In case of system triggers on a database, triggers fire for each
event for all users, whereas in case of system triggers on a schema, triggers fire for
each event for a specific user.
You can define database triggers on tables and also on views. But, if actions on the
view cause DML statements to be executed on the base table, then the triggers
associated to that table are also fired apart from the triggers associated with the
view.
Permissions to Create Triggers
To be able to create triggers on a table, you need to have permissions to modify the
table. To create triggers on tables you would need:

The ownership of the table


The ALTER privilege to be able to modify a particular table
The ALTER ANY TABLE permission if you want to modify or create a trigger on
a table from another users schema
The CREATE TRIGGER privilege to allow you to create triggers on your
schema. (This privilege comes with the RESOURCE role)
The CREATE ANY TRIGGER privilege to allow you to create triggers on other
schemas

Guidelines to Design Triggers

Below is a list of situations in which you should create triggers and those when you
should not.

298
SQL Star International Ltd.

Use triggers to ensure that when a specific operation takes place, related
actions are performed.

Use triggers only for global operations that should be fired for triggering
statements, irrespective of the user or application that issues the statement.

Do not create triggers to implement certain functionalities that are already


built into the Oracle server. For example, do not create triggers that will
duplicate the functionality of integrity rules that can be implemented using
declarative constraints.

Using too many triggers could lead to interdependencies and this is difficult to
maintain. Hence, use triggers only when required. Also take care of the
recursive or cascading effects that triggers could have.

In case of lengthy trigger logic, create stored procedures with the logic and
invoke the procedure in the trigger body.

DML Trigger Components and Classifications


Trigger functionality depends largely on its type and components. You need to decide
what kind of a trigger you want executed and when.
To create a trigger for a transaction you need to decide when the trigger should be fired,
what type of a trigger it should be and what should the trigger code perform. Hence, the
components of triggers include:

Trigger Timing

Trigger Events

Trigger Type

Trigger Code

Trigger Timing
Trigger timing indicates when the trigger fires with respect to the triggering event.
Depending on when the trigger should fire, triggers are categorized into the following
three types:

BEFORE triggers:

The trigger body is executed before the triggering DML event on a table. BEFORE
triggers are used mainly to:

Prevent unnecessary execution of triggering statements and their rollback in


case the triggering action raises an exception

Obtain column values before the completion of a triggering statement

Initialize global variables

Validate certain complex business rules

299
SQL Star International Ltd.

 AFTER triggers:
The trigger body is executed after the triggering DML event on the table. AFTER
triggers are used mainly to complete the triggering statements before the
triggering actions are performed.

 INSTEAD OF triggers:
Instead of the triggering statement, the trigger body is executed. INSTEAD OF
triggers are used to modify views, which otherwise cannot be modified directly
using SQL DML statements. When you try to execute INSERT, UPDATE and
DELETE statements on a view, the INSTEAD OF trigger works in the background
to perform the actions that are coded in the trigger body directly on the
underlying base table.

Triggering Events
Triggering event indicates which DML operation on the table or view causes the
trigger to fire. These events include the three DML statements: INSERT, UPDATE and
DELETE. You can write triggers to fire on the execution of any one or a combination
of the three DML statements.
In case the event is an UPDATE statement, a column list specifying which columns
must be updated in order to fire the trigger can be included. The same is not possible
in case of an INSERT or a DELETE statement as they are implemented on the entire
row in a table and not on selected columns.

Trigger Types
Triggers have been classified into two types based on how many times the trigger is
fired, that is, whether the trigger body is executed for each row the triggering
statement affects or is executed only once. The two types of triggers are:

 Row Triggers
Row triggers cause the trigger body to be executed once for each row affected by
the triggering event. In case no rows are affected by the triggering event, the
row trigger is not executed. These triggers are useful when the trigger action
depends on the data of rows that are affected by the triggering event.

 Statement Triggers
Statement triggers causes the trigger body to be executed only once for the
triggering event. This trigger does not depend on the data of rows affected and
hence fire once even if no rows have been affected. This is the default trigger.
This type of trigger is used mainly to perform complex security check on users.

Trigger Code
Trigger code contains what action needs to be performed when the triggering event
is issued. The trigger code is nothing but a PL/SQL block, which can contain SQL and

300
SQL Star International Ltd.

PL/SQL statements, variables, cursors and also exceptions. It can also contain a call
to a procedure.
Remember that a trigger size cannot exceed 32K.

Trigger Execution Mechanism


DML events can cause four types of triggers to fire. These triggers fire before or after
the DML statements are executed. The procedural steps that describe the execution
mechanism of a trigger are as follows:
1. The first to execute are all the BEFORE triggers. The trigger and its statements are
processed first and then the triggering statement is processed. This is not efficient in
an UPDATE statement but is for a DELETE statement.
2. Then the events for each row causes the following:

All the BEFORE row triggers are executed.

The DML statements are executed. Integrity constraints are checked if


required.

All the AFTER row triggers are executed.

3. Any integrity checks to be completed are done.


4. All the AFTER statement triggers are executed. The AFTER triggers fire after the
server processes the triggering statements and this is beneficial when the triggering
statement is an UPDATE on a row and the trigger is a row trigger. The triggering
statement is processed and the changes are stored in buffer, then the concentration
is on processing the trigger statement.
This sequence of execution is graphically depicted as follows:

Trigger Firing Sequence

301
SQL Star International Ltd.

Creating Database Triggers


A generic syntax for creating triggers is as follows:
CREATE [OR REPLACE] TRIGGER [user.]<trigger_name>
{ BEFORE | AFTER | INSTEAD OF}
{DML event1 [OR event2 OR event3. . .]}
ON <table_name> |
DDL event ON

[<schema_name>| <database_name> |

DATABASE event ON]


[FOR EACH ROW [WHEN (condition)]]
PL/SQL block;
Creating Statement Triggers

Statement level triggers, as you know fire once for the entire transaction. The syntax for
these triggers is:
CREATE [OR REPLACE] TRIGGER [user.]<trigger_name>
{timing}
{DML event1 [OR event2 OR event3. . .]}
ON <table_name>
PL/SQL block;

Where,
[OR REPLACE] is to be used when you want to modify the definition of an already
existing trigger.
trigger_name is the name of the trigger.
timing specifies when you want the trigger to fire BEFORE, AFTER or INSTEAD OF.

302
SQL Star International Ltd.

DML events are any of the INSERT, UPDATE, DELETE or any combination of them.
table_name is the name of the table to which the trigger is associated.
PL/SQL block is the set of statements that defines what the trigger should do. This is
the trigger code.

BEFORE Statement trigger


This trigger fires before a DML operation is performed on a table. The following code
gives an example of a BEFORE statement trigger.
CREATE OR REPLACE TRIGGER trigLibTranSecurity
BEFORE INSERT ON Transaction
BEGIN
IF (TO_CHAR (SYSDATE, DY) = SUN OR
(TO_CHAR (SYSDATE, HH24) NOT BETWEEN 10 AND 19))
THEN
RAISE_APPLICATION_ERROR (-20022, Cannot record
book issue transactions on Sundays and beyond
library hours);
END IF;
END;
/
Trigger created.
This trigger ensures that a user is denied the permission to insert book issue details
into the Transaction table either after the library hours or on a Sunday. If the user
attempts to do so, the trigger gets fired and the RAISE _APPLICATION _ERROR, a
server-side built-in procedure, is raised and the user gets an error message as
follows:
INSERT INTO Transaction
VALUES (T9876543101,CDB028504',CLS020005775'
,SYSDATE,SYSDATE+7,NULL,NULL);
Displays:

INSERT INTO Transaction


*
ERROR at line 1:

ORA-20022: Cannot record book issue transactions on Sundays and


beyond library hours

303
SQL Star International Ltd.

ORA-06512: at SCOTT.TRIGLIBTRANSECURITY, line 5


ORA-04088:

error during execution of trigger

SCOTT.TRIGLIBTRANSECURITY
Very often, you may be unaware of which DML transaction will be performed on a
table. Therefore, you would have to create triggers for all the three situations with
different functionality for each trigger if required.
There are two ways to do this:

Creating three triggers on the same table

Using conditional predicates and creating one trigger for that table. The three
conditional predicates are:


INSERTING

UPDATING

DELETING

The following code shows the trigger using the conditional predicates:
CREATE OR REPLACE TRIGGER trigLibMemSecurity
BEFORE INSERT OR UPDATE OR DELETE ON Member
BEGIN
IF USER <> CLERK THEN
IF INSERTING THEN
RAISE_APPLICATION_ERROR(-20012, You cannot
insert member details);
ELSIF UPDATING THEN
RAISE_APPLICATION_ERROR(-20013, Cannot update
member details);
ELSIF DELETING THEN
RAISE_APPLICATION_ERROR(-20014, Cannot delete
any details from the Member table);
END IF;
END IF;
END;
/

304
SQL Star International Ltd.

Trigger created.

Only the user CLERK has the access privilege to work with the Member table.
Writing the above trigger ensures this. The trigger prevents any user other than
CLERK to manipulate the member data. By using the conditional predicates each of
DML operations are given appropriate messages.
Creating Row Triggers
Row triggers are fired for every row of data that is affected by the triggering
statement. The syntax to create a row trigger is
CREATE [OR REPLACE] TRIGGER [user.]<trigger_name>
{timing}
{DML event1 [OR event2 OR event3. . .]}
ON <table_name>
[REFERENCING OLD AS old | NEW AS new]
FOR EACH ROW
[WHEN condition]
PL/SQL block;

Where,
[OR REPLACE] is to be used when you want to modify the definition of an already
existing trigger.
trigger_name is the name of the trigger.
timing specifies when you want the trigger to fire BEFORE, AFTER or INSTEAD OF.
DML events are any of the INSERT, UPDATE, DELETE or any combination of them.
table_name is the name of the table to which the trigger is associated.
[REFERENCING OLD AS <old> | NEW AS <new>] the old and new data are referred
to with some names for correlation. The default is old and new.
FOR EACH ROW specifies that it is a row trigger and should fire after or before every
row affected by the triggering statement.
[WHEN condition] is to specify the condition for the trigger action to take place. If
the condition is met the trigger code is executed else it is not.
PL/SQL block is the set of statements that defines what the trigger should do.
In row triggers, column values before and after data change are referenced by
prefixing them with the qualifiers OLD and NEW respectively.

305
SQL Star International Ltd.

The before and after values of columns in case of the different data operations are given
below.

When referencing the qualifiers in SQL and PL/SQL statements, prefix them with a
colon. However, when referencing the qualifiers in the WHEN condition, do not prefix
them with a colon.

BEFORE Row trigger


Here is an example to explain the functionality of BEFORE row trigger.
CREATE OR REPLACE TRIGGER trigChkAge
BEFORE INSERT OR UPDATE OF nAge
ON Member
FOR EACH ROW
BEGIN
IF (:NEW.nAge < 5)
THEN
RAISE_APPLICATION_ERROR (-20111, Member cannot be below
the

age of five);
END IF;
END;
/

Trigger created.

306
SQL Star International Ltd.

The above trigger will prevent a person from being admitted as a member of the
library if his age is below 5. Similarly, the trigger will not allow the age of a member
to be updated if it is below 5.
If you attempt to perform the following, the trigger raises an error:
UPDATE Member
SET nAge = 4
WHERE cMemberID = AVE119705;
UPDATE Member
*
ERROR at line 1:
ORA-20111: Member cannot be below the age of five
ORA-06512: at SCOTT.TRIGCHKAGE, line 4
ORA-04088: error during execution of trigger SCOTT.TRIGCHKAGE

BEFORE Row trigger Using the WHEN clause


If a WHEN clause is used with the row trigger, the trigger is fired only if a condition
is satisfied mentioned in the WHEN clause.
The example stated below shows how a trigger can be restricted for each row
before a DML operation is performed.
CREATE OR REPLACE TRIGGER trigChkCopies
BEFORE INSERT OR UPDATE OF nNoOfCopies ON Book
FOR EACH ROW
-- Restricting the row trigger
WHEN (NEW.cBranchID=01ADLNJ)
DECLARE
vMaxCopies Book.nNoOfCopies%TYPE;
vMinCopies Book.nNoOfCopies%TYPE;
BEGIN
SELECT MAX(nNoOfCopies), MIN(nNoOfCopies)
INTO vMaxCopies, vMinCopies
FROM Book

307
SQL Star International Ltd.

WHERE cBranchID = :NEW.cBranchID;


IF :NEW.nNoOfCopies <

vMinCopies OR :NEW.nNoOfCopies >


vMaxCopies

THEN
RAISE_APPLICATION_ERROR (-20002, Number of copies is
not within the expected range -||vMinCopies|| and
||vMaxCopies);
END IF;
END;
/
Trigger created.

This trigger trigchkcopies will prevent the number of copies of books stored in
branch 01ADLNJ to be updated below the minimum number of copies stored in the
branch or updated above the maximum number of copies stored in the branch. The
trigger should enforce the same restriction in case a new row is inserted into the
Book table for branch 01ADLNJ.
Testing the trigger trigChkCopies
You can check the maximum and minimum number of copies of books stored in
branch 01ADLNJ by querying the Book table before testing the trigger. The output
of the query would be as follows:

Now, to test the trigger,


Issue the following INSERT statement. The trigger fires and fails the INSERT
statement:
INSERT INTO BOOK
VALUES (PSH010012345,Tough Times Never Last,
But Tough People Do,Robert Schuller,02PSH,BW1265',16-OCT1989',01ADLNJ,1);

Displays the following error:


INSERT INTO BOOK
*

308
SQL Star International Ltd.

ERROR at line 1:
ORA-20002: Number of copies is not within the expected range -2
and 9
ORA-06512: at SCOTT.TRIGCHKCOPIES, line 11
ORA-04088: error during execution of trigger
SCOTT.TRIGCHKCOPIES

AFTER Row trigger


Example to show how the AFTER row trigger works:
For the purpose of this example, create a MemberAudit table, which should have
the following structure and data:

The trigger code is as follows:


CREATE OR REPLACE TRIGGER trigAuditMember
AFTER INSERT OR DELETE OR UPDATE OF vAddress, cPhone ON
Member
FOR EACH ROW
BEGIN
IF INSERTING THEN
UPDATE MemberAudit
SET Ins = Ins + 1

309
SQL Star International Ltd.

WHERE Table_Name=Member
AND Column_Name is NULL;
ELSIF DELETING THEN
UPDATE MemberAudit
SET Del = Del + 1
WHERE Table_Name=Member
AND Column_Name IS NULL;
ELSIF UPDATING (vAddress) THEN
UPDATE MemberAudit
SET Upd = Upd + 1
WHERE Table_Name=Member
AND Column_Name = vAddress;
ELSIF UPDATING (cPhone) THEN
UPDATE MemberAudit
SET Upd = Upd + 1
WHERE Table_Name=Member
AND Column_Name = cPhone;
ELSE
UPDATE MemberAudit
SET Upd = Upd + 1
WHERE Table_Name=Member
AND Column_Name IS NULL;
END IF;
END;
/
Trigger created.
The trigger trigAuditMember will update an audit table MemberAudit to keep a
count of the inserts, deletes and updates of vAddress and cPhone columns
performed by different users on the Member table.

310
SQL Star International Ltd.

To test the trigger trigAuditMember, execute the following UPDATE statement:


UPDATE Member
SET cPhone = 9822101779
WHERE cMemberID = CDB028504;
1 row updated.

The trigger trigAuditMember fires and populates the MemberAudit table as


follows:

AFTER Row trigger Using OLD and NEW identifiers


Create a trigger that will populate a MemberHistory table with the old and new
values of columns vAddress and cPhone, when users perform DMLs on the
Member table.
For the purpose of this example, create a MemberHistory table, which should have
the following structure:

Create the trigger as shown below:


CREATE OR REPLACE TRIGGER trigMemberHist
AFTER INSERT OR DELETE OR UPDATE OF vAddress, cPhone ON
Member

311
SQL Star International Ltd.

FOR EACH ROW


BEGIN
INSERT INTO MemberHistory
VALUES (USER,SYSDATE,:OLD.cMemberID,
:OLD.vAddress,:NEW.vAddress,
:OLD.cPhone,:NEW.cPhone);
END;
/
Trigger created.

To test the trigger trigMemberHist execute the following UPDATE statement:


UPDATE Member
SET cPhone = 981011724
WHERE cMemberID = CDB028504;
1 row updated.

The trigger trigMemberHist fires and populates the MemberHistory table as


follows:

Creating INSTEAD OF trigger


You can associate triggers to the views you are working with while performing DML
operations on them. It is to be remembered that data in complex views cannot be
manipulated. Hence, when you perform a DML transaction on these views, the
trigger associated with it will fire. Instead of the DML statements in the triggering
statement being executed, the code of the trigger will be executed. Such triggers are
referred to as INSTEAD OF triggers.

312
SQL Star International Ltd.

The syntax to create an INSTEAD OF trigger:

CREATE

[OR REPLACE] TRIGGER <trigger_name>


INSTEAD OF
{DML event1 [OR event2 OR event3]
ON <view_name>
[REFERENCING OLD AS old | NEW AS new]
[FOR EACH ROW]
PL/SQL block;

Where,
trigger_name is the name of the trigger
INSTEAD OF is the type of trigger used.
DML event1 are any of the DML statements of INSERT, UPDATE or DELETE.
view_name is the name of the view that the trigger is associated with
[REFERENCING OLD AS old | NEW AS new] specifies the correlating names for the
old and new data.
[FOR EACH ROW] is specified to designate the trigger to each row as is done in case
of row triggers.
PL/SQL block is the code of SQL and PL/SQL statements that defines the transactions
that the trigger should perform.

INSTEAD OF Trigger example


INSTEAD OF triggers are helpful in cases when the view has more than one base
table. If it is a multiple table view, the DML transactions could address different
transactions to different tables. You might have to perform an INSERT on one table
and an UPDATE on another. The INSTEAD OF trigger fires instead of the DML
transactions against the views. Thus the DML transactions are performed directly on
the base tables of the view.

To create an INSTEAD OF trigger as said above on a complex view,


you need to first do the following:
313
SQL Star International Ltd.

1. Alter the MemberFee table to include a column named nCountAge.


ALTER TABLE MemberFee
ADD nCountAge NUMBER (2);
Table altered.

2. Populate the nCountAge column with values taken from the Member
table as shown below.
UPDATE MemberFee
SET nCountAge = (SELECT COUNT (nAge)

FROM Member

WHERE Member.cGrade = MemberFee.cGrade);


4 rows updated.
Query the MemberFee table to view the values added to the
newly created column.

3. Create a complex view having Member and MemberFee as its underlying


tables.
CREATE OR REPLACE VIEW VwMemberGrade
AS
SELECT cMemberID, cFirstName, cLastName, vAddress, cArea,
cZipcode,
nAge, M.cGrade, nFeeAmount
FROM Member M, MemberFee MF
WHERE M.cGrade = MF.cGrade;
View created.

4. Create an INSTEAD OF trigger such that when a row is inserted into the
view vwMemberGrade, instead of inserting the rows into the view, the rows
are added to the Member and MemberFee table.
CREATE OR REPLACE TRIGGER trigVwMemberGrade
INSTEAD OF INSERT ON VwMemberGrade
FOR EACH ROW
BEGIN

314
SQL Star International Ltd.

INSERT INTO Member (cMemberID, cFirstName, cLastName,


vAddress, cArea, cZipcode, nAge, cGrade)
VALUES (:NEW.cMemberID, :NEW.cFirstName, :NEW.cLastName,
:NEW.vAddress, :NEW.cArea, :NEW.cZipcode, :NEW.nAge,
:NEW.cGrade);
UPDATE MemberFee
SET nCountAge = nCountAge + 1
WHERE cGrade = :NEW.cGrade;
END;
/
5. Insert a row in the view vwMemberGrade and verify whether the rows
got inserted in its underlying tables.
INSERT INTO vwMemberGrade(cMemberID,cFirstName,cLastName,
vAddress,cArea,cZipcode,nAge,cGrade)
VALUES(CPS010204,Phil,Spring,12-A101,Spring
Rd,Randolph,NJ09123',25,C);
1 row created.
On executing the INSERT statement, the INSTEAD OF trigger fires, causing
the respective data to be inserted into the Member and MemberFee table.
Verify the row inserted into the Member table:

SELECT * FROM MEMBER


WHERE cFirstName = Phil;

Verify the row updated in the MemberFee table:


SELECT * FROM MemberFee;

315
SQL Star International Ltd.

Trigger Modes
Mode of a trigger is the state a trigger is in, that is, whether it is enabled or disabled.
When a trigger is created it is enabled by default. When a trigger is enabled, the
Oracle server provides some resources to the trigger like, locking the tables for read
consistency, managing dependencies, and also performing a two-phased commit if
the trigger performs updates on remote tables.
If you do not want the trigger to fire, then you have to explicitly disable it using a
command.
You enable / disable a trigger with the following syntax:
ALTER TRIGGER <trigger_name> DISABLE | ENABLE;

If you want to enable / disable all the triggers on a particular table, then use the following
syntax:
ALTER TABLE <table_name> DISABLE | ENABLE ALL TRIGGERS

Triggers could be disabled:

To improve performance when loading a large amount of data.

When you dont want the data integrity checks to be performed when loading
a huge amount of data.

When the table associated to the trigger is not available due to various
reasons like no network connection, system hard disk crash, and offline data
file or offline tablespace.

Recompiling Triggers
Triggers are compiled like any other PL/SQL block.
When the trigger is compiled without any errors you will get a message, saying that
the trigger has been created.
In case a trigger is invalid, you need to explicitly recompile the trigger. This is done
using the ALTER TRIGGER statement. The ALTER TRIGGER statement with the
COMPILE option compiles the trigger irrespective of the fact that the trigger is valid
or invalid. The syntax for the same is:
ALTER TRIGGER <trigger_name> COMPILE

316
SQL Star International Ltd.

You can recompile the trigger trigvwMemberGrade as follows:


ALTER TRIGGER trigvwMemberGrade COMPILE;
Trigger altered.
Dropping a Trigger
You can delete a trigger from the database, using the DROP TRIGGER statement.
The syntax is:
DROP TRIGGER <trigger_name>
For example, you can drop the trigger trigvwMemberGrade as follows:
DROP TRIGGER trigvwMemberGrade;
Trigger dropped.

Viewing Trigger Information


To access the information about the triggers which are created in your schema, following Data
Dictionary views can be queried.
1. USER_OBJECTS: It gives us information regarding the status of the trigger and when it was
created.
2. The USER_ERRORS: It stores the information regarding the compilation errors of
unsuccessful compiled triggers.
3. The USER_TRIGGERS: This gives information details of components of the trigger such as
name, type, triggering event, the table on which the trigger is created and the body of the
trigger.

317
SQL Star International Ltd.

Summary
In this chapter, you have learnt that:

 A block of code that gets executed implicitly based on an event is called a


trigger.
 System triggers on a database fire for each event for all the users, whereas,
system triggers on a schema fire for each event for a specific user.

 You need to have certain privileges (Like CREATE TRIGGER, CREATE ANY
TRIGGER, ALTER TRIGGER etc) to create and modify a trigger.

 The components of a trigger include:


Trigger timing (Before or After an event)
Trigger Event (INSERT,UPDATE or DELETE)
Trigger Type (Row or Statement)
Trigger Code (Actual task to be performed when the trigger is fired)

 When a DML operation is performed on a complex view, the trigger associated


is fired. Instead of the DML statement, the code of the trigger is executed.
Such a trigger is called INSTEAD OF Trigger.

 Triggers can be enabled or disabled using ENABLE and DISABLE commands and
can be modified as (ALTER, DROP) as is done to tables.

 USER_TRIGGERS data dictionary view gives information about the components


of a trigger.

318
SQL Star International Ltd.

Lab Exercises
1.
Create a procedure called prCheckDML that prevents any DML statement
from being executed outside normal office hours, Monday through Friday, and
returns a message saying You are allowed to make changes only during office
hours.
2.
Create a statement trigger trigSecureEmp on the Employees table that
invokes the procedure prCheckDML.
3.
Create a row trigger trigRestrictSal, which will allow a new row to be
inserted into the Employees table or salary to be updated only if the new
department ID happens to be 20 or 60, and new salary happens to be more than
$12,000. Your trigger code should display an appropriate message if the insert or
update on Employees table fails. For instance, on attempting to update the salary of
employee 101 (who does not belong to departments 20 or 60) to $14,000, the
trigger trigRestrictSal should fire, and the following message must be displayed:
UPDATE Employees
*
ERROR at line 1:
ORA-20001: You are not eligible to earn this amount
ORA-06512: at HR.TRIGRESTRICTSAL, line 4
ORA-04088: error during execution of trigger HR.TRIGRESTRICTSAL
[Note: For the purpose of question 3, you need to create a table AuditEmp, which
tracks a users activity on the Employees table.]
The structure of AuditEmp table should be as follows:

319
SQL Star International Ltd.

4.
Create a trigger called trigAuditEmp, which will record the values of columns
of Employees table (both before and after changes made) to the table AuditEmp.
Insert a new row into the Employees table, and update the salary and department
ID of employee 104. Query the AuditEmp table and view the result.
5.

Disable all the triggers created.

320
SQL Star International Ltd.

Chapter 15

Creating Advanced Database Triggers

Creating Triggers on DDL Events


Database Event Triggers
CALL Statement
Managing Triggers
Triggers vs. Stored Procedures
Mutating and Constraining Tables
Enhancing Server Functionality

321
SQL Star International Ltd.

Objectives
In this chapter, you will be able to:


Create Advanced Database triggers

Optimize trigger functionality

Understand Mutating Table

Create triggers to enhance server functionality

322
SQL Star International Ltd.

Creating Advanced Database Triggers


You have seen the effect of triggers that fire due to DML statements. Apart from this, the
use of triggers can be extended to DDL and database events as well.
DDL Event Triggers

Triggers that fire due to DDL transactions are called DDL event triggers. You create
these triggers when using the CREATE, ALTER and DROP commands. One of the
reasons for creating these triggers could be the security of the database to prevent illegal
manipulation of database objects.

A DDL event trigger can be associated with all database objects.


The syntax to create this trigger is:
CREATE [OR REPLACE] TRIGGER <trigger_name>
timing [ON SCHEMA]
[ddl_event1 [OR ddl_event2 OR ddl_event] ]
ON {DATABASE

SCHEMA}

PL/SQL block;
Where,
ddl_event1 could be any of the statements CREATE, ALTER or DROP as and when
they are implemented on any of the database objects.
ON SCHEMA clause is specified if you want to create the trigger associated to some data
dictionary objects in your schema.
The rest of the syntax remains the same as that for creating DML triggers. An example
for this trigger is:
CREATE OR REPLACE TRIGGER trigPreventDrop
BEFORE DROP ON SCOTT.SCHEMA
BEGIN
IF DICTIONARY_OBJ_OWNER = SCOTT
AND DICTIONARY_OBJ_NAME LIKE MEM%

323
SQL Star International Ltd.

AND DICTIONARY_OBJ_TYPE = TABLE


THEN
RAISE_APPLICATION_ERROR (-20001, YOU ARE NOT
PERMITTED TO DROP THE TABLE);
END IF;
END;
/
Trigger created.

To test this trigger trigPreventDrop, first create a table Members, which is a duplicate
of the Member table:
CREATE TABLE MEMBERS
AS SELECT *
FROM MEMBER;
Table created.

Now, when you attempt to drop this newly created table, the DDL trigger prevents the
operation from succeeding and displays a message conveying the same:
DROP TABLE MEMBERS;
DROP TABLE SCOTT.MEMBERS
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: YOU ARE NOT PERMITTED TO DROP THE TABLE
ORA-06512: at line 6

Database Event Triggers


Database events are those that occur for the database as a whole and all the objects in the
schema may be affected by those events. Events like server shutdown and start up and
server error are all system or database events.
The syntax to create a database trigger is:
CREATE [OR REPLACE] TRIGGER <trigger_name>
timing [ON SCHEMA]

324
SQL Star International Ltd.

[database_event1 [OR database_event2 OR database_event] ]


ON {DATABASE|SCHEMA}
PL/SQL block;

The database event triggers include:

AFTER SERVERERROR trigger that will fire when an error is logged in the
database

AFTER LOGON, which fires as soon as you log on to the database

BEFORE LOGOFF trigger, which fires when you try to log off from the
database

AFTER STARTUP trigger, which fires when you open the database

BEFORE SHUTDOWN trigger that fires whenever you shutdown the database

Log On and Log Off Triggers

These triggers are created to track the number of times a user logs on and off from the
database. This trigger could be specified for a database or a schema. If for the former,
then the data you retrieve is the count of number of users accessing the database. If the
trigger is on a schema, then it keeps track of the number of time a users logs on and off
from his schema.
The following codes show you the use of log on and the log off triggers:
Note: For the following two examples, create a table LogTable, which should have the
following structure:

Now, create the AFTER LOGON trigger as follows:


CREATE OR REPLACE TRIGGER trigToLogon
AFTER LOGON ON SCOTT.SCHEMA
BEGIN
INSERT INTO LogTable (Usr_Name, Log_Dt, Operation)
VALUES (USER, SYSDATE, logging on);
END;
/
Trigger created.

325
SQL Star International Ltd.

To test the trigger, log off the database and log in again.
Now, query the LogTable. The table displays the following result:

Create the BEFORE LOGOFF trigger as follows:


CREATE OR REPLACE TRIGGER trigToLogoff
BEFORE LOGOFF ON scott.schema
BEGIN
INSERT INTO LogTable (Usr_Name, Log_Dt, Operation)
VALUES (USER, SYSDATE, logging off);
END;
/
Trigger created.

To test the trigger, log off the database and log in again.
Now, query the LogTable. The table displays the following result:

The CALL Statement

You have seen how triggers are created for DML, DDL and database events. There are a
few more operations that can be done with triggers. They include:
 Using the CALL statement in triggers
You can call any existing stored procedure instead of coding a PL/SQL block for the
trigger to perform some actions. The procedure could be implemented on any
software platform like PL/SQL, Java or C.
You reference trigger attributes using this statement with the :NEW and :OLD
parameters.
An example for this trigger is:
CREATE OR REPLACE TRIGGER trigChkFine
AFTER UPDATE OF dActualReturnDt ON Transaction
326
SQL Star International Ltd.

FOR EACH ROW


WHEN (NEW.dActualReturnDt > OLD.dReturnDt)
BEGIN
Call to stored function fnFineCal
CALL fnFineCal(:NEW.dActualReturnDt);
END;
/
Trigger created.
 Referencing system attributes in triggers.
With the CALL statement you can also refer to the system attributes by calling some
procedure that uses the system attributes as parameters.
An example of this is:
CREATE OR REPLACE TRIGGER trigSchemaObj
AFTER CREATE ON CLERK.SCHEMA
BEGIN
CALL prInsertAudit(SYS.Dictionary_Obj_Name);
END;
/
Trigger created.
The system parameters that you can refer to are:

SYSEVENT: names the event that fires the trigger

INSTANCE_NUM: displays the number of the instance

DATABASE_NAME: displays the name of the database

SERVER_ERROR: displays the error number of the error at the specified


position in the stack.

IS_SERVERERROR: returns a BOOLEAN value stating TRUE if there is an error


and FALSE if there is no error.

LOGIN_USER: returns the name of the user who has logged in.

DICTIONARY_OBJ_NAME: returns the name of the object

DICTIONARY_OBJ_TYPE : displays the type of database object that is being


accessed.

DICTIONARY_OBJ_OWNER: displays the objects owners mails.

327
SQL Star International Ltd.

DES_ENCRYPTED_PASSWORD: captures the password when a user account is


being created or altered.

Managing Triggers
When creating triggers, you must manage them in a way to optimize their functionality.
Managing Trigger Development

The three types of DML triggering statements are INSERT, UPDATE and DELETE.
Based on when they fire, triggers are classified as BEFORE and AFTER. In addition,
they are also classified as row and statement level triggers. So taking a combinations of
these types, you can create 12 types of triggers for one table in an Oracle database.
There is no need to create 12 triggers for all tables, as there is in-built functionality to
handle various situations. You could have triggers on your table, which in turn may fire
other triggers. However, these should be designed carefully so that there is no excess
trigger processing that happens.
For example you can create an INSERT trigger, Trg1 on table Tab1. When this trigger
fires, it inserts a row into table Tab2. An insert into Tab2 causes another INSERT
trigger, Trg2, against it to fire. Trg2 in turn inserts a row into Tab1. This is a badly
designed set of triggers as it causes a loop of executions to form and as a result no
insertions happen.
When a trigger causes another trigger to fire and that in turn causes a third one to fire, it
is said to be cascading of triggers. A maximum of 32 cascading triggers is allowed to
fire. Setting the MAX_OPEN_CURSORS parameter when starting up the database can
restrict this figure.
Some restrictions when working with triggers are:

You can use a trigger to insert data into a column of LONG or LONG RAW
datatype, but you cannot make use of these datatypes to declare variables in
a trigger. To extract data from a column of this datatype, you have to convert
your trigger column to a VARCHAR2 datatype and then perform the select.
NEW and OLD references also cannot be made.

If a cascading trigger on a table called from a package with a function or


procedure in turn calls the package then the updates will clash, the trigger
will re-fire before the problem is solved.

328
SQL Star International Ltd.

Triggers do not process rows according to an order you want. If you want to
specify the order of processing rows, then the trigger should include an
ORDER BY clause.

You need to explicitly handle exceptions in triggers. Triggers are the same as
any other PL/SQL block so they can also include an exception in their body.

Mutating and Constraining Tables

The two basic rules that apply when using triggers are that:

Your trigger should not change data in the primary key, foreign key or unique
key columns of a constraining table.

Your trigger should not read data from a mutating table.

You can understand the rules better, by understanding what mutating and constraining
tables are.
A constraining table is one that a trigger needs to access directly for DML statement or
indirectly when the SQL statement requires a foreign key check.
A mutating table is one that is either being modified by a DML statement, or needs to be
modified due to a DELETE CASCADE referential integrity action.

Tables mutate when an UPDATE, DELETE or INSERT is happening on them directly,


and constrain when reference has to be made to them by SQL statements or referential
integrity constraints in order to update some other table.
A row trigger can read or write data from a table to which it is attached, only through
OLD and NEW references.
By rule, a trigger is bound by read consistencies and referential integrity. For instance, an
INSERT trigger tries to insert a row with one foreign key value into a table. The trigger
cannot insert a value into the referencing table to maintain the foreign key constraint.

329
SQL Star International Ltd.

Triggers Vs Stored Procedures


Database triggers, as you have seen are the same as any other PL/SQL block or stored
procedure. They are created in the same way and the data dictionary contains the source
code and pcode for both. But there are a few basic differences between the two. The
following list explains the differences:

Database triggers are invoked implicitly and stored procedures are explicitly
invoked.

COMMIT, SAVEPOINT and ROLLBACK are not allowed in triggers but are
allowed in stored procedures.

Enhancing Server Functionality


Finally you need to know how trigger functionality enhances the working of the Oracle
server. Database triggers are to be created to enhance functionality, which cannot
otherwise be performed by the Oracle server.
The features that can be enhanced by using triggers are:

Security

Auditing

Non-declarative integrity


Data integrity

Referential integrity

Table replication

Derived data

330
SQL Star International Ltd.

Implementing Security with Triggers


The Oracle server implements security by creating schemas and roles and using the
GRANT and REVOKE commands. Triggers can be used to enhance these security
features by customizing some of them.

Some security issues are that:

The server verifies the user name when a user connects to a database, but
with a trigger you can set privileges not only on the user, but also on
database values. These values could be the day of the week or time among
others.

Oracle server can determine who can access tables, views, synonyms and
sequences. A trigger can be created to specifically restrict access to tables
only.

Oracle server specifies privileges for data manipulation and data definition
changes. A trigger can be created to control data manipulations only.

Controlling security within the server


GRANT SELECT, INSERT, UPDATE, DELETE
ON Member
TO rlMember; -- role created
GRANT rlMember TO CLERK; -- role granted to user

Controlling security with a trigger


CREATE OR REPLACE TRIGGER trigLibDBSecurity
BEFORE INSERT ON Transaction
BEGIN
IF (TO_CHAR (SYSDATE, DY)=SUN OR
HH24)

(TO_CHAR (SYSDATE,

NOT BETWEEN 10 AND 19)) THEN


RAISE_APPLICATION_ERROR (-20022, Cannot record

book issue

transactions

on

Sundays

and

beyond

library hours);
END IF;
END;
/

331
SQL Star International Ltd.

Trigger created.

Auditing
Within the server, you can monitor and gather data regarding specific database activities.
With triggers you can audit actual data values.
The auditing activities performed by the Oracle server and triggers include:

Oracle server audits data retrieval, manipulation and definition statements.


Triggers audit data manipulation statements only.

Oracle server writes the audit trail to the centralized audit table (that is, a
data dictionary table or an Operating System file), while a trigger can write it
to a user-defined audit table.

Oracle server generates an audit report for every one session or access
attempt, while a trigger can generate audit reports for every row.

Oracle server captures the successful and unsuccessful attempts, while a


trigger captures only successful attempts.

Auditing performed by the server


AUDIT INSERT, UPDATE, DELETE
ON Transaction
BY ACCESS
WHENEVER SUCCESSFUL;
Audit succeeded.

Auditing using a trigger


CREATE OR REPLACE TRIGGER trigAuditTransactions
AFTER INSERT OR UPDATE OR DELETE ON Transaction
FOR EACH ROW
BEGIN
INSERT INTO TransactionAudit (DBUser, Dt, OldMemID,
NewMemID, OldBkId,NewBkID, OldIssDt, NewIssDt, OldReturnDt,
NewReturnDt, OldActualRetDt,NewActualRetDt, OldFine,
NewFine)
VALUES (USER, SYSDATE, :OLD.cMemberID, :NEW.cMemberID,
cBookID,:NEW.cBookID, :OLD.dIssueDt, :NEW.dIssueDt,
:OLD.dReturnDt, :NEW.dReturnDt,

332
SQL Star International Ltd.

:OLD.dActualReturnDt, :NEW.dActualReturnDt, :OLD.nFine,


:NEW.nFine);
END;
/
Trigger created.

Enforcing Non-declarative Integrity


The Oracle server enforces standard data integrity and referential integrity rules.
The server implements data integrity by:

Maintaining constant default values

Enforcing static constraints

Dynamically enabling and disabling

With a trigger, you can provide variable default values, enforce dynamic constraints,
enable and disable dynamically, and incorporate declarative constraints within the
definition of a table.
Referential integrity is implemented to avoid data inconsistency. The important issues
are:

Restricting updates and deletes

Managing cascading and deletes

Enabling and disabling dynamically

When implementing referential integrity with triggers, some actions not possible by
declarative constraints are taken care of:

Cascading triggers

Setting default and NULL values during updates and inserts

Enforcing referential integrity in a distributed environment

Enabling and disabling dynamically

Data integrity enforced within the server


ALTER TABLE Member
ADD CONSTRAINT ChkFee CHECK (nFeeAmount>5);
Table altered.

Data integrity enforced with a trigger

333
SQL Star International Ltd.

CREATE OR REPLACE TRIGGER trigChkFee


BEFORE UPDATE OF nFeeAmount ON MemberFee
FOR EACH ROW
WHEN (NEW.nFeeAmount< OLD.nFeeAmount)
BEGIN
RAISE_APPLICATION_ERROR (-20111, cannot decrease the fee
amount);
END;
/
Trigger created.

Testing trigger trigChkFee


When you issue the following UPDATE statement, the trigger fires and fails the
UPDATE:
UPDATE MemberFee
SET nFeeAmount = 10
WHERE cGrade = B;
UPDATE MemberFee
*
ERROR at line 1:
ORA-20111: cannot decrease the fee amount
ORA-06512: at SCOTT.TRIGCHKFEE, line 2
ORA-04088: error during execution of trigger SCOTT.TRIGCHKFEE

Table Replication
The Oracle server replicates a table by creating snapshots. A snapshot is a local copy of a
table that originates from one or more remote master tables. You can read data from a
snapshot but not perform DML statements on it. Data in the snapshot is periodically
refreshed so that it shows the latest data as is in the master. Some activities you can
perform with snapshots are:

Copy tables asynchronously

Base the snapshots on multiple tables

334
SQL Star International Ltd.

Improve the performance of data manipulation on the master, especially in


case of a network failure

Replicating tables with triggers includes:

Copying tables synchronously

Basing the tables on single master tables

Read and write from and to the replica tables

Destroys the performance of data manipulations on the master especially in


case of a network failure

Copies of tables are maintained automatically with snapshots on remote


nodes

Table replication within the server


CREATE SNAPSHOT BookCopy
AS SELECT * FROM Book;

Table replication with a trigger


CREATE OR REPLACE TRIGGER trigBkCopy
BEFORE INSERT OR UPDATE ON Book
FOR EACH ROW
WHEN (NEW.cBranchID=04RANNJ)
BEGIN
-- replicating the book data of branch 04RANNJ to table
RandolphBook
IF INSERTING THEN
INSERT INTO RandolphBook
VALUES(:NEW.cBookID,:NEW.cBookName,:NEW.cAuthorName,
:NEW.cCategoryID,:NEW.cPublisherID,
:NEW.dPublishedYr,
:NEW.cBranchID,:NEW.nNoOfCopies,n);
ELSE
UPDATE RandolphBook
SET cBookID = :NEW.cBookID
WHERE cBookID = :NEW.cBookID;
END IF;

335
SQL Star International Ltd.

END;

Computing Derived Values

The Oracle server allows you to compute derived values in a batch by:

Computing the derived columns asynchronously, at user defined intervals

Storing the values within the tables

Modifying the data in one step and calculating the derived value in the next

With triggers, you can continuously compute derived values by:

Computing the derived columns synchronously

Storing the values within the tables or package variables

Modifying the data and performing the calculations are done in one step

Computing derived data within the server


UPDATE MemberFee
SET nCountAge = (SELECT COUNT (nAge)
FROM Member
WHERE Member.cGrade =
MemberFee.cGrade);

Computing derived data with a trigger


CREATE OR REPLACE TRIGGER trigIncCopies
AFTER INSERT OR DELETE OR UPDATE OF nNoOfCopies ON Book
FOR EACH ROW
BEGIN
IF DELETING THEN
-- invoking a procedure prIncCopies
PrIncCopies (:OLD.cBookID,1 *
:OLD.nNoOfCopies);

336
SQL Star International Ltd.

ELSIF UPDATING THEN


PrIncCopies (:NEW.cBookID,
:NEW.nNoOfCopies - :OLD.nNoOfCopies);
ELSE
PrIncCopies (:NEW.cBookID, :NEW.nNoOfCopies);
END IF;
END;
/
Trigger created.

Procedure code
CREATE OR REPLACE PROCEDURE prIncCopies
(vBookId IN Book.cBookID%TYPE, vCopies IN Book.nNoOfCopies%TYPE)
IS
BEGIN
UPDATE Book
SET nNoOfCopies = nNoOfCopies + vCopies
WHERE cBookID = vBookId;
END;
/
Procedure created.

Summary

337
SQL Star International Ltd.

In this chapter, you have learnt that:


 DDL Event triggers fire due to DDL events.
 Database Event triggers fire on database events like Server Startup, Shutdown,
Server Error etc.
 LOGON and LOGOFF triggers track the number of times a user logs on and off
respectively from the database.
 A procedure can be called from the trigger using the CALL statement.
 Triggers enhance Security, Auditing, Data Integrity, Table Replication and
Derived- data features.

338
SQL Star International Ltd.

Lab Exercises
1.

Write a trigger on your schema to track when you have logged in and logged out.

2.
What happens if a procedure that updates a column of table X is called in a
database trigger of the same table ?

339
SQL Star International Ltd.

340
SQL Star International Ltd.

Вам также может понравиться