Sei sulla pagina 1di 117

PL/SQL Practices

About PL/SQL Execution

Every time an anonymous PL/SQL block is executed, the code is sent to the PL/SQL engine on
the server, where it is compiled. A named PL/SQL block is compiled only at the time of its
creation, or if it has been changed. The compilation process includes syntax checking, binding,
and p-code generation.
Syntax checking involves checking PL/SQL code for syntax or compilation errors. A syntax error
occurs when a statement does not exactly correspond to the syntax of the programming
language. Errors such as a misspelled keyword, a missing semicolon at the end of the
statement, or an undeclared variable are examples of syntax errors.
After the programmer corrects syntax errors, the compiler can assign a storage address to
program variables that are used to hold data for Oracle. This process is called binding. It allows
Oracle to reference storage addresses when the program is run. At the same time, the compiler
checks references to the stored objects such as table names or column names in the SELECT
statement, or a call to a named PL/SQL block.
Next, p-code is generated for the PL/SQL block. P-code is a list of instructions to the PL/SQL
engine. For named blocks, p-code is stored in the database, and it is used the next time the
program is executed. As soon as the process of compilation has completed successfully, the
status of a named PL/SQL block is set to VALID, and it is also stored in the database. If the
compilation process was unsuccessful, the status of a named PL/SQL block is set to INVALID.

DID YOU KNOW?


Successful compilation of the named PL/SQL block does not guarantee successful execution of
this block in the future. At the time of execution, if any one of the stored objects referenced by
the block is not present in the database or is inaccessible to the block, execution fails. At such
time, the status of the named PL/SQL block is changed to INVALID.

PL / SQL Block Structure

DECLARE (Optional)

Declaration statements

Variables, constants, cursors, user-defined exceptions

BEGIN (Mandatory)

Executable statements

- SQL Statements

- PL/SQL Statements

EXCEPTION (Optional)

Exception-handling statements

Actions to perform when errors and abnormal conditions occur

END ; (Mandatory)

1
PL/SQL Practices

Example :

Displaying PL/SQL output :


Another change with PL/SQL from SQL is that the database does return the output. PL/SQL code
normally will change data , insert values and so forth inside the database. It will not normally display the
results back to the user. To do this we use a procedure called dbms_output.put_line to place the results
in the buffer that sql*plus will retrieve and display. SQL*PLUS must be told to retrieve data from this
buffer in order to display the results. The SQL*PLUS command ' set serveroutput on' causes sql*plus to
retrieve and display the buffer.
The PL/SQL DBMS_OUTPUT package has a PUT_LINE procedure to allow you to write data to flat file or
to direct your PL/SQL output to a screen.
When using DBMS_OUTPUT inside sql*plus you may need to use the "SET SERVEROUTPUT ON 10000"
command ( where 10000 is the display arraysize argument) to see the pl/sql output directly with
dbms_output.put_line. This package DBMS_OUTPUT was introduced in oracle 7. PL/SQL functions like
DBMS_OUTPUT.PUT_LINE procedure will also trims off white space. The leading and training spaces will
be removed when placed on the buffer. Here is an example of DBMS_OUTPUT.PUT_LINE used to
removed spaces :
dbms_output.put_line (? This Has Spaces. ?). This example would remove spaces before and after
the spaces "This Has Spaces."

PROCEDURE DBMS_OUTPUT.DISABLE
PROCEDURE DBMS_OUTPUT.ENABLE

[ Note : In Oracle SQL Developer 11g from View => Dbms Output , and establish a connection. ]
DECLARATION SECTION
The declaration section is the first section of the PL/SQL block. It contains definitions of PL/SQL
identifiers such as variables, constants, cursors, and so on. PL/SQL identifiers are covered in
detail throughout this book.

FOR EXAMPLE
DECLARE
v_first_name VARCHAR2(35);
v_last_name VARCHAR2(35);
c_counter CONSTANT NUMBER := 0;

This example shows a declaration section of an anonymous PL/SQL block. It begins with the
keyword DECLARE and contains two variable declarations and one constant declaration. The
names of the variables, v_first_name and v_last_name, are followed by their datatypes
and sizes. The name of the constant, c_counter, is followed by the keyword CONSTANT, its
datatype, and a value assigned to it. Notice that a semicolon terminates each declaration.

EXECUTABLE SECTION
The executable section is the next section of the PL/SQL block. This section contains
executable statements that allow you to manipulate the variables that have been declared in the
declaration section.
FOR EXAMPLE
BEGIN

2
PL/SQL Practices

SELECT first_name, last_name


INTO v_first_name, v_last_name
FROM student
WHERE student_id = 123;
DBMS_OUTPUT.PUT_LINE ('Student name: '||v_first_name||' '||
v_last_name);
END ;

This example shows the executable section of the PL/SQL block. It begins with the keyword
BEGIN and contains a SELECT INTO statement from the STUDENT table. The first and last
names for student ID 123 are selected into two variables: v_first_name and v_last_name.
(Chapter 3, “SQL in PL/SQL,” contains a detailed explanation of the SELECT INTO statement.)
Then the values of the variables, v_first_name and v_last_name, are displayed on the
screen with the help of the DBMS_OUTPUT.PUT_LINE statement. This statement is covered in
greater detail later in this chapter. The end of the executable section of this block is marked by
the keyword END.

Note :
The executable section of any PL/SQL block always begins with the keyword BEGIN and ends
with the keyword END.

EXCEPTION-HANDLING SECTION
The exception-handling section is the last section of the PL/SQL block. This section contains
statements that are executed when a runtime error occurs within the block. Runtime errors
occur while the program is running and cannot be detected by the PL/SQL compiler. When a
runtime error occurs, control is passed to the exception-handling section of the block. The error
is then evaluated, and a specific exception is raised or executed. This is best illustrated by the
following example.

FOR EXAMPLE
BEGIN
SELECT first_name, last_name
INTO v_first_name, v_last_name
FROM student
WHERE student_id = 123;
DBMS_OUTPUT.PUT_LINE ('Student name: '||v_first_name||' '||
v_last_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('There is no student with '||
'student id 123');
END;

This example shows the exception-handling section of the PL/SQL block. It begins with the
keyword EXCEPTION. The WHEN clause evaluates which exception must be raised. In this
example, there is only one exception, called NO_DATA_FOUND, and it is raised when the
SELECT INTO statement does not return any rows. If there is no record for student ID 123 in
the STUDENT table, control is passed to the exception-handling section, and the DBMS_
OUTPUT.PUT_LINE statement is executed. Chapters 8, 9, and 10 contain more detailed
explanations of the exception-handling section.

3
PL/SQL Practices

DECLARE
V_LINE VARCHAR2(30);
BEGIN
V_LINE : = ' Hello World';
DBMS_OUTPUT.PUT_LINE (V_LINE);
END ;
/
PL/SQL procedure successfully completed.
SET SERVEROUTPUT ON;
DECLARE
V_LINE VARCHAR2(30);
BEGIN
V_LINE := 'Hello World';
DBMS_OUTPUT.PUT_LINE (V_LINE);
END ;
/
Hello World
PL/SQL procedure successfully completed.

The first time the script is run , the result was just a notice that the pl/sql script completed successfully .
Once we set serveroutput on and again run the script , the results are shown.

As discussed earlier , this is an anonymous block of PL/SQL code. The script is sent to the database ,
complied and executed, then sql*plus retrieves the results. The script is stored in the sql*plus buffer
and can be rerun by executing the forward slash (/).
SQL> /
Hello World
PL/SQL procedure successfully completed.

The script is not stored in the database (like a stored or named procedure). It must be resent to the
database and compiled each time it is executed.
As with SQL statements, SQL*Plus variables can be used to make the PL/SQL script dynamic. Just as with
a SQL statement , the variables are local to SQL*Plus and are substituted before the code is sent to the
database.

SET SERVEROUTPUT ON
DECLARE
V_LINE VARCHAR2(30);
BEGIN
V_LINE := 'Hello &name';
DBMS_OUTPUT.PUT_LINE(V_LINE);
END;
/

Enter value for name : Roshan


old v_line := 'Hello &name';

4
PL/SQL Practices

new v_line :='Hello Roshan';


Hello Roshan

The SQL*Plus accept command is a more flexible method of embedding dynamic data in the script.
accept_v_string prompt "Enter Your First Name: "
Enter Your First Name: Thomas

DECLARE
V_LINE VARCHAR2(30):= '&v_string';
BEGIN
V_LINE :='Hello '|| V_LINE;
DBMS_OUTPUT.PUT_LINE (V_LINE);
END;
/

Old 2: v_line varchar2(30) :='&v_string';


New 2: v_line varchar2(30):='Thomas';
Hello Thomas
PL/SQL procedure successfully completed.
Let' s look at this script a little closer. The first line is the SQL*Plus accept command to get the SQL*Plus
variable v_string. This line must be executed alone, not part of the PL/SQL block. At the prompt the
name Thomas was entered. Now the script is run but it is slightly modified from previous example.
DECLARE
V_LINE VARCHAR2(30) :='&V_STRING';
The variable V_LINE is declared as a varchar2(30) and is given a default value that equals V_STRING. The
PL/SQL assignment operator (:=) is used to assign the value. Hence V_LINE is a bucket that gets assigned
the string 'Thomas'. A developer would read an assignment the string 'Thomas'. A developer would read
an assignment statement in English as 'V_LINE gets V_STRING ' to indicate the assignment. Let's example
a more complex assignment statement.
V_LINE := 'Hello '||V_LINE ;
Here concatenate (||) operator to append 'Hello ' to the front of V_LINE now contains the string 'Hello
Thomas' . If you set SET VERIFY ON , then you can see
old 2 : V_LINE VARCHAR2(30) := &V_STRING ;
new 2: V_LINE VARCHAR2(30) :='Thomas';
These two lines demonstrate sql*plus verify function showing us what is substituted before the code is
sent to the database for execution. This information can be switched on / off with the verify command :
SET VERIFY ON
SET VERIFY OFF

PL/SQL Variable Declaration and Conversion


In the previous example a variable V_LINE was defined. All variable are defined in the declaration
section of the block. Variables are defined in the form :
variable_name datatype : = defaultvalue ;
Below are examples of variables. Variables can be defined as any valid data type to include user defined
data types and records.
DECLARE
V_STR1 VARCHAR2(80);
V_STR2 VARCHAR2(30);

5
PL/SQL Practices

D_TODAY DATE;
N_SALES NUMBER;
N_ORDER NUMBER(8);
BEGIN
A Constant is defined the same way a variable is with the key word constant.
C_STANDARD constant number :=90;
Notice that a constant must be assigned a value and the above statement does four things:
*Names the variable C_standard
*Defines C_standard as a constant
*Defines C_standard as a numeric datatype
*Assigns a value of 90 to C_standard
With PL/SQL constants, note that a constant value can't be changed unless it is redefined in a
subsequent block.
In the example above out two variable are defined as numbers, and we are now ready to see how to
include the precision and scale for a number. As in SQL, PL/SQL supports mathematical operations and
has a large library of mathematical functions, covering everything from advanced multivariate statistics
to Newtonian Calculus. PL/SQL also supports single - row functions to convert numbers to characters
and characters to numbers.

Syntax :
DECLARE
V_VARIABLE VARCHAR2(5) ;
BEGIN
SELECT COLUMN_NAME
INTO V_ARIABLE
FROM TABLE_NAME;
EXCEPTION
WHEN EXCEPTION_NAME
THEN RETURN 'return_statemnt'
END ;

 Place a semicolon (;) at the end of a SQL Statement or PL/SQL control statement.
 When the block is executed successfully, without unhandled errors or compile errors, the
massage output should be as follows :
PL/SQL procedure successfully completed.
 Keywords DECLARE, BEGIN and EXCEPTION are not followed by a semicolon (;).
 END keyword always require a semicolon (;) to terminate the statement.
 PL/SQL error is called an exception.

Anonymous Block:
[DECLARE]
BEGIN
----------Statements ;
[EXCEPTION]
END ;

6
PL/SQL Practices

What are Anonymous Blocks ? Anonymous blocks are unnamed blocks. They are declared at the
point in an application where they are to be executed and are passed to the PL/SQL engine for
execution at run time.
What are Subprograms ? A Subprogram(s) are named PL/SQL blocks that can accept parameters
and can be invoked.

What are Variables ? A Variable(s) are used for temporary storage of data.
Use of Variables :
 Temporary Storage of Data.
 Manipulation of Stored Values.
 Reusability.
 They can be easily maintained.
Types of Variables :
- Scalar Variables
- Composite Variables.
- Reference Variables.
- LOB (Large Objects).
- Non PL/SQL Variables : Bind and Host Variables.

Scalar Variables Data Type: It holds a single value and has no internal components. It can be
classified into four categories : Number, Character, Boolean and Date. Character and Number data
type have subtypes that associate a base type to a constraint. For example , INTEGER and POSITIVE
are subtype of the NUMBER base type. Base Scalar Data Types :
- Char(maximum_length)
-Varchar2(maximum_length)
-Long
-Long Raw
-Number[(precision, scale)]
(12345677 . 123)
-------------- ------ = Length 10
Precision Scale
10 3
NUMBER(10,3)
-Binary_Integer
-PLS_INTEGER
-BOOLEAN
-Date
-Timestamp
-Timestamp with time zone, Timestamp with local time zone, Interval year to month, Interval day to
second.
Composite Variables Data Type: Such as records, allow groups of fields to be defined and
manipulated in PL/SQL Block.
Reference Variables Data Type: It holds values which are called pointers, that designate other
program items. These data types are beyond this course.
LOB (Large Objects) Data Types: It hold values, called locators, that specify the location of large
objects (such as graphic images) that are used out of line.
There are also a Boolean type of Variables that stores one of three possible values used for logical
calculation TRUE , FALSE or NULL.
7
PL/SQL Practices

 TRUE or FALSE represents a Boolean Value.


 25-Feb-2018 represents a DATE type .
 A photograph represents a BLOB.
 A text of a speech represents a LONG.
 2654411.07 represents a NUMBER data type with precision and scale.
 A movie represents a BFILE.
 The City Name , Atlanta, represents a VARCHAR2.

Examples of Declaring Variables in PL/SQL :


DECLARE
v_hiredate DATE ;
v_deptno NUMBER(2) NOT NULL :=10;
v_location VARCHAR2(25) :='Atlanta' ;
c_commission CONSTANT NUMBER :=1400 ;

Examples of Declaring Scalar Variables:


DECLARE
v_job VARCHAR2(15);
(Variable to store an employee job title)
v_count BINARY_INTEGER :=0;
(variable to count the iterations of a loop and initialized to 0)
v_total_sal NUMBER (9,2) :=0;
(variable to accumulate the total salary for a department and initialized to 0)
v_orderdate DATE := SYSDATE + 7;
(variable to store the ship date of an order and initialize to one week from today)
c_tax_rate CONSTANT NUMBER (3,2) :=8.25;
(a constant variable for the tax rate, which never changes throughout the pl/sql block)
v_valid BOOLEAN NOT NULL := TRUE ;
(flag to indicate wheather a piece of data is valid or invalid and initialized to TRUE)

Note : CONSTANT - Contains the variables so that its value cannot change ; constants must be
initialized. NOT NULL constrains the variable so that it must contain a value . ( NOT NULL variable
must be initialized ).

Naming Rules :
- Two variables can have same name , provided they are in different blocks.
- The variable name (identifier) should not be the same as the name of table columns used in the
block.
Example :
DECLARE
employee_id NUMBER(6);
BEGIN
SELECT employee_id
INTO employee_id
FROM employees
WHERE last_name = 'Kochhar' ;

8
PL/SQL Practices

END ;
Note : Adopt a naming convention for PL/SQL identifiers : for example v_employee_id .
Example of Assignment Operator:
v_hire_date :='15-SEP-2017'
(This four-digit value for year, YYYY , assignment is possible only in Oracle 8i and later. Previous
versions may require the use of the TO_DATE function.)
Example of DEFAULT keyword :
v_mgr NUMBER(6) DEFAULT 100 ;
You can use DEFAULT keyword instead of assignment operator (:=) to initialize a variable. If a user
do not supply a value then the default value will be taken automatically.
Example of NOT NULL :
v_city VARCHAR2(30) NOT NULL := 'Oxford'
Impose the NOT NULL constraint when the variable must contain a value. You cannot assign nulls to
a variable defined as NOT NULL. The NOT NULL constraint must be followed by an initialization
clause.
Note : String Literals must be enclosed in single quotation marks.
For example : 'Hello, World'. If there is a single quotation mark in the string, use a single quotation
mark twice - for example , to insert a value Fisherman's Drive , the string would be
'Fisherman''s Drive' (two single quotation within quotations '.........''........').

Select and Fetch database values into it :


The following example computes 10% bonus for the employees with the EMPLOYEE_ID 167 and
assigns the computed value to the v_bonus variable. This is done using the INTO clause.
DECLARE
v_bonus NUMBER(8,2);
BEGIN
SELECT salary*0.10
INTO v_bonus
FROM employees
WHERE employee_id = 167;
END ;
/
Then you can use the variable v_bonus in another computation or insert its value into a database
table.

The %TYPE Attribute :


 Rather than hard coding the data type and precision of a variable, you can use the %TYPE
attribute to declare a variable according to another previously declared variable or database
column. The %TYPE attribute is most often used when the value stored in the variable will
be derived from a table in the database.

Example of Declaring %TYPE attribute :


Syntax : identifier table_name. column_name % TYPE ;

v_name employees. last_name %TYPE;

9
PL/SQL Practices

Declaring variables to store the last name of an employee. The variable v_name is defined to be of
the same data type as the LAST_NAME column in the EMPLOYEES table. %TYPE provides the data
type of a database column.

v_balance NUMBER(7,2);
v_min_balance v_balance%TYPE := 10;
Declare variables to store the balance of a bank account, as well as the minimum balance, which
starts out as 10. The variable v_min_balance defined to be of the same data type as the variable
v_balance . %TYPE provides the data type of variable
*A NOT NULL database column constraint does not apply to variable that are declared using
%TYPE . Therefore , if you declare a variable using the %TYPE attribute that uses a database column
defined as NOT NULL, you can assign the NULL value to the variable.

Example of Declaring BOOLEAN variables:


- Only the values TRUE, FALSE and NULL can be assigned to BOOLEAN variable.
-The variables are compared by the logical operators AND, OR and NOT.
-The variables always yield TRUE, FALSE or NULL.
-Arithmetic , character and Date expressions can be used to return a BOOLEAN value.
Note : Null stands for a missing , inapplicable or unknown value.

Example:
DECLARE
V_SAL1 NUMBER(5) :=60000;
V_SAL2 NUMBER(5) :=70000;
V_BOOLEAN VARCHAR2(5);

BEGIN
IF V_SAL1 < V_SAL2 THEN
V_BOOLEAN:='TRUE';
ELSE
V_BOOLEAN:='FALSE';
END IF;
DBMS_OUTPUT.PUT_LINE(V_BOOLEAN);
END;
/

LOB Data Type Variables:


-CLOB (Character large object) data type is used to store large blocks of single-byte character data
in the database in the line inside the row or outside the row. Such as Books.
-BLOB (Binary Large Object) data type is used to store large binary objects in the database in line
inside the row or outside the row. Such as Photos.
-BFILE (Binary File) data type is used to store large binary objects in operating system files outside
the database. Such as Movies.
-NCLOB (National Language Character Large Object) data type is used to store large blocks of single-
byte or fixed-width multiple NCHAR unicode data in the database, in line (inside the row) or out of
line (outside the row).

Composite Data Types:

10
PL/SQL Practices

A Scalar data type has no internal components. A Composite type has internal components that
can be manipulated individually. Composite data type also known as collection - are TABLE, RECORD,
NESTED, TABLE and VARRAY types.

Bind Variables :
A Bind variables is variable that you declare in a host environment. Bind variable can be used to
pass run-time values, either number or character , into or out of one or more PL/SQL programs. The
PL/SQL programs use bind variables as they would use any other variable.

Creating Bind Variables :


To declare a bind variable use the command VARIABLE , with variable type NUMBER and
VARCHAR2
VARIABLE return_code NUMBER;
VARIABLE return_msg VARCHAR2(30);

To display the current value of bind variables use the PRINT command. However PRINT cannot be
used inside a PL/SQL block because it is an iSQL*Plus command. Use colon (:) to call a bind variable.

* Print salary of the employee whose employee id is 167 , from EMPLOYEES table.
VARIABLE V_SALARY NUMBER;
BEGIN
SELECT SALARY INTO : V_SALARY
FROM EMPLOYEES
WHERE EMPLOYEE_ID = 167 ;
END ;
/
PRINT V_SALARY;
anonymous block completed.
V_SALARY
-------------
6200

*Print total salary with commission pct (if have) of a year of employee id 100 from EMPLOYEES
table.
VARIABLE V_RESULT NUMBER(8,2);
BEGIN
SELECT (SALARY*12) + (NVL(COMMISSION_PCT,0)*12) INTO : V_RESULT
FROM EMPLOYEES
WHERE EMPLOYEE_ID = 100;
END ;
/
PRINT V_RESULT
anonymous block completed.
V_RESULT
------------
288000

Referencing NON - PL/SQL Variables:

11
PL/SQL Practices

 Store the annual salary into v_monthly_sal host variable.


: g_monthly_sal := v_sal / 12 ;

* Compute the monthly salary, based upon the annual salary supplied by the user.
VARIABLE V_MONTHLY_SAL NUMBER;
DEFINE C_ANNUAL_SAL = 120000;
SET VERIFY OFF
DECLARE
V_SAL NUMBER(8,2) := &C_ANNUAL_SAL;
BEGIN
:V_MONTHLY_SAL := V_SAL / 12;
END;
/
PRINT V_MONTHLY_SAL;
anonymous block completed.
V_MONTHLY_SAL
---------------------
10000

*Write a PL/SQL code to display the monthly salary , the yearly salary will be supplied at the run time.
Divide the yearly salary by 12.
SET ECHO OFF
SET VERIFY OFF
SET SERVEROUTPUT ON

DECLARE
V_SAL NUMBER(8,2):=&ANNUAL_SALARY;
BEGIN
V_SAL := V_SAL /12;
DBMS_OUTPUT.PUT_LINE ('The Monthly salary is $'||V_SAL);
END;
/
anonymous block completed.
The Monthly salary is $ 16250

Practice - 1

Explain each of the following declarations. Determine which of them are not legal and explain why.
a. DECLARE
V_ID NUMBER(4);
------------------------------------------------
DECLARE
V_ID NUMBER(4);
BEGIN
V_ID :=1322;
DBMS_OUTPUT.PUT_LINE(V_ID);
END;
/
12
PL/SQL Practices

anonymous block completed.


1322

b. DECLARE
V_X, V_Y, V_Z VARCHAR2(10);
----------------------------------------------------------------
This is an illegal declaration , it will show PL/SQL compilation error. Because only one identifier per
declaration is allowed. Use the following technique

DECLARE
V_X VARCHAR2(15);
V_Y VARCHAR2(10);
V_Z VARCHAR2(10);
BEGIN
V_X :='Oracle';
V_Y :='SQL';
V_Z :='PL/SQL';
DBMS_OUTPUT.PUT_LINE(V_X);
DBMS_OUTPUT.PUT_LINE(V_Y);
DBMS_OUTPUT.PUT_LINE(V_Z);
END;
/
anonymous block completed.
Oracle
SQL
PL/SQL

c. DECLARE
V_BIRTHDATE DATE NOT NULL;
Illegal , as a not null value must be initialized .
DECLARE
V_BIRTHDATE DATE NOT NULL;
BEGIN
V_BIRTHDATE :='22-Jan-2017';
DBMS_OUTPUT.PUT_LINE(V_BIRTHDATE);
END;
/
ORA - 06550 : line 2, column 13
PLS - 00218: a variable declared NOT NULL must have an initialization assignment

DECLARE
V_BIRTHDATE DATE NOT NULL := '22-Jan-2017';
BEGIN
V_BIRTHDATE :='22-Jan-2017';
DBMS_OUTPUT.PUT_LINE(V_BIRTHDATE);
END;
/

13
PL/SQL Practices

anonymous block completed.


22-JAN-2017

d. DECLARE
V_IN_STOCK BOOLEAN := 1;

PLS - 00382: Expression Type is wrong type.

2. In each of the following assignments, indicate whether the statement is valid and what the valid data
type of the result will be.

a. V_DAYS_TO_GO := V_DUE_DATE - SYSDATE ;


Valid is : V_DAYS_TO_GO NUMBER (7) := V_DUE_DATE - SYSDATE ;
b. V_SENDER := USER ||'; '||TO_CHAR(V_DEPT_NO)
Valid is : V_SENDER VARCHAR2(3):= USER ||'; '||TO_CHAR(V_DEPT_NO)
c. V_SUM := $100,000 + $250,000 ;
Illegal : PL/SQL cannot convert special symbols from VARCHAR2 to NUMBER.
d. v_flag := TRUE ;
Valid : Boolean
e. v_n1 :=v_n2 >(2*v_n3);
Valid : Boolean
f. v_value := NULL;
Valid : Any Scalar data type.

3. Create an anonymous block to output the phrase "My PL/SQL Block Works"
G_MESSAGE
My PL/SQL Block Works

VARIABLE G_MESSAGE VARCHAR2(25);


BEGIN
:G_MESSAGE :='My PL/SQL Block Works';
END;
/
PRINT G_MESSAGE;
anonymous block completed.
G_MESSAGE
-------------------
My PL/SQL Block Works

Alternative Solution :
SET SERVEROUTPUT ON
BEGIN
DBMS_OUTPUT.PUT_LINE('My PL/SQL Block Works');
END;
/
14
PL/SQL Practices

anonymous block completed.


My PL/SQL Block Works

4. Create a block that declares tow variables. Assign the value of these PL/SQL variables to iSQL*Plus
host variables and print the results of the PL/SQL variables to the screen .Execute your PL/SQL block .

v_char Character (variable length)


v_num number

Assign values to these variables as follows:


Variable Value
v_char The literal '42 is the answer'
v_num The first two characters from v_char
------------------------------------------------------------------------------------------------------------------------------------------
VARIABLE G_CHAR VARCHAR2(30)
VARIABLE G_NUM NUMBER
DECLARE
V_CHAR VARCHAR2(30);
V_NUM NUMBER(11,2);
BEGIN
V_CHAR := '42 is the answer';
V_NUM := TO_NUMBER(SUBSTR(V_CHAR,1,2));
:G_CHAR := V_CHAR;
:G_NUM := V_NUM;
END;
/
PRINT G_CHAR
PRINT G_NUM

Alternative Solution :
VARIABLE G_CHAR VARCHAR2(30)
VARIABLE G_NUM NUMBER
BEGIN
:G_CHAR :='42 is the answer';
:G_NUM :=TO_NUMBER(SUBSTR('42 is the answer',1,2));
END;
/
PRINT G_CHAR G_NUM

* Write a smallest size of PL/SQL block.


BEGIN
null;
END;
/
anonymous block completed.

*Write a PL/SQL block to output Employee Name , Employee Id and Salary of given input which will be
supplied at run time by & (ampersand operator) .

15
PL/SQL Practices

DECLARE
V_NAME VARCHAR2(30) :='&Name';
V_EMPID VARCHAR2(3):='&Employee_ID';
V_SALARY NUMBER(8,2):=&Monthly_Salary;
BEGIN
DBMS_OUTPUT.PUT_LINE ('Employee Name:'||V_NAME||' , Employee Id:'||V_EMPID||'
,Salary:'||V_SALARY);
END;
/
*Write a PL/SQL block to display the name , employee id and monthly salary of employee
from EMPLOYEES table. Employee ID will be supplied at run time. The output will as following
:
Name : Neena Kochhar
Employee ID: 101
Salary: 17000 Enter Substitution Variable
Employee_ID

OK Cancel

SET ECHO OFF


SET VERIFY OFF

DECLARE
V_NAME VARCHAR2(50);
V_EMPID VARCHAR2(3):='&Employee_ID';
V_SALARY NUMBER(8,2);
V_ID VARCHAR2(3):=V_EMPID;
BEGIN

SELECT FIRST_NAME||' '||LAST_NAME INTO V_NAME


FROM EMPLOYEES
WHERE EMPLOYEE_ID = V_ID;
DBMS_OUTPUT.PUT_LINE('Name: '||V_NAME);

SELECT EMPLOYEE_ID INTO V_EMPID


FROM EMPLOYEES
WHERE EMPLOYEE_ID =V_ID;
DBMS_OUTPUT.PUT_LINE('Employee ID:'||V_EMPID);

SELECT SALARY INTO V_SALARY


FROM EMPLOYEES
WHERE EMPLOYEE_ID =V_ID;
DBMS_OUTPUT.PUT_LINE('Salary:'||V_SALARY);

END;
/

16
PL/SQL Practices

anonymous block completed.


Name : Neena Kochhar
Employee ID: 101
Salary: 17000

What is Lexical Units in PL/SQL ?

A line of PL/SQL text contains groups of characters known as lexical units. Which can be classified as
follows :
- Delimiters ( It includes Simple and Compound Symbols)
Simple Symbols :
Symbols Meaning
+ Addition Operator
- Subtraction / Negation Operator
* Multiplication Operator
/ Division Operator
= Relational Operator
@ Remote Access Indicator
; Statement Terminator

Compound Symbols:
Symbol Meaning
<> Relational Operator / Not Equal to
!= Relational Operator / Not Equal to
|| Concatenation Operator
-- Single Line comment indicator
/* Beginning Comment indicator
*\ Ending Comment indicator
:= Assignment Operator

- Identifiers (It includes variables, constraints, exception, cursors, subprograms and packages.)
 Identifiers can contain up to 30 characters , but they must start with an alphabetic character.
 Don't choose the same name names for the identifier as the name of columns in a table used in
the block. If PL/SQL identifiers are in the same SQL statements and have the same name as a
column , then Oracle assumes that it is the column that is being referenced.
 Reserved Words should be written in uppercase to promote readability.
 An identifier consists of a letter, optionally followed by more letters, numerals and dollar signs,
underscores and number signs. Other characters such as Hyphens, Slashes and Spaces are illegal
as the following examples show :
v_dots&dashes -- illegal ampersand
v_debit-amount -- illegal hyphen

17
PL/SQL Practices

on/off -- illegal slash


user id -- illegal space
v_money$$$tree -- legal ( dollar signs are allowed)
v_sn## -- legal ( Hash sings are allowed)
v_try_again -- legal (underscores are allowed)

- Literals ( A literal is an ,explicit numeric, character , string or boolean value that is not represented by
an identifier )
 Character literals include all the printable characters in the PL/SQL character set ( that may be
letters, numerals, spaces and special symbols). Character and Date literals must be enclosed in
single quotation marks.
v_name := 'London' ;
v_date :='28-Feb-2018';
 Numeric literals can be represented either by a simple value (example : -32.5) or by a scientific
notation (example : 2E5 , meaning 2* (10 to the power of 5)=200000).
 A PL/SQL program is terminated and executed by a slash (/) on a line by itself.
-- Comments
 Prefix Single - line comments with two dashes (--).
 Place multiple - line comments between the symbols /* and */.
Example:

SET ECHO OFF


SET VERIFY OFF
SET SERVEROUTPUT ON

VARIABLE G_ANN_SALARY NUMBER


DECLARE
V_SAL NUMBER(9,2) ;
V_MONTHLY_SAL NUMBER(9,2) := &MONTHLY_SALARY ;
BEGIN
/* Compute the annual salary based on the monthly salary input from the user */
V_SAL := V_MONTHLY_SAL * 12 ;
: G_ANN_SALARY := V_SAL;
END ;
/
PRINT G_ANN_SALARY

SQL Functions in PL/SQL : Examples:


- Number Functions , Character Functions, Conversion Functions , Date Functions and Miscellaneous
Functions.
*Build a Mailing List for a Company.
DECLARE
V_MAILING_ADDRESS VARCHAR2(120);
V_NAME VARCHAR2(30):='&Name';
V_ADDRESS VARCHAR2(50):='&Address';
V_STATE VARCHAR2(20) :='&State';
V_ZIP VARCHAR2(11) :='&Zip';
18
PL/SQL Practices

BEGIN
V_MAILING_ADDRESS :=V_NAME||', '||V_ADDRESS||', '||V_STATE||', '||V_ZIP;
DBMS_OUTPUT.PUT_LINE ('Employee''s Address:'||V_MAILING_ADDRESS);

END;
/
*Convert the employee name to upper case, lower case and first letter in upper case and rest of letters
in lower case.
Convert to Upper Case :
VARIABLE V_EMP_NAME VARCHAR2(25)
DECLARE
V_ENAME VARCHAR2(25);
BEGIN
:V_EMP_NAME :=UPPER('&EMPLOYEE_NAME');
END;
/
PRINT V_EMP_NAME
anonymous block completed
V_EMP_NAME
---------------------
ROXANN REXIT

Convert to Lower Case :

VARIABLE V_EMP_NAME VARCHAR2(25)


DECLARE
V_ENAME VARCHAR2(25);
BEGIN
:V_EMP_NAME :=LOWER('&EMPLOYEE_NAME');
END;
/
PRINT V_EMP_NAME
anonymous block completed
V_EMP_NAME
---------------------
roxann rexit

Convert to First Letter in Upper case and rest in lower case :

VARIABLE V_EMP_NAME VARCHAR2(25)


DECLARE
V_ENAME VARCHAR2(25);
BEGIN
:V_EMP_NAME :=INITCAP('&EMPLOYEE_NAME');
END;
/
PRINT V_EMP_NAME

19
PL/SQL Practices

anonymous block completed


V_EMP_NAME
---------------------
Roxann Rexit

Data Type Conversion :


 Convert data to a comparable data types.
 Mixed data types can result in an error and affect performance.
 Conversion Functions :
- TO_CHAR
- TO_DATE
- TO_NUMBER

DECLARE
V_DATE DATE;
BEGIN
V_DATE :=TO_DATE('03-MARCH-2018','DD-MON-YYYY');
DBMS_OUTPUT.PUT_LINE(V_DATE);
END;
/
anonymous block completed
03-MAR-2018

Nested Blocks and Variable Scope:


X NUMBER ;------------------------------------------------------|
BEGIN Scope of X |
........... |
DECLARE |
Y NUMBER;------------------------------|
BEGIN Scope of Y |
Y :=X;---------------------------------------|
END;
...... ------------------------------------------------------|
END;

Here Scope of an identifier is that region of a program unit (block, subprogram or package) from which
you can reference the identifier.

An identifier is visible in the block in which it is declared and all nested sub blocks, procedures and
functions. If the block does not find the identifier declared locally, it looks up to the declarative section
of the enclosing (or parent) block. The parent block never looks down to enclosed (child block) or
sideways to sibling blocks.
That means a child block can looks up to parent blocks, as in the above example variable Y can reference
the variable X . But a parent block cannot looks down to child block , so variable X never can reference
variable Y.

20
PL/SQL Practices

Qualify an Identifier :
 The qualifier can be the label of an enclosing block.
 Qualify an identifier by using the block label prefix.

<<OUTER>>
DECLARE
BIRTHDATE DATE;
BEGIN
DECLARE
BIRTHDATE DATE;
BEGIN
OUTER.BIRTHDATE :=TO_DATE('05-MAR-2018','DD-MON-YYYY');
END;
DBMS_OUTPUT.PUT_LINE('BIRTHDATE: '||OUTER.BIRTHDATE);
END;
/
anonymous block completed
BIRTHDATE :05-MAR-2018

In the inner block a variable with the same name, BIRTHDATE , as the variable in the outer block is
declared. To reference the variable, BIRTHDATE, from the outer block in the inner block , prefix the
variable by the block name , OUTER.BIRTHDATE.

Example :
<<OUTER>>
DECLARE
V_SAL NUMBER(7,2) :=60000;
V_COMM NUMBER(7,2) :=0.20;
V_MESSAGE VARCHAR2(255) := ' eligible for commission';
BEGIN
DECLARE
V_SAL NUMBER(7,2):=50000;
V_COMM NUMBER(7,2):=0;
V_TOTAL_COMP NUMBER(7,2):=V_SAL + V_COMM;
BEGIN
V_MESSAGE :='CLERK NOT '||V_MESSAGE;
OUTER.V_COMM :=V_SAL * 0.30;
END;
V_MESSAGE :='SALESMAN ,'||V_MESSAGE;
DBMS_OUTPUT.PUT_LINE(V_MESSAGE);
END;
/

Operators :
Logical , Arithmetic, Concatenation, Parentheses (To control order of operations) , Comparison , Logical
(and, or , not ) and

21
PL/SQL Practices

Exponential Operators (**).

*Increment the counter for a Loop :


V_COUNT :=V_COUNT + 1;

*Set the value of a BOOLEAN Flag:


V_EQUAL :=(V_N1 =V_N2);

*Validate whether an employee number contains a value


V_VALID :=(V_EMPNO IS NOT NULL);

 Comparisons involving NULLS always yield NULL.


 Applying the logical operator NOT to a null yields NULL.
 In conditional control statements, if the condition yields NULL, its associated sequence of
statements is not executed.
Indenting Code:
BEGIN
IF x=0 THEN
y := 1;
END IF;
END;
/
BEGIN
IF X = Y THEN
V_MAX :=X;
ELSE
V_MAX:=Y;
END IF;
END;
/

Example :
DECLARE
V_DEPTNO NUMBER(4);
V_LOCATION_ID NUMBER(4);
BEGIN
SELECT DEPARTMENT_ID, LOCATION_ID
INTO V_DEPTNO, V_LOCATION_ID
FROM DEPARTMENTS
WHERE DEPARTMENT_NAME = INITCAP('SALES') ;
DBMS_OUTPUT.PUT_LINE('Department ID: '||V_DEPTNO ||', '||'Location ID: '||V_LOCATION_ID);
END;
/
anonymous block completed.
Department ID: 80 , Location ID: 2500

Practice 2
22
PL/SQL Practices

PL/SQL Block:

1. Evaluate the PL/SQL , block below and determine the data type and value of each of the following
variables according to the rules of scoping.

a. The value of V_WEIGHT at position 1 is : 1 + 1=2

b. The value of V_NEW_LOCN at position 1 is : Western Europe

c. The value of V_MESSAGE at position 2 is:600+1=601

d. The value of V_MESSAGE at position 2 is :Product 1012 is in stock

e. The value of V_NEW_LOCN at position 2 is: Error as V_NEW_LOCN is not declared

DECLARE
V_WEIGHT NUMBER(3) :=600;
V_MESSAGE VARCHAR2(15) :='Product 1012';
BEGIN
DECLARE
V_WEIGHT NUMBER(3):=1;
V_MESSAGE VARCHAR2(15):='Product 110011';
V_NEW_LOCN VARCHAR2(25):='Europe';
BEGIN
V_WEIGHT :=V_WEIGHT + 1;
V_NEW_LOCN :='Western '||V_NEW_LOCN;
END;
1--------------->
V_WEIGHT :=V_WEIGHT +1;
V_MESSAGE :=V_MESSAGE ||' is in stock';
V_NEW_LOCN :='Western '||V_NEW_LOCN;
2----------------->
END;
/

Example 2:
DECLARE
V_WEIGHT NUMBER(3) :=600;
V_MESSAGE VARCHAR2(25) :='Product 1012';
V_NEW_LOCN VARCHAR2(15) :='London';

BEGIN
DECLARE
V_WEIGHT NUMBER(3):=1;
V_MESSAGE VARCHAR2(15):='Product 110011';

23
PL/SQL Practices

V_NEW_LOCN VARCHAR2(15):='Europe';
BEGIN
V_WEIGHT :=V_WEIGHT + 1;
V_NEW_LOCN :='Western '||V_NEW_LOCN;
DBMS_OUTPUT.PUT_LINE('First Position V_WEIGHT: '||V_WEIGHT);
DBMS_OUTPUT.PUT_LINE('New Locn first pos V_NEW_LOCN: '||V_NEW_LOCN);

END;
V_WEIGHT :=V_WEIGHT +1;
V_MESSAGE :=V_MESSAGE ||' is in stock';
V_NEW_LOCN :='Western '||V_NEW_LOCN;
DBMS_OUTPUT.PUT_LINE('Second Pos V_WEIGHT: '||V_WEIGHT);
DBMS_OUTPUT.PUT_LINE('Second Pos V_MESSAGE: '||V_MESSAGE);
DBMS_OUTPUT.PUT_LINE('Second Pos V_NEW_LOCN: '||V_NEW_LOCN);

END;
/
anonymous block completed
First Position V_WEIGHT: 2
New Locn first pos V_NEW_LOCN: Western Europe
Second Pos V_WEIGHT: 601
Second Pos V_MESSAGE: Product 1012 is in stock
Second Pos V_NEW_LOCN: Western London

Note : The inner block can access the outer block, but the outer block cannot access the inner block.

2.
DECLARE
/*V_NAME VARCHAR2(25):='India';*/
V_CUSTOMER VARCHAR2(50) := 'WomenSports';
V_CREDIT_RATING VARCHAR2(50) :='Excellent' ;
BEGIN
DECLARE
V_CUSTOMER NUMBER(7) := 201;
V_NAME VARCHAR2(25) :='UniSports';
BEGIN
V_CUSTOMER :=V_CUSTOMER;
V_NAME :=V_NAME;
DBMS_OUTPUT.PUT_LINE ('Value of V_CUSTOMER in Subblock:'||V_CUSTOMER);
DBMS_OUTPUT.PUT_LINE ('Value of V_NAME in Subblock:'||V_NAME);
DBMS_OUTPUT.PUT_LINE ('Value of V_CREDIT_RATING in Subblock:'||V_CREDIT_RATING);
END;
DBMS_OUTPUT.PUT_LINE ('Value of V_CUSTOMER in main block:'||V_CUSTOMER);
DBMS_OUTPUT.PUT_LINE ('Value of V_NAME in main block:'||V_NAME);
DBMS_OUTPUT.PUT_LINE ('Value of V_CREDIT_RATING in Subblock:'||V_CREDIT_RATING);
END;
/
anonymous block completed

24
PL/SQL Practices

Value of V_CUSTOMER in Subblock:201


Value of V_NAME in Subblock: UniSports
Value of V_CREDIT_RATING in Subblock: Excellent
Value of V_CUSTOMER in main block: WomenSports
Value of V_NAME in main block: Returns Error (Refer Note below)
Value of V_CREDIT_RATING in Subblock: Excellent
Note : The inner block can access the outer block, but the outer block cannot access the inner block.
3. Create and execute a PL/SQL block that accepts two numbers through Substitution variable.
a. Use the Define command to provide the two values.
DEFINE P_NUM1 =2 -----Example1
DEFINE p_NUM2 =4 ------Example2
b. Pass these two values defined in step above , to the PL/SQL block through the substitution variables.
The first number should be divided by the second number and have the second number added to the
result. The result should be stored in a PL/SQL variable and printed on the screen.

DEFINE P_NUM1 = 70;


DEFINE P_NUM2 = 10;

DECLARE
V_NUM1 NUMBER(12,3) := &P_NUM1;
V_NUM2 NUMBER(12,3) := &P_NUM2;
V_RESULT NUMBER(12,3);

BEGIN
V_RESULT :=(V_NUM1/V_NUM2)+V_NUM2;
DBMS_OUTPUT.PUT_LINE ('Result is :'||V_RESULT);
END;
/
anonymous block completed.
Result is :17

4. Build a PL/SQL Block that computes the total compensation for one year.
a. The annual salary and the annual bonus percentage values are defined using the DEFINE command.
b. Pass the values defined in the above step to the PL/SQL block through substitution variables. The
bonus must be converted from whole number to a decimal ( for example, 15 to .15). If the commission
pct is Null , set it to zero before computing the total compensation. Execute the PL/SQL block.

DECLARE
V_SAL NUMBER(9,2) :=&P_SAL;
V_COMM NUMBER(9,2) :=&P_COMM;
V_TOTAL_SAL NUMBER (19,2);

BEGIN
V_TOTAL_SAL :=ROUND((V_SAL * 12) + (TO_NUMBER((NVL(V_COMM , 0)) *12)) , 0);
DBMS_OUTPUT.PUT_LINE ('Total Annual Salary:'||V_TOTAL_SAL);
END;
/
anonymous block completed

25
PL/SQL Practices

192301

5. Create a PL/SQL block that selects the maximum department number in the DEPARTMENTS table .

DECLARE
V_MAX_DEPTNO VARCHAR2(25);
BEGIN
SELECT MAX(DEPARTMENT_ID)
INTO V_MAX_DEPTNO
FROM EMPLOYEES;
DBMS_OUTPUT.PUT_LINE(V_MAX_DEPTNO);
END;
/
anonymous block completed
110

VARIABLE G_MAX_DEPTID VARCHAR2(25);


DECLARE
V_MAX_DEPTID VARCHAR2(25);
BEGIN
SELECT MAX(DEPARTMENT_ID)
INTO V_MAX_DEPTID
FROM DEPARTMENTS ;
:G_MAX_DEPTID :=V_MAX_DEPTID;
END;
/
PRINT G_MAX_DEPTID

anonymous block completed


G_MAX_DEPTID
----------------------
271

6. Write a Pl/SQL Block to count total number of departments that exists in DEPARTMENT table.
DECLARE
V_COUNT_DEPT NUMBER(9);
BEGIN
SELECT COUNT(DEPARTMENT_ID)
INTO V_COUNT_DEPT
FROM DEPARTMENTS;
DBMS_OUTPUT.PUT_LINE ('Total Number of Departments:'||V_COUNT_DEPT);
END;
/

26
PL/SQL Practices

anonymous block completed


Total Number of Departments:28

What are the use of SQL Statements in PL/SQL ?

 Extract a row of data from the database by using the SELECT command.
 Make changes to rows in the database by using DML commands.
 Control a transaction with the COMMIT, ROLLBACK, or SAVEPOINT command.
 Determine DML outcome with implicit cursor attributes.

- The keyword END signals the end of a PL/SQL block, not the end of a transaction. Just as a block can
span multiple transactions, a transaction can span multiple blocks.
- PL/SQL does not directly support data definition language (DDL) statements, such as CREATE, TABLE,
ALTER TABLE or DROP TABLE.
- PL/SQL does not support data control language (DDL) statements, such as GRANT or REVOKE .

Select Statement in PL/SQL :


SELECT select_list
INTO [variable_name1 [, variable_name2].........
FROM table
[WHERE condition];

- Terminate each SQL statement with a semicolon ( ; ).


- The INTO clause is required for the SELECT statement when it is embedded in PL/SQL.
- The WHERE clause is optional and can be used to specify input variables, constants , literals or PL/SQL
expressions.
- Specify the same number of variables in the INTO clause as database columns in the SELECT clause. Be
sure that they correspond positional and that their data types are compatible.
- Group functions , such as SUM can be used only in SQL statements.
- The INTO clause is required.
- Queries must return only one row.
- Variables are used to hold the values that SQL returns from the select clause. You must specify one
variable for each item selected, and the order of the variables must correspond with the items selected.

DECLARE
V_DEPTNO NUMBER(5);
V_DEPTNAME VARCHAR2(30);
V_MGRNO NUMBER(5);
V_LOCATION_ID NUMBER(5);

BEGIN
SELECT DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID
INTO V_DEPTNO, V_DEPTNAME, V_MGRNO, V_LOCATION_ID
FROM DEPARTMENTS
WHERE LOWER(DEPARTMENT_NAME) = 'sales';
DBMS_OUTPUT.PUT_LINE (V_DEPTNO||','|| V_DEPTNAME||','|| V_MGRNO||','|| V_LOCATION_ID);
END;

27
PL/SQL Practices

/
anonymous block completed
80,Sales,145,2500
Retrieving Data in PL/SQL by using %TYPE;
*Retrieve the hire data and the salary for the specified employee.
DECLARE
V_HIRE_DATE EMPLOYEES.HIRE_DATE%TYPE;
V_SALARY EMPLOYEES.SALARY%TYPE;
BEGIN
SELECT HIRE_DATE, SALARY
INTO V_HIRE_DATE, V_SALARY
FROM EMPLOYEES
WHERE LOWER(LAST_NAME)='rogers';
DBMS_OUTPUT.PUT_LINE('Hire Date:'||V_HIRE_DATE||' ,'||'Salary:'||' ,'||V_SALARY);
END;
/
anonymous block completed
Hire Date:26-Aug-06 ,Salary:2900

*Write a PL/SQL Statement to return total salary (sum of salary) of a given Department.

[ Note :
SELECT SUM(SALARY)
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID = D.DEPARTMENT_ID)
WHERE D.DEPARTMENT_NAME= 'Sales';
]

DECLARE
C_DEPT DEPARTMENTS . DEPARTMENT_NAME%TYPE :='&Department_Name';
V_SUM_SAL EMPLOYEES . SALARY%TYPE ;
BEGIN
SELECT SUM(SALARY)
INTO V_SUM_SAL
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID = D.DEPARTMENT_ID)
AND D.DEPARTMENT_NAME = INITCAP( C_DEPT) ;
DBMS_OUTPUT.PUT_LINE('Total Monthly Salary of Department '||C_DEPT||':'||V_SUM_SAL);
END;
/
anonymous block completed
Total Salary of Department Sales:304500

*Write a PL/SQL Statement to retrieve employee's hire date and sysdate and difference between the
two dates of any given employee id.
DECLARE
V_HIRE_DATE EMPLOYEES.HIRE_DATE%TYPE;
V_SYSDATE EMPLOYEES.HIRE_DATE%TYPE;

28
PL/SQL Practices

V_MONTHS_WORKED NUMBER;
BEGIN
SELECT HIRE_DATE, SYSDATE
INTO V_HIRE_DATE, V_SYSDATE
FROM EMPLOYEES
WHERE EMPLOYEE_ID = 110;
dbms_output.put_line (v_hire_date||', '||v_sysdate);
SELECT ROUND ((MONTHS_BETWEEN(SYSDATE,HIRE_DATE))/12,2)
INTO V_MONTHS_WORKED
FROM EMPLOYEES
WHERE EMPLOYEE_ID = 110;
DBMS_OUTPUT.PUT_LINE(V_MONTHS_WORKED||' months worked appx');
END;
/
anonymous block completed
28-SEP-05 , 16-MAR-2018
12.49 months worked appx

Manipulating Data Using PL/SQL


INSERT , UPDATE, DELETE & MERGE.

 Inserting Data
*Add a new employee information to the employees table.

BEGIN
INSERT INTO EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, HIRE_DATE, JOB_ID,
SALARY)
VALUES
(EMPLOYEES_SEQ.NEXTVAL, 'Ruth', 'Cores', 'RCORES', SYSDATE, 'AD_ASST', 5000);
END;
/
[Note : You can generate primary key value using database sequence. Ex. EMPLOYEES_SEQ.NEXTVAL ]

 Updating Data

*Increase the salary of all employees who are stock clerks.

DECLARE
V_SAL_INCREASE EMPLOYEES.SALARY%TYPE :=800;
BEGIN
UPDATE EMPLOYEES
SET SALARY = SALARY + V_SAL_INCREASE
WHERE JOB_ID = 'ST_CLERK' ;
END;
/

 Deleting Data
*Delete rows that belong to department 10 from the EMPLOYEES table.
29
PL/SQL Practices

DECLARE
V_DEPTNO EMPLOYEES.DEPARTMENT_ID%TYPE :=10;
BEGIN
DELETE FROM EMPLOYEES
WHERE DEPARTMENT_ID = V_DEPTNO;
END;
/
[Note : Delete statement remove unwanted rows from tables, without the use of where clause , the
entire contents of a table can be removed , if there is no integrity constraints. ]
 Merging Rows
How to create a blank table ( only copy the structure of a table) ?
- CREATE TABLE COPY_EMP
AS SELECT * FROM EMPLOYEES WHERE DEPARTMENT_ID = 0;
How to fill the contents of the above table by copying the contents of Employees table?
INSERT INTO COPY_EMP
SELECT * FROM EMPLOYEES ;
How to copy a table including all contents (Create a table with a different name with all its contents) ?
CREATE TABLE COPY_EMP
AS SELECT * FROM EMPLOYEES;

Insert or Update rows in the COPY_EMP table to match the EMPLOYEES table.
DECLARE
V_EMPNO EMPLOYEES.EMPLOYEE_ID%TYPE :=100;
BEGIN
MERGE INTO COPY_EMP C
USING EMPLOYEES E
ON (E.EMPLOYEE_ID = V_EMPNO)
WHEN MATCHAED THEN
UPDATE SET
C.FIRST_NAME = E.FIRST_NAME,
C.LAST_NAME = E.LAST_NAME,
C.EMAIL = E.EMAIL,
C.PHONE_NUMBER = E.PHONE_NUMBER,
C.HIRE_DATE = E.HIRE_DATE,
C.JOB_ID = E.JOB_ID,
C.SALARY = E.SALARY,
C.COMMISSION_PCT = E.COMMISSION_PCT,
C.MANAGER_ID = E.MANAGER_ID,
C.DEPARTMENT_ID = E.DEPARTMENT_ID
WHEN NOT MATCHED THEN
INSERT VALUES (E.EMPLOYEE_ID, E.FIRST_NAME, E.LAST_NAME, E.EMAIL, E.PHONE_NUMBER,
E.HIRE_DATE, E.JOB_ID, E.SALARY, E.COMMISSION_PCT, E.MANAGER_ID, E.DEPARTMENT_ID);
END;
/

Note : Naming Conventions . Use a naming convention to avoid ambiguity in the WHERE clause.
Database columns and identifiers should have distinct names.

30
PL/SQL Practices

Identifier Naming Convention Example


Variable V_NAME V_SAL
Constant C_NAME C_COMPANY_NAME
Cursor Name_cursor Emp_cursor
Exception E_name E_too_many
Table Type NAME_TABLE_TYPE Amount_table_type
Table NAME_TABLE COUNTIRES
Record Type NAME_RECORD_TYPE EMP_RECORD_TYPE
RECORD NAME_RECORD CUSTOMER_RECORD
Substitution Variable Type P_NAME P_SAL
Host or Bind Variable (Global) G_NAME G_YEAR_SAL
 What is SQL Cursor ?
A Cursor is a private SQL work area. ( Whenever you issue a SQL statement, the Oracle Server opens an
area of memory in which the command is parsed and executed. This is called a cursor)
There are two types of cursors : - Implicit Cursors and Explicit Cursors.
The Oracle Server uses implicit cursors to parse and execute your SQL statements.
Explicit Cursors are explicitly declared by the programmer.

SQL Cursor Attributes


SQL%ROWCOUNT Number of rows affected by the most recent SQL Statement ( it is an
integer value)

SQL%FOUND Boolean attribute that evaluates to TRUE if the most recent SQL statement
affects one or more rows

SQL%ISOPEN Always evaluates to FALSE because PL/SQL close implicit cursors


immediately after they are executed

SQL%ROWCOUNT - Example
*Delete rows that have the specified employee ID from the COPY_EMP table. Print the number of Rows
Deleted.

DECLARE
V_ROWS_DEL VARCHAR2(25);
V_DEPT_ID COPY_EMP.DEPARTMENT_ID%TYPE :='&Deparment_ID';

BEGIN
DELETE FROM COPY_EMP
WHERE DEPARTMENT_ID = V_DEPT_ID;
V_ROWS_DEL :=SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE (TO_CHAR(V_ROWS_DEL)||' rows deleted successfully.');

END;
/
anonymous block completed
3 rows deleted successfully.
31
PL/SQL Practices

[Note : While using substitution variable use underscore to avoid ORA-06502 character to number
conversion error ]

Practice 3
1. Write a PL/SQL code to find the maximum department number from departments table.

DECLARE
V_MAX_DEPTID DEPARTMENTS.DEPARTMENT_ID%TYPE;
BEGIN
SELECT MAX(DEPARTMENT_ID)
INTO V_MAX_DEPTID
FROM DEPARTMENTS;
DBMS_OUTPUT.PUT_LINE('Maximum Dept No:'||V_MAX_DEPTID);
END;
/
anonymous block completed
Maximum Dept NO:271

2. Write a pl/sql block to insert a new department into the COPY_DEPARTMENT table.
a. Use the DEFINE command to provide the department name. Name the new department
Education.
SET ECHO OFF
SET VERIFY OFF
DEFINE P_DEPT_NAME = Education
b. Pass the value to the PL/SQL block through a iSQL*Plus substitution variable. Rather than
printing the department number retrieved from simple select statement, add 10 to it as the department
number for the new department.
c. Leave the location number as null for now.
DEPARTMENT_ID DEPARTMENT_NAME MANAGER_ID LOCATION_ID
281 Education
d. Display the new department you created.
SET ECHO OFF
SET VERIFY OFF
DEFINE P_DEPT_NAME = 'Education'

DECLARE
V_MAX_DEPTID COPY_DEPARTMENTS.DEPARTMENT_ID%TYPE ;
BEGIN
SELECT MAX(DEPARTMENT_ID) + 10
INTO V_MAX_DEPTID
FROM COPY_DEPARTMENTS;
INSERT INTO COPY_DEPARTMENTS (DEPARTMENT_ID, DEPARTMENT_NAME, LOCATION_ID)
VALUES (V_MAX_DEPTID, '&P_DEPT_NAME', NULL);
32
PL/SQL Practices

COMMIT;
END;
/
anonymous block completed
SELECT * FROM COPY_DEPARTMENTS
WHERE DEPARTMENT_NAME = 'Education';

3. Create a PL/SQL block that updates the location ID for the new department that you added in the
previous practice.
a. Use the DEFINE command to provide the location ID. Name the new location ID 1700.
DEFINE P_DEPTNO = 280
DEFINE P_LOC = 1700
b. Pass the value to the pl/sql block through substitution variable.
c. Display the department that you updated.

DEFINE P_DEPTNO = 281


DEFINE P_LOC = 1700
DEFINE P_MGR =100

BEGIN
UPDATE COPY_DEPARTMENTS
SET MANAGER_ID =&P_MGR
WHERE DEPARTMENT_ID = &P_DEPTNO;

UPDATE COPY_DEPARTMENTS
SET LOCATION_ID =& P_LOC
WHERE DEPARTMENT_ID = &P_DEPTNO;
COMMIT;
END;
/
anonymous block completed

DEFINE P_DEPTID = 281


SELECT * FROM COPY_DEPARTMENTS
WHERE DEPARTMENT_ID = &P_DEPTID;

4. Create a PL/SQL block that delete the department Education from Copy_Departments table. Print the
rows affected.

DEFINE P_DEPTNO = 281


DECLARE
v_rows_deleted varchar2(5);
BEGIN
DELETE FROM COPY_DEPARTMENTS
WHERE DEPARTMENT_ID = &P_DEPTNO;
V_ROWS_DELETED :=SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(V_ROWS_DELETED ||' row(s) deleted.');

33
PL/SQL Practices

END;
/
anonymous block completed
1 row(s) deleted.
DEFINE P_DEPTNO = 281

SELECT * FROM COPY_DEPARTMENTS


WHERE DEPARTMENT_ID = &P_DEPTNO ;
Controlling PL/SQL Flow of Execution

 To change the logical execution of statements using conditional IF statements and loop control
structures.
 Conditional IF Statements:

-- IF - THEN -END IF
-- IF - THEN - ELSE - END IF
-- IF - THEN - ELSIF - END IF
[Note : Focus on the ELSIF spelling ]

Simple IF Statements
Syntax :- IF condition THEN
Statements.....;
END IF ;
*If the last name is Vargas , set job ID to SA_REP and set department number to 110.
IF v_name = 'Vargas' THEN
V_job :='SA_REP' ;
V_DEPTNO := 110 ;
END IF;

DECLARE
V_JOB_ID COPY_EMP.JOB_ID%TYPE;
V_DEPTNO COPY_EMP.DEPARTMENT_ID%TYPE;
V_ENAME COPY_EMP.LAST_NAME%TYPE;
V_ROWS_AFFECT VARCHAR2(5);
BEGIN
IF V_ENAME = 'Vargas' THEN
V_JOB_ID := 'SA_REP';
V_DEPTNO := 110;
END IF;
UPDATE COPY_EMP
SET JOB_ID = V_JOB_ID
WHERE LAST_NAME = V_ENAME;
UPDATE COPY_EMP
SET DEPARTMENT_ID = V_DEPTNO
WHERE LAST_NAME = V_ENAME;
V_ROWS_AFFECT :=SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(V_ROWS_AFFECT||' ROW(S) UPDATED');

34
PL/SQL Practices

END;
/

anonymous block completed


0 row(s) updated.

Compound IF Statements:
IF - THEN - ELSE Statement

IF V_ENAME = 'Vargas' AND salary > 6500 THEN


V_DEPTNO :=60;
END IF;

IF V_DEPARTMENT = '110' OR V_HIREDATE > '01-Dec-2018'


THEN V_MGR :=100;
END IF;
Syntax : IF condition1 THEN
Statement 1;
ELSE
Statement 2;
END IF;

Nested IF Statement:
Syntax: IF condition1 THEN
Statement 1;
ELSE
IF condition 2 THEN
Statement 2;
END IF;
END IF;
*Set a Boolean flag to TRUE if the hire date is greater than five years; otherwise , set the Boolean flag
to FALSE.

DECLARE
V_HIRE_DATE COPY_EMP.HIRE_DATE%TYPE:='12-Dec-1990';
V_FIVE_YRS VARCHAR2(15);
V_MONTHS_BET NUMBER;
BEGIN
SELECT MONTHS_BETWEEN(SYSDATE,HIRE_DATE)
INTO V_MONTHS_BET
FROM COPY_EMP
WHERE EMPLOYEE_ID = 100;

IF (V_MONTHS_BET/12) >5 THEN


V_FIVE_YRS :='TRUE';

35
PL/SQL Practices

ELSE
V_FIVE_YRS :='FALSE';
END IF;
DBMS_OUTPUT.PUT_LINE(V_FIVE_YRS);
DBMS_OUTPUT.PUT_LINE(V_MONTHS_BET||' months worked');
END;
/

*Check the value in the V_ENAME variable , If the value is King , set the V_JOBID variable to
AD_PRESS . Otherwise, set the V_JOBID to ST_CLERK.
DECLARE
V_ENAME COPY_EMP.LAST_NAME%TYPE:='King';
V_JOBID COPY_EMP.JOB_ID%TYPE;
BEGIN
IF V_ENAME='King' THEN
V_JOBID :='AD_PRESS';
ELSE
V_JOBID :='ST_CLERK';
END IF;
DBMS_OUTPUT.PUT_LINE(V_JOBID);
END;
/
anonymous block completed
AD_PRESS

IF - THEN - ELSIF Statement:


Syntax :
IF condition1 THEN
sequence of statements 1;
ELSIF condition2 THEN
sequence of statements 2;
ELSE
sequence of statements 3;
END IF;

Example:
DECLARE
V_DEPTNO COPY_EMP.DEPARTMENT_ID%TYPE :='&DEPARTMENT_ID';
V_BONUS NUMBER;
BEGIN
IF V_DEPTNO =10 THEN
V_BONUS:=0.1;
ELSIF V_DEPTNO >10 AND V_DEPTNO <50 THEN
V_BONUS:=0.3;
ELSIF V_DEPTNO >= 50 AND V_DEPTNO <=90 THEN
V_BONUS:=0.5;
ELSE

36
PL/SQL Practices

V_BONUS:=0.7;
END IF;
SELECT SALARY * V_BONUS
INTO V_BONUS
FROM COPY_EMP
WHERE DEPARTMENT_ID=V_DEPTNO;
DBMS_OUTPUT.PUT_LINE(V_BONUS||' is extra bonus, Enjoy!');
END;
/
anonymous block completed
440 is extra bonus, Enjoy!

CASE Expressions
 A CASE expression selects a result and returns it.
 To select the result, the CASE expression uses an expression whose value is used to select one of
several alternatives.
CASE selector
WHEN expression 1 THEN result1
WHEN expression 2 THEN result 2
...........
WHEN expression THEN resultN
[ ELSE resultN+1 ;]
END ;
*Write a PL/SQL code to get the result of an examination.
DECLARE
V_MARKS VARCHAR2(3):='&Marks_Obtained';
V_GRADE VARCHAR2(1):=UPPER('&P_Grade');
V_APPRAISAL VARCHAR2(30);
BEGIN
V_APPRAISAL :=
CASE
WHEN V_GRADE = 'A' THEN 'Excellent'
WHEN V_GRADE = 'B' THEN 'Very Good'
WHEN V_GRADE = 'C' THEN 'Good'
ELSE 'NO Such Grade'
END;
DBMS_OUTPUT.PUT_LINE('Marks Obtained:'||V_MARKS);
DBMS_OUTPUT.PUT_LINE('Grade: '||V_GRADE||' , Appraisal: '||V_APPRAISAL);
END;
/
anonymous block completed
Marks Obtained:83
Grade: A , Appraisal: Excellent

LOOP Statements
What is LOOP ?
- Loops repeat a statement or sequence of statements multiple times.
- There are three loop types:

37
PL/SQL Practices

-Basic Loop (Basic Loop that perform repetitive actions without overall conditions)
-For Loop (For Loop that perform iterative control of actions based on a count)
-While Loop (While Loop that perform iterative control of actions based on a condition)

General Guidelines :
 Use the Basic Loop when the statements inside the loop must execute at least once.
 Use the WHILE loop if the condition has to be evaluated at the start of each iteration.
 Use a For Loop if the number of iterations is known.
Basic Loops
Syntax :
LOOP
Statement 1 ;
EXIT [WHEN condition];
END LOOP ; |
|
[Note : condition is a Boolean variable or expression (True, False or Null ) ].
It is to remember that a Basic Loop allows execution of its statements at least once .

In the below example , three new locations IDs for the country code CA and the city of Montreal are
being added.
Example:
CREATE TABLE COPY_LOCATIONS
AS SELECT * FROM LOCATIONS;

DECLARE
V_COUNTRY_ID COPY_LOCATIONS.COUNTRY_ID%TYPE :='CA';
V_LOCATION_ID COPY_LOCATIONS.LOCATION_ID%TYPE ;
V_COUNTER NUMBER(2) :=1;
V_CITY COPY_LOCATIONS.CITY%TYPE :='Montreal';
V_ROWS_INSERTED VARCHAR2(1);
BEGIN
SELECT MAX(LOCATION_ID)
INTO V_LOCATION_ID
FROM COPY_LOCATIONS
WHERE COUNTRY_ID = V_COUNTRY_ID;
LOOP
INSERT INTO COPY_LOCATIONS (LOCATION_ID, CITY, COUNTRY_ID)
VALUES ((V_LOCATION_ID + V_COUNTER), V_CITY, V_COUNTRY_ID);
V_COUNTER := V_COUNTER +1;
EXIT WHEN V_COUNTER > 3;
END LOOP;
V_ROWS_INSERTED :=SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE(V_ROWS_INSERTED||' row(s) inserted');
38
PL/SQL Practices

END;
/
anonymous block completed
1 row(s) inserted

WHILE LOOP:
Syntax:
WHILE condition LOOP <------ Condition is evaluated at the beginning of each iteration.
statement 1;
statement 2;
statement 3;
...........
END LOOP ;

[Note: Use the WHILE loop to repeat statements while a condition is TRUE. The condition is evaluated
at the start of each iteration. The loop terminates when the condition is FALSE. If the condition is FALSE
at the start of the loop, then no further iterations are performed. If the variable involved in the
conditions do not change during the body of the loop, then the condition remains TRUE and the loop
does not terminate. If the condition yields NULL, the loop is bypassed and control passes to the next
statement.]

DECLARE
V_COUNTRY_ID COPY_LOCATIONS.COUNTRY_ID%TYPE :='CA';
V_LOCATION_ID COPY_LOCATIONS.LOCATION_ID%TYPE ;
V_COUNTER NUMBER(2) :=1;
V_CITY COPY_LOCATIONS.CITY%TYPE:='Montreal';
V_ROWS_INSERTED VARCHAR2(1);
BEGIN
SELECT MAX(LOCATION_ID)
INTO V_LOCATION_ID
FROM COPY_LOCATIONS
WHERE COUNTRY_ID = V_COUNTRY_ID;
WHILE V_COUNTER <=3 LOOP
INSERT INTO COPY_LOCATIONS (LOCATION_ID, CITY, COUNTRY_ID)
VALUES ((V_LOCATION_ID + V_COUNTER),V_CITY,V_COUNTRY_ID);
V_COUNTER:=V_COUNTER +1;
END LOOP;
V_ROWS_INSERTED:=(SQL%ROWCOUNT);
DBMS_OUTPUT.PUT_LINE(V_ROWS_INSERTED||' row(s) inserted');
END;
/
anonymous block completed

With each iteration through the WHILE Loop , a counter (v_counter) is incremented. If the number of
iterations is less than or equal to the number 3, the code within the loop is executed and a row is
inserted into the LOCATIONS table. After the counter exceeds the number of items for this locations the
condition that controls the loop evaluates to FALSE and the loop is terminated.

39
PL/SQL Practices

FOR Loop :
Syntax :
FOR counter IN [REVERSE]
lower_bound . . upper_bound LOOP
statement1;
statement2;
. . .
END LOOP ;
[ Note : counter - is implicitly declared integer whose value automatically increase or decrease (
decreases if the REVERSE keyword is used) by i on each iteration of the loop until the upper or lower
bound is reached.
Reverse - causes the counter to decrement with each iteration from the upper bound to the
lower bound. Note that lower bound is to be referenced first in any condition.
lower_bound - specifies the lower bound for the range of counter values.
upper_bound - specifies the upper bound for the range of counter values.
 Do not declare the counter ( i ) , it is declared implicitly as an integer.
 Use a FOR LOOP to shortcut the test for the number of iterations.
 ' lower_bound . . upper_bound ' is required as in the syntax.

DECLARE
V_COUNTRY_ID COPY_LOCATIONS.COUNTRY_ID%TYPE :='CA';
V_LOCATION_ID COPY_LOCATIONS.LOCATION_ID%TYPE ;
V_COUNTER NUMBER(2) :=1;
V_CITY COPY_LOCATIONS.CITY%TYPE:='Montreal';
V_ROWS_INSERTED VARCHAR2(1);
BEGIN
SELECT MAX(LOCATION_ID)
INTO V_LOCATION_ID
FROM COPY_LOCATIONS
WHERE COUNTRY_ID = V_COUNTRY_ID;
FOR I IN
1..3 LOOP
INSERT INTO COPY_LOCATIONS (LOCATION_ID, CITY, COUNTRY_ID)
VALUES (V_LOCATION_ID, V_CITY, V_COUNTRY_ID);
END LOOP;
V_ROWS_INSERTED:=(SQL%ROWCOUNT);
DBMS_OUTPUT.PUT_LINE(V_ROWS_INSERTED||' row(s) inserted');
END;
/
anonymous block completed

* Write a PL/SQL code to get the last digit after 0 to 9 iteration using for loop.
DECLARE
V_COUNTER NUMBER(3):=0;
BEGIN
FOR I IN
V_COUNTER..9 LOOP
V_COUNTER :=V_COUNTER + 1;
40
PL/SQL Practices

--DBMS_OUTPUT.PUT_LINE('For Loop:'||v_counter);
END LOOP;
DBMS_OUTPUT.PUT_LINE('For Loop:'||v_counter);
END;
/
anonymous block completed.
For Loop: 10

* Print value 0 to 9 using For Loop.

DECLARE
V_COUNTER NUMBER(3):=0;
BEGIN
FOR i IN
V_COUNTER..9 LOOP
V_COUNTER :=V_COUNTER + 1;
DBMS_OUTPUT.PUT_LINE('For Loop:'||i);
END LOOP;
END;
/
anonymous block completed
For Loop:0
For Loop:1
For Loop:2
For Loop:3
For Loop:4
For Loop:5
For Loop:6
For Loop:7
For Loop:8
For Loop:9

* Print value 1 to 11 using For Loop.


DECLARE
V_COUNTER NUMBER(3):=1;
BEGIN
FOR I IN
V_COUNTER..11 LOOP
V_COUNTER :=V_COUNTER + 1;
DBMS_OUTPUT.PUT_LINE('For Loop:'||i);
END LOOP;
END;
/
anonymous block completed
For Loop:1
For Loop:2
For Loop:3
For Loop:4

41
PL/SQL Practices

For Loop:5
For Loop:6
For Loop:7
For Loop:8
For Loop:9
For Loop:10
For Loop:11

* Print value 1 to 100.


DECLARE
V_CONTAINTER NUMBER;
V_LOWER NUMBER(3):=1;
V_UPPER NUMBER(3) :=100;
BEGIN
FOR I IN
V_LOWER..V_UPPER
LOOP
V_CONTAINTER:=I;
DBMS_OUTPUT.PUT_LINE(V_CONTAINTER);
END LOOP;
END;
/
anonymous block completed

Nested Loop :
What is Nested Loop ?
- A loop within another loop is called nested loop.
- A label is placed before a statement, either on same line or on a separate line. <<label>> is a label
delimiter .

Practice -4
1. Create a Table MESSAGES with a single column and single row. Name the column Results , and type of
the column is varchar2(3).

 Insert the numbers 1 to 10, excluding 6 and 8.


 Commit before end of the work.
 Verify by querying the table.

CREATE TABLE MESSAGES


(
RESULTS VARCHAR2(3)
);

BEGIN
FOR I IN
1..10
LOOP

42
PL/SQL Practices

IF I=6 OR I=8 THEN


NULL;
ELSE
INSERT INTO MESSAGES (RESULTS)
VALUES(I);
END IF;
COMMIT;
END LOOP;
END;
/
anonymous block completed
SELECT * FROM MESSAGES ;

* Write a PL/SQL code to print numbers 1 to 19, excluding number 10, 13, 16 and 18 respectively.
DECLARE
V_FOR_LOOP VARCHAR2(3);
BEGIN
FOR I IN
1..19
LOOP
IF I=10 OR I=13
THEN NULL;
ELSIF I=16 OR I=18
THEN NULL;
ELSE
V_FOR_LOOP :=I;
DBMS_OUTPUT.PUT_LINE(V_FOR_LOOP);
END IF;
END LOOP;
END;
/
anonymous block completed

* Write a PL/SQL code to display the bonus from Employees table based on the following conditions:
1. You may use define command to provide the employee_id. [ DEFINE p_empno = 100 ]
2. If the employee's salary is less than $5,000 , display the bonus amount for the employee as
10% of the salary.
3. If the employee's salary is between $5,000 and $10,000, display the bonus amount for the
employee as 15% of the salary.
4. If the employee's salary exceeds $10,000 , display the bonus amount for the employee as 20%
of the salary.
5. If the employee's salary is NULL, display the bonus amount for the employee as 0.
You can show the result as the below:
Employee Number Salary Resulting Bonus
100 24000 4800
149 10500 2100
178 7000 1050

43
PL/SQL Practices

CREATE TABLE MONTHLY_BONUS


(EMPLOYEE_NUMBER VARCHAR2(4),
SALARY NUMBER(8,2),
RESULTING_BONUS NUMBER(8,2)
);

set echo off


set verify off
set serveroutput on

DECLARE
V_EMPNUM VARCHAR2(4):='&P_Employee_ID';
V_SAL NUMBER(8,2);
V_BONUS NUMBER(8,2);
BEGIN
SELECT SALARY
INTO V_SAL
FROM EMPLOYEES
WHERE EMPLOYEE_ID = V_EMPNUM;
IF V_SAL<5000 THEN V_BONUS:=V_SAL*.1;
ELSIF V_SAL>=5000 AND V_SAL<=10000 THEN V_BONUS:=V_SAL*.15;
ELSIF V_SAL>10000 THEN V_BONUS:=V_SAL*.2;
ELSE V_BONUS:=0;
END IF;
INSERT INTO MONTHLY_BONUS(EMPLOYEE_NUMBER,SALARY,RESULTING_BONUS)
VALUES (V_EMPNUM, V_SAL, V_BONUS);
COMMIT;
END;
/
anonymous block completed
SELECT * FROM MONTHLY_BONUS;

3. Create a replica of the Employees table, name it as COPY_EMP. Add a new column as STARS , of
varchar2 data type and length of 50 for the COPY_EMP table for storing asterisk(*).

CREATE TABLE COPY_EMP


AS SELECT * FROM EMPLOYEES;
table COPY_EMP created

ALTER TABLE COPY_EMP


ADD STARS VARCHAR2(50);
table COPY_EMP altered

44
PL/SQL Practices

4. Create a PL/SQL block that rewards an employee by appending an asterisk in the STARS column for
every $1000 of the employee's salary. For example , if the salary is $8000 the asterisks should contain
eight asterisks. If salary is $11,500, the string of asterisks should contain 12 asterisks.
Update the STARS column for the employee with the string of asterisks. Commit it.

You can define the employee_id as follows:


DEFINE p_empno=174
DEFINE p_empno=176
DEFINE p_empno=100

DECLARE
V_EMPNO COPY_EMP.EMPLOYEE_ID%TYPE:=TO_NUMBER('&P_Emp_ID');
V_SAL COPY_EMP.SALARY%TYPE;
V_ASTERISK COPY_EMP.STARS%TYPE;
BEGIN
SELECT NVL(ROUND(SALARY/1000),0)
INTO V_SAL
FROM COPY_EMP
WHERE EMPLOYEE_ID=V_EMPNO;
FOR I IN
1..V_SAL
LOOP
V_ASTERISK:=V_ASTERISK||'*';
END LOOP;
UPDATE COPY_EMP
SET STARS = V_ASTERISK
WHERE EMPLOYEE_ID=V_EMPNO;
COMMIT;
END;
/
anonymous block completed

SELECT * FROM COPY_EMP


WHERE EMPLOYEE_ID=100;

SELECT EMPLOYEE_ID, SALARY, STARS


FROM COPY_EMP
WHERE STARS IS NOT NULL;

SELECT EMPLOYEE_ID, SALARY, STARS


FROM COPY_EMP
WHERE EMPLOYEE_ID IN (100, 101, 102);

45
PL/SQL Practices

Like scalar variables , composite variables have data types. Composite Data Type is also known as
collection. The Data Types are RECORD, TABLE, NESTED TABLE and VARRAY.

What is record ?
A record is a group of related data items stored as fields, each with its own name and data type. For
example , suppose you have different kinds of data about Employee, such as Name, Salary, Hire Date
and so on. This data is dissimilar in type but logically related. You can treat the data as a logical unit. A
table contains a column and primary key to give you array like access to rows.

Declaring %ROWTYPE Attribute:


To declare a record based on collection of columns in a database table or view, you use the %ROWTYPE
attribute. The fields in the record take their names and data types from the columns of the table or
view. The record can also store an entire row of data fetched from a cursor or cursor variable.

DECLARE
emp_record employee%ROWTYPE ;
*The emp_record will have a structure consisting each corresponding fields contains in the Employees
table.

Advantages of Using %ROWTYPE:


 The number and data types of the underlying data types columns need not be known.
 The number and data types of the underlying database column may change at run time.
 The attribute is useful when retrieving a row with the SELECT * statement.
Examples:
Declare a variable to store the information about a department from the DEPARTMENTS table.
dept_record departments%ROWTYPE ;

The declaration on the slide creates a record with the same field names ,field data types and order as a
row in the DEPARTMENTS table. The fields are DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID
and LOCATION_ID.

Declare a variable to store the information about an employee from the EMPLOYEES table.
emp_record employees%ROWTYPE ;

Similarly emp_record declaration creates a record with the same field names ,data types and order as a
row in the EMPLOYEES table.

*Create a table named RETIRED_EMPLOYEES as replica of EMPLOYEES table. An Employee is retiring ,


information about the retiring employee is added to the RETIRED_EMPLOYEES table, that holds
information about the employee. The user supplies employee ID. The record of the employee specified
by the user is retrieved from the EMPLOYEES and stored into the emp_rec variable, which is declared
using the %ROWTYPE attribute.

CREATE TABLE RETIRED_EMPLOYEES


AS SELECT * FROM EMPLOYEES
WHERE EMPLOYEE_ID = -1;

46
PL/SQL Practices

SELECT CURRENT_DATE
FROM DUAL;

DEFINE EMPLOYEE_NUM =124


DECLARE
EMP_REC EMPLOYEES%ROWTYPE;

BEGIN
SELECT * INTO EMP_REC
FROM EMPLOYEES
WHERE EMPLOYEE_ID= &EMPLOYEE_NUM ;

INSERT INTO RETIRED_EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL,


PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID)
VALUES (EMP_REC.EMPLOYEE_ID, EMP_REC.FIRST_NAME, EMP_REC.LAST_NAME, EMP_REC.EMAIL,
EMP_REC.PHONE_NUMBER, EMP_REC.HIRE_DATE, EMP_REC.JOB_ID, EMP_REC.SALARY,
EMP_REC.COMMISSION_PCT, EMP_REC.MANAGER_ID, EMP_REC.DEPARTMENT_ID);

COMMIT;
END;
/

SELECT * FROM RETIRED_EMPLOYEES;

Index By Tables :
Object of the TABLE type are called INDEX BY tables. They are modeled as database tables, but not the
same as database tables. INDEX BY tables use a primary key to provide you with array like access to
rows.
A INDEX BY table:
- Is similar to an array.
- Must contain tow components:
-A primary key of data type BINARY_INTEGER that indexes the INDEX BY table.
-A column of a scalar or record data type, which stores the INDEX BY table elements.
-Can increase dynamically because it is unconstrained.
Syntax :
TYPE type_name IS TABLE OF
{COLUMN_TYPE | VARIABLE%TYPE }|TABLE.COLUMN%TYPE} [NOT NULL] |TABLE.%ROWTYPE
[INDEX BY BINARY_INTEGER];
IDENTIFIER TYPE_NAME ;

type_name is the name of the table type.


column_type is any scalar (scalar and composite) data type such as varchar2, date, number or %type.
identifier is the name of the identifier that represents an entire pl/sql table.

47
PL/SQL Practices

An index-by table (also called an associative array) is a set ofkey-value pairs. Each
key is unique and is used to locate the corresponding value. The key can be either an
integer or a string.

An index-by table is created using the following syntax. Here, we are creating
an index-by table named table_name, the keys of which will be of the
subscript_type and associated values will be of the element_type

TYPE type_name IS TABLE OF element_type [NOT NULL] INDEX BY subscript_type;

table_name type_name;

INDEX BY table Structure:

Unique Identifier Column


1 Jones
2 Smith
3 Maduro

Creating an Index By Table


DECLARE
TYPE ename_table_type IS TABLE OF
employees.last_name%TYPE
INDEX_BY BINARY_INTEGER ;
TYPE hiredate_table_type IS TABLE OF
DATE
INDEX BY BINARY_INTEGER;
ename_table ename_table_type;
hiredate_table hiredate_table_type;
BEGIN
ename_table(1) :='CAMERON';
hiredate_table(8) :=SYSDATE + 7;
IF ename_table.EXISTS(1) THEN
INSERT INTO.............
..............
END ;
/

Using INDEX BY Table Methods :

Method Description
EXISTS(n) Returns TRUE if the nTH elemant in pl/sql table exists
COUNT Returns the number of elements that a PL/SQL table currently contains
FIRST Returns the first and last (smallest is first and largest is last) index numbers in a

48
PL/SQL Practices

LAST PL/SQL table . Returns NULL if the PL/SQL table is empty.


PRIOR(n) Returns the index number that succeeds index n in a PL/SQL table.
NEXT (n) Returns the index number that succeeds index n in a pl/sql table
TRIM TRIM removes one element from the end of a PL/SQL table.
TRIM (n) removes n elements from the end of a PL/SQL table.
DELETE DELETE removes all elements from a PL/SQL table.
DELETE (n) removes the nTH element from a PL/SQL table.
DELETE(m, n) removes all elements in the range m...n from a PL/SQL table.

Example of INDEX BY TABLE :


*Display the Last Names of first five employees (start from employee Id 100 to 105) by index by table.

DECLARE
TYPE EMP_TABLE_TYPE IS TABLE OF
EMPLOYEES%ROWTYPE
INDEX BY BINARY_INTEGER;
MY_EMP_TABLE EMP_TABLE_TYPE;
V_COUNT NUMBER(3):=105;
BEGIN
FOR I IN 100..V_COUNT
LOOP
SELECT * INTO MY_EMP_TABLE(I)
FROM EMPLOYEES
WHERE EMPLOYEE_ID = I;
END LOOP;
FOR I IN MY_EMP_TABLE.FIRST..MY_EMP_TABLE.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(MY_EMP_TABLE(I).LAST_NAME);
END LOOP;
END;
/
anonymous block completed
King
Kochhar
De Haan
Hunold
Ernst
Austin

DECLARE
TYPE DEPT_TABLE_TYPE IS
TABLE OF DEPARTMENTS%ROWTYPE
49
PL/SQL Practices

INDEX BY BINARY_INTEGER ;
MY_DEPT_TABLE DEPT_TABLE_TYPE;
V_COUNT NUMBER(3):=270;
BEGIN
FOR I IN 10..V_COUNT
LOOP
SELECT * INTO MY_DEPT_TABLE(I)
FROM DEPARTMENTS
WHERE DEPARTMENT_ID = I;

END LOOP;
FOR I IN MY_DEPT_TABLE.FIRST..MY_DEPT_TABLE.LAST
LOOP
DBMS_OUTPUT.PUT_LINE(MY_DEPT_TABLE(I).DEPARTMENT_NAME);
END LOOP;
END;
/
anonymous block completed
Practice - 5
1. Write a PL/SQL block to print information about a given country.
a. Declare a PL/SQL record based on the structure of the COUNTRIES table.
b. Use the DEFINE command to provide the country ID. Pass the value to the PL/SQL block
through a substitution variable.
SET VERIFY OFF
SET ECHO OFF
SET SERVEROUTPUT ON
DEFINE P_COUNTRYID = CA
c. Use DBMS_OUTPUT.PUT_LINE print selected information about the country. A simple output
is shown below.
Country ID: CA Country Name: Canada Region: 2
d. Execute the PL/SQL block for the countries with the IDs CA, DE, UK, US.

DEFINE COUNTRY_ID ='CA';


DECLARE
EMP_COUNTRY_REC COUNTRIES%ROWTYPE;
BEGIN
SELECT COUNTRY_ID, COUNTRY_NAME, REGION_ID
INTO EMP_COUNTRY_REC
FROM COUNTRIES
WHERE COUNTRY_ID = '&country_id';
DBMS_OUTPUT.PUT_LINE('Country Id:'||EMP_COUNTRY_REC.COUNTRY_ID||' Country
Name:'||EMP_COUNTRY_REC.COUNTRY_NAME||' Region:'||EMP_COUNTRY_REC.REGION_ID);
END;
/
anonymous block completed

* Print total number of departments and department numbers from departments table.

50
PL/SQL Practices

DECLARE
V_COUNT NUMBER(3);
V_DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE;

BEGIN
SELECT DISTINCT COUNT(*)
INTO V_COUNT
FROM DEPARTMENTS;
DBMS_OUTPUT.PUT_LINE('Individual Number of Departments:');
FOR I IN 1..V_COUNT
LOOP
IF I=1 THEN
V_DEPTNO:=10;
ELSIF I=2 THEN
V_DEPTNO:=20;
ELSIF I=3 THEN
V_DEPTNO:=30;
ELSIF I=4 THEN
V_DEPTNO:=40;
ELSIF I=5 THEN
V_DEPTNO:=50;
ELSIF I=6 THEN
V_DEPTNO:=60;
ELSIF I=7 THEN
V_DEPTNO:=70;
ELSIF I=8 THEN
V_DEPTNO:=80;
ELSIF I=9 THEN
V_DEPTNO:=90;
ELSIF I=10 THEN
V_DEPTNO:=100;
ELSIF I=11 THEN
V_DEPTNO:=110;
ELSIF I=12 THEN
V_DEPTNO:=120;
ELSIF I=13 THEN
V_DEPTNO:=130;
ELSIF I=14 THEN
V_DEPTNO:=140;
ELSIF I=15 THEN
V_DEPTNO:=150;
ELSIF I=16 THEN
V_DEPTNO:=160;
ELSIF I=17 THEN
V_DEPTNO:=170;
ELSIF I=18 THEN
V_DEPTNO:=180;
ELSIF I=19 THEN

51
PL/SQL Practices

V_DEPTNO:=190;
ELSIF I=20 THEN
V_DEPTNO:=200;
ELSIF I=21 THEN
V_DEPTNO:=210;
ELSIF I=22 THEN
V_DEPTNO:=220;
ELSIF I=23 THEN
V_DEPTNO:=230;
ELSIF I=24 THEN
V_DEPTNO:=240;
ELSIF I=25 THEN
V_DEPTNO:=250;
ELSIF I=26 THEN
V_DEPTNO:=260;
ELSIF I=27 THEN
V_DEPTNO:=270;
ELSIF I=28 THEN
V_DEPTNO:=271;
END IF;
DBMS_OUTPUT.PUT_LINE(V_DEPTNO);
END LOOP;
DBMS_OUTPUT.PUT_LINE('Total Number of Departments:'||V_COUNT);
END;
/
Individual Number of Departments:
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
160
170
180
190
200
210
220

52
PL/SQL Practices

230
240
250
260
270
271
Total Number of Departments:28

2. Create a PL/SQL block to retrieve the name of each department from the DEPARTMENT table and
print each department name on the screen, incorporating an INDEX BY table.
a. Declare an INDEX BY table, MY_DEPT_TABLE to temporarily store the name of the
departments.
b. Using a loop, retrieve the name of all departments currently in the DEPARTMENTS table and
store them in the INDEX BY table. Use the following table to assign the value for DEPARTMENT_ID
based on the value of the counter used in the loop.
COUNTER DEPARTMENT_ID
1 10
2 20
3 50
4 60
5 80
6 90
7 110

c. Using another loop , retrieve the department names from the INDEX BY table and print them
to the screen, using DBMS_OUTPUT.PUT_LINE. The output from the program is shown on the next page.
DECLARE
TYPE DEPT_TABLE_TYPE IS
TABLE OF DEPARTMENTS%ROWTYPE
INDEX BY BINARY_INTEGER;
MY_DEPT_TABLE DEPT_TABLE_TYPE;
V_COUNT NUMBER(2);
V_DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE;
BEGIN
SELECT COUNT(*)
INTO V_COUNT
FROM DEPARTMENTS;
FOR I IN 1..V_COUNT
LOOP
IF I=1 THEN
V_DEPTNO:=10;
ELSIF I=2 THEN
V_DEPTNO:=20;
ELSIF I=3 THEN
V_DEPTNO:=30;
ELSIF I=4 THEN

53
PL/SQL Practices

V_DEPTNO:=40;
ELSIF I=5 THEN
V_DEPTNO:=50;
ELSIF I=6 THEN
V_DEPTNO:=60;
ELSIF I=7 THEN
V_DEPTNO:=70;
ELSIF I=8 THEN
V_DEPTNO:=80;
ELSIF I=9 THEN
V_DEPTNO:=90;
ELSIF I=10 THEN
V_DEPTNO:=100;
ELSIF I=11 THEN
V_DEPTNO:=110;
ELSIF I=12 THEN
V_DEPTNO:=120;
ELSIF I=13 THEN
V_DEPTNO:=130;
ELSIF I=14 THEN
V_DEPTNO:=140;
ELSIF I=15 THEN
V_DEPTNO:=150;
ELSIF I=16 THEN
V_DEPTNO:=160;
ELSIF I=17 THEN
V_DEPTNO:=170;
ELSIF I=18 THEN
V_DEPTNO:=180;
ELSIF I=19 THEN
V_DEPTNO:=190;
ELSIF I=20 THEN
V_DEPTNO:=200;
ELSIF I=21 THEN
V_DEPTNO:=210;
ELSIF I=22 THEN
V_DEPTNO:=220;
ELSIF I=23 THEN
V_DEPTNO:=230;
ELSIF I=24 THEN
V_DEPTNO:=240;
ELSIF I=25 THEN
V_DEPTNO:=250;
ELSIF I=26 THEN
V_DEPTNO:=260;
ELSIF I=27THEN
V_DEPTNO:=270;
ELSIF I=28 THEN

54
PL/SQL Practices

V_DEPTNO:=271;
END IF;
SELECT *
INTO MY_DEPT_TABLE(I)
FROM DEPARTMENTS
WHERE DEPARTMENT_ID = V_DEPTNO;
END LOOP;
FOR I IN 1..V_COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('Department Number:'||MY_DEPT_TABLE(I).DEPARTMENT_ID
||',Department Name:'||MY_DEPT_TABLE(I).DEPARTMENT_NAME
);
END LOOP;
END;
/
anonymous block completed

3.Write a PL/SQL block to retrieve all information about each department from the DEPARTMENTS table
and print the information to the screen , incorporating an INDEX BY table of records.
Declare an INDEX BY table, MY_DEPT_TABLE to temporarily store the number, name and location of all
departments .

DECLARE
TYPE DEPT_TABLE_TYPE IS
TABLE OF DEPARTMENTS%ROWTYPE
INDEX BY BINARY_INTEGER;
MY_DEPT_TABLE DEPT_TABLE_TYPE;
V_COUNT NUMBER(2):=7;
V_DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE;
BEGIN
SELECT COUNT(*)
INTO V_COUNT
FROM DEPARTMENTS;
FOR I IN 1..V_COUNT
LOOP
IF I=1 THEN
V_DEPTNO:=10;
ELSIF I=2 THEN
V_DEPTNO:=20;
ELSIF I=3 THEN
V_DEPTNO:=30;
ELSIF I=4 THEN
V_DEPTNO:=40;
ELSIF I=5 THEN
V_DEPTNO:=50;
ELSIF I=6 THEN
V_DEPTNO:=60;
ELSIF I=7 THEN

55
PL/SQL Practices

V_DEPTNO:=70;
END IF;
SELECT *
INTO MY_DEPT_TABLE(I)
FROM DEPARTMENTS
WHERE DEPARTMENT_ID = V_DEPTNO;
END LOOP;
FOR I IN 1..V_COUNT
LOOP
DBMS_OUTPUT.PUT_LINE('Department Number:'||MY_DEPT_TABLE(I).DEPARTMENT_ID
||',Department Name:'||MY_DEPT_TABLE(I).DEPARTMENT_NAME
||',Manager Id:'||MY_DEPT_TABLE(I).MANAGER_ID
||',Location Id:'||MY_DEPT_TABLE(I).LOCATION_ID);
END LOOP;
END;
/
anonymous block completed

Extra Notes:

Collection Methods
PL/SQL provides the built-in collection methods that make collections easier to use.
The following table lists the methods and their purpose −

S.No Method Name & Purpose

EXISTS(n)
1
Returns TRUE if the nth element in a collection exists; otherwise
returns FALSE.

COUNT
2 Returns the number of elements that a collection currently
contains.

LIMIT
3
Checks the maximum size of a collection.

56
PL/SQL Practices

FIRST
4 Returns the first (smallest) index numbers in a collection that uses
the integer subscripts.

LAST
5 Returns the last (largest) index numbers in a collection that uses
the integer subscripts.

PRIOR(n)
6
Returns the index number that precedes index n in a collection.

NEXT(n)
7
Returns the index number that succeeds index n.

EXTEND
8
Appends one null element to a collection.

EXTEND(n)
9
Appends n null elements to a collection.

EXTEND(n,i)
10
Appends n copies of the ith element to a collection.

TRIM
11
Removes one element from the end of a collection.

TRIM(n)
12
Removes n elements from the end of a collection.

13 DELETE

57
PL/SQL Practices

Removes all elements from a collection, setting COUNT to 0.

DELETE(n)

Removes the nth element from an associative array with a numeric


14 key or a nested table. If the associative array has a string key, the
element corresponding to the key value is deleted. If n is
null, DELETE(n) does nothing.

DELETE(m,n)

Removes all elements in the range m..n from an associative array


15
or nested table. If m is larger than n or if m or n is
null, DELETE(m,n) does nothing.

Collection Exceptions
The following table provides the collection exceptions and when they are raised −

Collection Exception Raised in Situations

COLLECTION_IS_NULL You try to operate on an atomically


null collection.

A subscript designates an element that


NO_DATA_FOUND was deleted, or a nonexistent element
of an associative array.

SUBSCRIPT_BEYOND_COUNT A subscript exceeds the number of


elements in a collection.

SUBSCRIPT_OUTSIDE_LIMIT A subscript is outside the allowed


range.

A subscript is null or not convertible


VALUE_ERROR
to the key type. This exception might
occur if the key is defined as

58
PL/SQL Practices

a PLS_INTEGER range, and the


subscript is outside this range.

Example
Following example shows how to create a table to store integer values along with
names and later it prints the same list of names.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000;
salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000;
salary_list('James') := 78000;

-- printing the table


name := salary_list.FIRST;
WHILE name IS NOT null LOOP
dbms_output.put_line
('Salary of ' || name || ' is ' || TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
When the above code is executed at the SQL prompt, it produces the following result −

Salary of James is 78000


Salary of Martin is 100000
Salary of Minakshi is 75000
Salary of Rajnish is 62000

Example
Elements of an index-by table could also be a %ROWTYPE of any database table
or %TYPE of any database table field. The following example illustrates the concept.
We will use theCUSTOMERS table stored in our database as −

Select * from customers;

+----+----------+-----+-----------+----------+
| ID | NAME | AGE | ADDRESS | SALARY |

59
PL/SQL Practices

+----+----------+-----+-----------+----------+
| 1 | Ramesh | 32 | Ahmedabad | 2000.00 |
| 2 | Khilan | 25 | Delhi | 1500.00 |
| 3 | kaushik | 23 | Kota | 2000.00 |
| 4 | Chaitali | 25 | Mumbai | 6500.00 |
| 5 | Hardik | 27 | Bhopal | 8500.00 |
| 6 | Komal | 22 | MP | 4500.00 |
+----+----------+-----+-----------+----------+
DECLARE
CURSOR c_customers is
select name from customers;

TYPE c_list IS TABLE of customers.Name%type INDEX BY binary_integer;


name_list c_list;
counter integer :=0;
BEGIN
FOR n IN c_customers LOOP
counter := counter +1;
name_list(counter) := n.name;
dbms_output.put_line('Customer('||counter||'):'||name_lis t(counter));
END LOOP;
END;
/
When the above code is executed at the SQL prompt, it produces the following result −

Customer(1): Ramesh
Customer(2): Khilan
Customer(3): kaushik
Customer(4): Chaitali
Customer(5): Hardik
Customer(6): Komal

Nested Tables
A nested table is like a one-dimensional array with an arbitrary number of elements.
However, a nested table differs from an array in the following aspects −

 An array has a declared number of elements, but a nested table does not. The
size of a nested table can increase dynamically.

60
PL/SQL Practices

 An array is always dense, i.e., it always has consecutive subscripts. A nested


array is dense initially, but it can become sparse when elements are deleted
from it.

A nested table is created using the following syntax −

TYPE type_name IS TABLE OF element_type [NOT NULL];


table_name type_name;

This declaration is similar to the declaration of an index-by table, but there is


no INDEX BY clause.

A nested table can be stored in a database column. It can further be used for
simplifying SQL operations where you join a single-column table with a larger table. An
associative array cannot be stored in the database.

Example
The following examples illustrate the use of nested table −
DECLARE
TYPE names_table IS TABLE OF VARCHAR2(10);
TYPE grades IS TABLE OF INTEGER;
names names_table;
marks grades;
total integer;
BEGIN
names := names_table('Kavita', 'Pritam', 'Ayan', 'Rishav', 'Aziz');
marks:= grades(98, 97, 78, 87, 92);
total := names.count;
dbms_output.put_line('Total '|| total || ' Students');
FOR i IN 1 .. total LOOP
dbms_output.put_line('Student:'||names(i)||', Marks:' || marks(i));
end loop;
END;
/
When the above code is executed at the SQL prompt, it produces the following result −

Total 5 Students
Student:Kavita, Marks:98
Student:Pritam, Marks:97
Student:Ayan, Marks:78
Student:Rishav, Marks:87
Student:Aziz, Marks:92
PL/SQL procedure successfully completed.

61
PL/SQL Practices

Example
Elements of a nested table can also be a %ROWTYPE of any database table or
%TYPE of any database table field. The following example illustrates the concept. We
will use the CUSTOMERS table stored in our database as −

Select * from customers;


+----+----------+-----+-----------+----------+
| ID | NAME | AGE | ADDRESS | SALARY |
+----+----------+-----+-----------+----------+
| 1 | Ramesh | 32 | Ahmedabad | 2000.00 |
| 2 | Khilan | 25 | Delhi | 1500.00 |
| 3 | kaushik | 23 | Kota | 2000.00 |
| 4 | Chaitali | 25 | Mumbai | 6500.00 |
| 5 | Hardik | 27 | Bhopal | 8500.00 |
| 6 | Komal | 22 | MP | 4500.00 |
+----+----------+-----+-----------+----------+
DECLARE
CURSOR c_customers is
SELECT name FROM customers;
TYPE c_list IS TABLE of customerS.No.ame%type;
name_list c_list := c_list();
counter integer :=0;
BEGIN
FOR n IN c_customers LOOP
counter := counter +1;
name_list.extend;
name_list(counter) := n.name;
dbms_output.put_line('Customer('||counter||'):'||name_list(counter));
END LOOP;
END;
/
When the above code is executed at the SQL prompt, it produces the following result −

Customer(1): Ramesh
Customer(2): Khilan
Customer(3): kaushik
Customer(4): Chaitali
Customer(5): Hardik
Customer(6): Komal
PL/SQL procedure successfully completed.

So PL/SQL provides three collection types −

62
PL/SQL Practices

 Index-by tables or Associative array

 Nested table

 Variable-size array or Varray

Oracle documentation provides the following characteristics for each type of collections

Can Be
Dense
Collection Number of Subscript Where Object
or
Type Elements Type Created Type
Sparse
Attribute

Associative
Only in
array (or String or
Unbounded Either PL/SQL No
index-by integer
block
table)

Either in
Starts
PL/SQL
dense,
block or
Nested table Unbounded Integer can Yes
at
become
schema
sparse
level

Either in
PL/SQL
Variablesize
Always block or
array Bounded Integer Yes
dense at
(Varray)
schema
level

We have already discussed varray in the chapter 'PL/SQL arrays'. In this chapter, we
discussed the PL/SQL tables. Both types of PL/SQL tables, i.e., the index-by tables and
the nested tables have the same structure and their rows are accessed using the
subscript notation. However, these two types of tables differ in one aspect; the nested
tables can be stored in a database column and the index-by tables cannot.
What is a PL/SQL Record

A PL/SQL record is a composite data structure that is a group of related data stored
in fields. Each field in the PL/SQL record has its own name and data type.

63
PL/SQL Practices

Declaring a PL/SQL Record

PL/SQL provides three ways to declare a record: table-based record, cursor-based


record and programmer-defined records.

1.Declaring Table-based Record

To declare a table-based record you use a table name with %ROWTYPE attribute. The fields of
the PL/SQL record has the same name and data type as the column of the table.
The following illustrates table-based record declaration:

Syntax : record_name table_name%ROWTYPE ;

DECLARE
table_based_record table_name%ROWTYPE ;
After having the table-based record, you can use it in various ways, for example in SQL SELECT
statement as follows:
SET SERVEROUTPUT ON SIZE 1000000;
1 DECLARE
2 emp_record employees%ROWTYPE;
3
4
v_empid employees.employee_id%TYPE := 200 ;
5 BEGIN
6 SELECT *
7 INTO r_emp
8 FROM employees
9
10 WHERE employee_id = v_empid ;
11 -- print out the employee's first name
12 DBMS_OUTPUT.PUT_LINE(r_emp.first_name) ;
13 END;
/
In the above example:

 First, we defined a record based on employees table in HR sample database.


 Second, we used the SELECT statement to retrieve the employee information of the
employee id 200 and populate the data into the r_emp record .
 Third, we print out the first name of the selected employee from the r_emp employee
record.
Example - 2

DECLARE
EMP_RECORD EMPLOYEES%ROWTYPE;
BEGIN
SELECT *

64
PL/SQL Practices

INTO EMP_RECORD
FROM EMPLOYEES
WHERE EMPLOYEE_ID=&EMP_ID;
DBMS_OUTPUT.PUT_LINE(EMP_RECORD.FIRST_NAME||' '||EMP_RECORD.LAST_NAME||'
SALARY:'||EMP_RECORD.SALARY);
END;
/
Steven King SALARY:24000

2.Declaring Programmer-defined Record

To declare programmer-defined record, first you have to define a record type by using
TYPE statement with the fields of record explicitly. Then, you can declare a record based on
record type that you’ve defined.
The following illustrates the syntax of the defining programmer-defined record with TYPE
statement:
Syntax:
TYPE type_name IS RECORD
1
2 (field1 data_type1 [NOT NULL] := [DEFAULT VALUE],
3 field2 data_type2 [NOT NULL] := [DEFAULT VALUE],
4 ...
5 fieldn data_type3 [NOT NULL] := [DEFAULT VALUE]
6
);

The data type of field can be any of the following:

 Scalar type ( VARCHAR2, NUMBER…).


 Anchor declaration %TYPE.
 %ROW type, in this case we have a nested record.
 SUBTYPE
 PL/SQL collection types.

 Cursor variable REF CURSOR .


Once you define the record type, you can declare a record based on the record type as
follows:

Syntax:

1 record_name type_name ;
The following example demonstrates how to declare programmer-defined record:

1 SET SERVEROUTPUT ON SIZE 1000000 ;

65
PL/SQL Practices

2 DECLARE
3 TYPE t_name IS RECORD
4
5 (
6 V_first_name employees.first_name%TYPE,
7 V_last_name employees.last_name%TYPE,
8 v_sal employees.salary%type
9
10
);
11 r_name t_name; -- name record
12 v_emp_id employees.employee_id%TYPE := 200;
13 BEGIN
14 SELECT first_name, last_name , salary
15
16 INTO r_name
17 FROM employees
18 WHERE employee_id = v_emp_id;
-- print out the employee's name
DBMS_OUTPUT.PUT_LINE(r_name.v_first_name || ' ' ||
r_name.v_last_name||',Salary:'||r_name.v_sal );
END;
/
Anonymous block completed
Jennifer Whalen,Salary:4400
3.Declaring Cursor-based Record
You can define a record based on a cursor. First, you must define a cursor. And then you use
%ROWTYPE with the cursor variable to declare a record. The fields of the record correspond to
the columns in the cursor SELECT statement.
The following is an example of declaring a record based on a cursor.
SET SERVEROUTPUT ON SIZE 1000000;
1
2
DECLARE
3 CURSOR cur_emp IS
4 SELECT *
5 FROM employees
6
WHERE employee_id = 200 ;
7
8 emp_rec cur_emp%ROWTYPE;
9 BEGIN
10 NULL;
11 END;
12
/
Anonymous block completed
Note: Results no output , as there is null in between begin and end.

Working with PL/SQL Record

After having a PL/SQL record, you can work with a record as a whole or you can work with
individual field of the record.

66
PL/SQL Practices

Working with PL/SQL record at record level

At record level, you can do the following:

 You can assign a PL/SQL record to another PL/SQL record. The pair of PL/SQL
records must have the same number of fields and the data type of each field has to be
convertible.

 You can assign a PL/SQL record NULL value by assigning an uninitialized record.
 A PL/SQL record can be used as an argument of parameter in a function

 You can return a PL/SQL record from a function

 To check if the record is NULL, you have to check each individual field of the record.

 To compare two records, you have to compare each individual field of each record.

* Here is an example of working with PL/SQL record at record level:

1
2 DECLARE
3 TYPE t_name IS RECORD
4 (
5 V_FIRST_NAME EMPLOYEES.FIRST_NAME%TYPE,
6
7
V_LAST_NAME EMPLOYEES.LAST_NAME%TYPE,
8 V_SAL EMPLOYEES.SALARY%TYPE
9 );
10 r_name t_name;
11
r_name2 t_name;
12
13 R_NAME_NULL T_NAME;
14 v_emp_id employees.employee_id%TYPE := &EMPLOYEE_ID;
15 BEGIN
16 -- assign employee's infomation to record
17
18
SELECT first_name, last_name, salary
19 INTO r_name
20 FROM EMPLOYEES
21 WHERE employee_id = v_emp_id;
22
23
24 -- assign record to another record
25 r_name2 := r_name;
26 -- print out the employee's name
27 DBMS_OUTPUT.PUT_LINE(r_name2.v_first_name || ',' || r_name2.v_last_name||' and
28
29
Salary:'||r_name2.v_sal);
30
31 -- assign record to NULL
32 r_name2 := r_name_null;
33
34

67
PL/SQL Practices

-- check NULL for each individual field


IF R_NAME2.V_FIRST_NAME IS NULL AND
r_name2.v_last_name IS NULL THEN
DBMS_OUTPUT.PUT_LINE('Record r_name2 is NULL');
END IF;

END;
/
Anonymous block completed
Steven,King and Salary:24000
Record r_name2 is NULL
Working with PL/SQL record at field level

As you see in the above example, we can reference to a field of a record by using dot
notation (.) as follows:

1 record_name . field
If you reference to a record variable in different package or schema you need to explicitly
specify those information as shown below:

1 [schema_name.][package_name.]record_name.field
You can use the assignment operator ( :=) to change the value of field of a record that you
reference to.
For the nested record you need to use extra dot notation ( .)
The following example demonstrates how to use PL/SQL record a field level:

1 DECLARE
2 TYPE TYPE_ADDRESS IS RECORD
3
4 (
5 HOUSE_NUMBER VARCHAR2(7),
6 STREET VARCHAR2(50),
7 PHONE VARCHAR2(15),
8
9
REGION VARCHAR2(10),
10 POSTAL_CODE VARCHAR2(10),
11 COUNTRY VARCHAR2(25)
12 );
13
14
15 TYPE TYPE_CONTACT IS RECORD
16 (
17 HOME TYPE_ADDRESS,
18 BUSINESS TYPE_ADDRESS
19
20
);
21
22 RECORD_CONTACT TYPE_CONTACT;

68
PL/SQL Practices

23 BEGIN
RECORD_CONTACT.BUSINESS.HOUSE_NUMBER:='729';
RECORD_CONTACT.BUSINESS.STREET:='Oracle Parkway';
RECORD_CONTACT.BUSINESS.REGION:='CA';
RECORD_CONTACT.BUSINESS.POSTAL_CODE:='+91';
RECORD_CONTACT.BUSINESS.COUNTRY:='USA';
RECORD_CONTACT.BUSINESS.PHONE:='+1.800.223.1177';
DBMS_OUTPUT.PUT_LINE(RECORD_CONTACT.BUSINESS.HOUSE_NUMBER||'
'||RECORD_CONTACT.BUSINESS.STREET
||' '||RECORD_CONTACT.BUSINESS.REGION||'
'||RECORD_CONTACT.BUSINESS.COUNTRY);
END;
/
Anonymous block completed
729 Oracle Parkway CA USA

In this tutorial, you’ve learned how to use PL/SQL record to manipulate data more
efficiently, and to make your code cleaner and easier to maintain.

Short Notes:
 PL/SQL makes it very easy to declare records that have the same structure as a table, a view, or the result
set of a cursor by offering the %ROWTYPE attribute.
 Use %ROWTYPE to declare a record that has the same structure as a SELECT statement in a cursor. This
is especially helpful for fetching either a subset of columns from a table or columns from multiple tables.
Here’s an example:

DECLARE
CURSOR emp_cursor IS
SELECT last_name, salary
FROM employees;
record_emp emp_cursor %ROWTYPE;
 Declare a record as follows : emp_record employees%ROWTYPE;
Extra Activity:
1. Declare a record with the same structure as Employees table , the fill the record with the
content of one row from the table and the show the full name of the employee whose
employee id is to be supplied by a substitution variable.

DECLARE
EMP_REC EMPLOYEES%ROWTYPE;
BEGIN
SELECT *
INTO EMP_REC
FROM EMPLOYEES
WHERE EMPLOYEE_ID=&P_EMP_ID;
DBMS_OUTPUT.PUT_LINE(EMP_REC.FIRST_NAME||' '||EMP_REC.LAST_NAME);
END;

69
PL/SQL Practices

/
anonymous block completed
Steven King
[Note : 100 is supplied at P_EMP_ID ]

2.Declare a cursor that fetches the last name and salary of all employees. Then use %ROWTYPE
to declare a record that contains two fields: employee.last_name and employee.salary. Finally,
open the cursor, fetch one row into the record, and close the cursor.

DECLARE
CURSOR EMP_CURSOR IS
SELECT LAST_NAME, SALARY
FROM EMPLOYEES;
EMP_RECORD EMP_CURSOR%ROWTYPE;
BEGIN
OPEN EMP_CURSOR;
FETCH EMP_CURSOR INTO EMP_RECORD;
CLOSE EMP_CURSOR;
END;
/
anonymous block completed

3.Assign one record to another. Oracle Database supports record-level assignments, even the
assignment of NULL to a record.
DECLARE
old_employee employees%ROWTYPE;
new_employee employees%ROWTYPE;
BEGIN
new_employee := old_employee;
old_employee := NULL;
END;
/
anonymous block completed

* The cursor FOR loop is a variation on the numeric FOR loop, which looks like this:

FOR index IN low_value .. high_value


LOOP
loop_body_statements
END LOOP;

The index is implicitly declared by Oracle Database as an integer and can be


referenced only inside the body of this loop.

A cursor FOR loop has a similar structure but replaces a numeric range with a query:

70
PL/SQL Practices

FOR index IN ( SELECT_statement )


LOOP
loop_body_statements
END LOOP;

Oracle Database also implicitly declares this loop index as well, but in the case of a
cursor FOR loop, it declares the index as a record by using %ROWTYPE against the
query in the loop header.

* The following block uses a cursor FOR loop to fetch only the last name of each
employee, deposit that name into a record, and then display the value of the last_name
field of that record:

BEGIN
FOR employee_rec
IN (SELECT last_name
FROM employees
ORDER BY last_name)
LOOP
DBMS_OUTPUT.put_line (
employee_rec.last_name);
END LOOP;
END;
/
anonymous block completed
BEGIN
FOR EMPLOYEE_REC
IN (SELECT last_name||' '||first_name ename
FROM employees
ORDER BY last_name)
LOOP
DBMS_OUTPUT.PUT_LINE (
employee_rec.ename);
END LOOP;
END;
/
anonymous block completed

Inserting and Updating with Records

As you have seen, PL/SQL makes it very easy to populate a record from a row in a table. But
what if you want to change the contents of a row in a table by using a record? PL/SQL offers

71
PL/SQL Practices

special syntax in both the INSERT and UPDATE statements so that you can easily use records to
perform those data manipulation language (DML) operations as well.
The most common form of an INSERT statement is

INSERT INTO table_name (column_list)


VALUES (expression_list)

If I want to provide a value for each column in a table that has, say, 500 columns, writing and
managing that code can become quite tedious. Inserting with a record comes in very handy in
such a scenario.
Code Listing 1: Insert of a single row with each column specified
DECLARE
v_employee_id employees.employee_id%TYPE := 500;
v_last_name employees.last_name%TYPE := 'Mondrian';
v_salary employees.salary%TYPE := 2000;
BEGIN
INSERT INTO EMPLOYEES (employee_id, last_name, salary)
VALUES ( v_employee_id, v_last_name, v_salary);
END;
/

To perform a record-level insert, simply leave off the parentheses around the record in the
VALUES clause. Listing 1 demonstrates an insert of a single row into the employees table that
specifies each column individually. The following demonstrates the same insert, using a record:

DECLARE
v_employee employees%ROWTYPE;
BEGIN
v_employee.employee_id := 500;
v_employee.last_name := 'Mondrian';
v_employee.salary := 2000;

INSERT INTO employees


VALUES v_employee;
END;
/

So if you ever find yourself typing what feels like an endless list of variables in the VALUES
clause of your INSERT statement, try using a record instead.
For updates, use SET ROW to update all the columns in a row from the record:

DECLARE
v_employee copy_employees%ROWTYPE;
BEGIN

72
PL/SQL Practices

v_employee.employee_id := 106;
v_employee.last_name := 'Mondrian';
V_EMPLOYEE.SALARY := 2000;
V_EMPLOYEE.EMAIL:='DMONDRIAN';
V_EMPLOYEE.HIRE_DATE:='25-MAY-18';
v_employee.JOB_ID:='IT_PROG';

UPDATE copy_employees
SET ROW = v_employee
WHERE employee_id = 106;
END;
/
Note:Remember: this UPDATE sets the value of every column in the table, including your primary key,
so you should use the SET ROW syntax with great care.

What is cursor?
Every SQL statement executed by the Oracle Server has an individual cursor associated with it.
Two types of cursors - 1. Implicit Cursors , 2. Explicit Cursors.
Implicit and Explicit Cursors:
The Oracle Server uses work areas, called private SQL areas , to execute SQL statements and to store
processing information.
Implicit cursors are declared by PL/SQL implicitly for all DML and PL/SQL SELECT statements, including
queries that return only one row.
Explicit cursors are used for the queries that return more than one row, explicit cursors are declared
and named by the programmer and manipulated through specific statements in the block's executable
actions.

Controlling Explicit Cursors


________
| Declare |-->Create a named SQL area.
--------------
|
_________
| Open |-->Identify the active set.
---------------
|
__________
| Fetch |--->Load the current row into variables.
----------------
|
__________
| Empty ? |--->Test for existing rows. Return to FETCH if rows are found.
----------------
|
-----------------
| Close |-->Release the active set.
----------------

73
PL/SQL Practices

1.Declaring The Cursor:


CURSOR cursor_name is
select_statement ;
[ Note : Do not include the INTO clause in the cursor declaration]

Example:

DELCARE
V_EMPNO EMPLOYEES.EMPLOYEE_ID%TYPE;
V_ENAME EMPLOYEES.LAST_NAME%TYPE;
CURSOR emp_cursor IS
SELECT employee_id, last_name
FROM employees;
CURSOR dept_cursor IS
SELECT *
FROM departments
WHERE location_id = 170;
BEGIN
...........................

2.Opening the Cursor:


OPEN cursor_name ;
 Open the cursor to execute the query and identify the active set.
 If the query returns no rows, no exception is raised.
 Use cursor attributes to test the outcome after a fetch.
[Note : If the query returns no rows when the cursor is opened, PL/SQL does not raise an exception .
However, you can test the status of the cursor after a fetch using the SQL%ROWCOUNT cursor
attribute.]

3.Fetching Data from the Cursor:


FETCH cursor_name
INTO [variable1, variable2,...........variableN] | record_name ];

 Retrieve the current row values into variables.


 Include the same number of variables.
 Match each variable to correspond to the columns positionally.
 Test to see whether the cursor contains rows.
Example : Print first 19 employees employee number and last name from employees table.
DECLARE
V_EMPNO EMPLOYEES.EMPLOYEE_ID%TYPE;
V_ENAME EMPLOYEES.LAST_NAME%TYPE;
CURSOR EMP_CURSOR IS
SELECT EMPLOYEE_ID, LAST_NAME
FROM EMPLOYEES
ORDER BY EMPLOYEE_ID;
BEGIN
OPEN EMP_CURSOR;

74
PL/SQL Practices

FOR I IN 1..19
LOOP
FETCH EMP_CURSOR
INTO V_EMPNO, V_ENAME ;
EXIT WHEN EMP_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(TO_CHAR(V_EMPNO)||' '||V_ENAME);
END LOOP;
END;
/
anonymous block completed

Example 2: Print employee number and last name of all employees from employees table.
DECLARE
V_EMPNO EMPLOYEES.EMPLOYEE_ID%TYPE;
V_ENAME EMPLOYEES.LAST_NAME%TYPE;
V_COUNT NUMBER(3);
CURSOR EMP_CURSOR IS
SELECT EMPLOYEE_ID, LAST_NAME
FROM EMPLOYEES
ORDER BY EMPLOYEE_ID;
BEGIN
SELECT COUNT(EMPLOYEE_ID)
INTO V_COUNT
FROM EMPLOYEES;
OPEN EMP_CURSOR;
FOR I IN 1..V_COUNT
LOOP
FETCH EMP_CURSOR
INTO V_EMPNO, V_ENAME ;
EXIT WHEN EMP_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(TO_CHAR(V_EMPNO)||' '||V_ENAME);
END LOOP;
END;
/
anonymous block completed

4.Closing the Cursor


CLOSE cursor_name ;
For example : OPEN emp_cursor
FOR i IN 1..19 LOOP
FETCH emp_cursor INTO v_empno, v_ename ;
..............
END LOOP ;
CLOSE emp_cursor ;
END ;

Explicit Cursor Attributes :


Attributes to obtain status information about a cursor:

75
PL/SQL Practices

Attribute Type Description


%ISOPEN Boolean Evaluates to TRUE if the cursor is open.
%NOTFOUND Boolean Evaluates to TRUE if the most recent fetch does not return a row
%FOUND Boolean Evaluates to TRUE if the most recent fetch returns a row;
Complement of %NOTFOUND
%ROWCOUNT Number Evaluates to the total number of rows returned so far.

The %ISOPEN Attribute


 Fetch rows only when the cursor is open.
 Use the %ISOPEN cursor attribute before performing a fetch to test whenever the cursor is open
Example:
IF NOT emp_cursor%ISOPEN THEN
OPEN emp_cursor ;
END IF ;
LOOP
FETCH emp_cursor...
Note : You can fetch rows only when the cursor is open. Use %ISOPEN cursor attribute to determine
whether the cursor is open. Fetch rows in a loop. Use cursor attribute to determine when to exit the
loop.
The %NOTFOUND Attribute
 %NOTFOUND is the logical opposite of %FOUND. %NOTFOUND yields FALSE if the last fetch
returned a row, or TRUE if the last fetch failed to return a row.
 Example of %NOTFOUND to exit a loop when FETCH fails to return a row:
LOOP
FETCH emp_cursor INTO last_name, salary, hire_date ;
EXIT WHEN emp_cursor%NOTFOUND ;
..........
END LOOP;
[Note: if a cursor is not open, referencing it with %NOTFOUND raises INVALID_CURSOR]
The %ROWCOUNT Attribute
 Use the %ROWCOUNT cursor attribute to retrieve an exact number of rows.
When a cursor or cursor variable is opened , %ROWCOUNT is zeroed. Before the first fetch
%ROWCOUNT yields 0. Thereafter it yields the number of rows fetched so far. The number is
incremented if the last fetch returned a row.
Example:
LOOP
FETCH emp_cursor INTO last_name, salary, hire_date ;
IF emp_cursro%ROWCOUNT >10 THEN
....
END IF;
....
END LOOP ;
[Note: if a cursor is not open, referencing it with %ROWCOUNT raises INVALID_CURSOR]
Example : Print first 11 employee Id with last name from employees table using cursor.
DECLARE
V_EMPID EMPLOYEES.EMPLOYEE_ID%TYPE;
V_ENAME EMPLOYEES.LAST_NAME%TYPE;

76
PL/SQL Practices

CURSOR EMP_CURSOR IS
SELECT EMPLOYEE_ID, LAST_NAME
FROM EMPLOYEES
ORDER BY EMPLOYEE_ID;
BEGIN
OPEN EMP_CURSOR;
LOOP
FETCH EMP_CURSOR INTO V_EMPID, V_ENAME ;
EXIT WHEN EMP_CURSOR%ROWCOUNT >11
OR EMP_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(V_EMPID||' '||V_ENAME);
END LOOP;
CLOSE EMP_CURSOR;
END;
/
Note : Before the first fetch , %NOTFOUND evaluates to NULL, So if FETCH never executes successfully ,
the loop is never exit. That is because the EXIT WHEN statement executes only if its WHEN condition
is true. To be safe, use the following EXIT statement:
EXIT WHEN emp_cursor%NOTFOUND
OR emp_cursor%NOTFOUND IS NULL ;

* Create a table name TEMP_LIST with two columns EMP_ID , EMP_NAME. Use a cursor to retrieve
employee numbers and names of those employees who get salary 15000 or more and populate the
database table TEMP_LIST with the information.

CREATE TABLE TEMP_LIST


(
EMP_ID VARCHAR2(3),
EMP_NAME varchar2(25)
);

[Note : You cannot use EMPLOYEES.EMPLOYEE_ID%TYPE while creating a table ]

DECLARE
CURSOR EMP_CURSOR IS
SELECT EMPLOYEE_ID, FIRST_NAME||' '||LAST_NAME ENAME
FROM EMPLOYEES
WHERE SALARY >=15000;
EMP_RECORD EMP_CURSOR%ROWTYPE;
BEGIN
OPEN EMP_CURSOR;
LOOP
FETCH EMP_CURSOR INTO EMP_RECORD;
EXIT WHEN EMP_CURSOR%NOTFOUND
OR EMP_CURSOR%NOTFOUND IS NULL;
INSERT INTO TEMP_LIST (EMP_ID, EMP_NAME)
VALUES (EMP_RECORD.EMPLOYEE_ID, EMP_RECORD.ENAME);
END LOOP;

77
PL/SQL Practices

COMMIT;
CLOSE EMP_CURSOR;
END;
/
anonymous block completed

In the example first created a cursor and then created a record of the cursor type. Then open the cursor
and within loop fetched the cursor into the record type and using the record , inserted data into the
table.

Using FOR LOOP in cursor:


Syntax : FOR record_name IN curosr_name LOOP
statement1 ;
statementN;
END LOOP ;
In the Syntax :
record_name is the name of implicitly declared record.
cursor_name is a PL/SQL identifier for the previously declared cursor.

*Retrieve employees one by one and print out a list of those employees currently working in the sales
department (Department Id 80).

DECLARE
CURSOR EMP_CURSOR IS
SELECT FIRST_NAME||' '||LAST_NAME ENAME, DEPARTMENT_ID
FROM EMPLOYEES;
BEGIN
FOR EMP_RECORD IN EMP_CURSOR
LOOP
IF EMP_RECORD.DEPARTMENT_ID=80 THEN
DBMS_OUTPUT.PUT_LINE('Employee:'||EMP_RECORD.ENAME||' works in the Sales department.');
END IF;
EXIT WHEN EMP_CURSOR%NOTFOUND;
END LOOP;
CLOSE EMP_CURSOR;
END;
/
anonymous block completed
[ Note : no exit cursor statement has been used , because LOOP will continue till EMP_CURSOR has a
value when no value then the FOR LOOP will exit automatically ]

Cursor FOR LOOP Subqueries :


 When you use a subquery in a FOR LOOP , there is no need to declare a cursor.
Example:
*Retrieve employees one by one and print out a list of those employees currently working in the sales
department (Department Id 80).

BEGIN

78
PL/SQL Practices

FOR EMP_RECORD IN
( SELECT FIRST_NAME||' '||LAST_NAME ENAME, DEPARTMENT_ID
FROM EMPLOYEES )
LOOP
--implicit open and implict fetch cursor
IF EMP_RECORD.DEPARTMENT_ID=80 THEN
DBMS_OUTPUT.PUT_LINE('Employee '||EMP_RECORD.ENAME||' works in sales department');
END IF;
END LOOP;
END;
/
anonymous block completed

*Retrieve the first five employees with a job history.


BEGIN
FOR EMP_RECORD IN
(SELECT *
FROM JOB_HISTORY
WHERE ROWNUM<=5
ORDER BY EMPLOYEE_ID )

LOOP
IF EMP_RECORD.EMPLOYEE_ID <=122 THEN

DBMS_OUTPUT.PUT_LINE('Employee Id '||EMP_RECORD.EMPLOYEE_ID||' held the job of


'||EMP_RECORD.JOB_ID||' from '||EMP_RECORD.START_DATE||' to '||EMP_RECORD.END_DATE||' at
department '||EMP_RECORD.DEPARTMENT_ID);

END IF;
END LOOP;
END;
/
anonymous block completed

( use rownum to retrieve the job_history of first five employees)

WITH JOB_HISTRY_QUERY AS
(SELECT *
FROM JOB_HISTORY
ORDER BY EMPLOYEE_ID )
SELECT *
FROM JOB_HISTORY
WHERE ROWNUM <=5

or solving the program using cursor:


79
PL/SQL Practices

DECLARE
V_EMPID JOB_HISTORY.EMPLOYEE_ID%TYPE;
V_STDATE JOB_HISTORY.START_DATE%TYPE;
V_ENDDATE JOB_HISTORY.END_DATE%TYPE;
V_JOBID JOB_HISTORY.JOB_ID%TYPE;
V_DEPTID JOB_HISTORY.DEPARTMENT_ID%TYPE;

CURSOR EMP_CURSOR IS
SELECT *
FROM JOB_HISTORY
ORDER BY EMPLOYEE_ID;

BEGIN
OPEN EMP_CURSOR;
LOOP
FETCH EMP_CURSOR INTO
V_EMPID, V_STDATE, V_ENDDATE, V_JOBID, V_DEPTID;
EXIT WHEN EMP_CURSOR%ROWCOUNT >5
OR EMP_CURSOR%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Employee ID '||V_EMPID||' held the job '||V_JOBID||' from '||
v_stdate||' to '||v_enddate||' at department ID '||v_deptid);

END LOOP;
CLOSE EMP_CURSOR;
END;
/
anonymous block completed

Practice - 6
1. Create a table for storing the salaries of Employees.
CREATE TABLE TOP_DOGS
( SALARY NUMBER(8,2) ) ;
-a. Accept a number n from the user where n represents the number of top n earners from the
Employees table. For example , to view the top five earners enter 5.
-b. In a LOOP use the substitution variable parameter created in step 1 and gather the salaries of the top
n people from the EMPLOYEES table. There should be no duplication in the salaries. If two employees
earn the same salary , salary should picked up only once.
-c. Store the salaries in the TOP_DOGS table.
-d. Test the output by showing top five salaries from Employees table.

Using SQL Command to store all salaries from Employees table in descending order :
CREATE TABLE SAL_SORT
AS SELECT DISTINCT SALARY
FROM EMPLOYEES
ORDER BY SALARY DESC;

80
PL/SQL Practices

DECLARE
V_NUM NUMBER(3):=&P_NUM;
V_SAL TOP_DOGS.SALARY%TYPE;
CURSOR TOPSAL_CURSOR IS
SELECT DISTINCT SALARY
FROM EMPLOYEES
ORDER BY SALARY DESC ;
BEGIN
OPEN TOPSAL_CURSOR;
LOOP
FETCH TOPSAL_CURSOR INTO V_SAL;
INSERT INTO TOP_DOGS(SALARY)
VALUES (V_SAL);
EXIT WHEN TOPSAL_CURSOR%ROWCOUNT >= V_NUM
OR TOPSAL_CURSOR%NOTFOUND;
END LOOP;
CLOSE TOPSAL_CURSOR;
END;
/

SELECT * FROM TOP_DOGS;


DELETE FROM TOP_DOGS;

3. Create a Pl/SQL block that does the following:


a. Use the Define command to provide the Department ID. Use a substitution variable.
b. Retrieve the last_name, salary and Manager_ID of the employees working in that department.
c. If the salary of the employee is less than 5000 and if the manager ID is either 104 or 124 display the
massage <<last_name>> Due for a raise. Otherwise , display a message <<last_name>> Not Due for a
raise.
Department_ID Message
10 Whalen Due for a raise
20 Hartstein Not Due for a raise
Fay Not Due for a raise
50 Weles Not Due for a raise
Fripp Due for a raise
Kaufling Due for a raise
...........
80 Russel Not Due for a raise
Partners Not Due for a raise
Errazuriz Not Due for a riase
...................

81
PL/SQL Practices

DEFINE P_DEPTNO =10


DECLARE
V_DEPTNO EMPLOYEES.DEPARTMENT_ID%TYPE:=&P_DEPTNO;
V_ENAME EMPLOYEES.LAST_NAME%TYPE;
V_SAL EMPLOYEES.SALARY%TYPE;
V_MGR EMPLOYEES.MANAGER_ID%TYPE;
CURSOR EMP_CURSOR IS
SELECT LAST_NAME, SALARY, DEPARTMENT_ID, MANAGER_ID
FROM EMPLOYEES
WHERE DEPARTMENT_ID = V_DEPTNO;
BEGIN
OPEN EMP_CURSOR;
LOOP
FETCH EMP_CURSOR INTO
V_ENAME, V_SAL, V_DEPTNO, V_MGR;
EXIT WHEN EMP_CURSOR%NOTFOUND ;
IF V_SAL<5000 AND (V_MGR =101 OR V_MGR=124) THEN
DBMS_OUTPUT.PUT_LINE(V_ENAME||' Due for a raise');
ELSE
DBMS_OUTPUT.PUT_LINE(V_ENAME||' Not Due for a raise');
END IF;
FETCH EMP_CURSOR INTO
V_ENAME, V_SAL, V_DEPTNO, V_MGR;
END LOOP;
CLOSE EMP_CURSOR;
END;
/
anonymous block completed

Top-N Queries
Top-N queries provide a method for limiting the number of rows returned from ordered
sets of data. They are extremely useful when you want to return the top or bottom "N"
number of rows from a set or when you are paging through data. This article presents
several methods to implement Top-N queries.
* First populate numbers from 1 to 10.

SELECT level
FROM dual
CONNECT BY LEVEL <= 10;

SELECT EMPLOYEE_ID, SALARY

82
PL/SQL Practices

FROM (SELECT employee_id,SALARY


FROM EMPLOYEES
ORDER BY SALARY DESC)
WHERE ROWNUM <=5;

First create a table rownum_order_test with one row name it VAL and Datatype is Number(2).

CREATE TABLE ROWNUM_ORDER_TEST


( VAL NUMBER(2));

Now insert 20 rows ( from 1 to 10 , twice) into ROWNUM_ORDER_TEST.


INSERT ALL
INTO ROWNUM_ORDER_TEST
INTO ROWNUM_ORDER_TEST
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <=10;
20 rows inserted.

select * from rownum_order_test;

What not to do!


The following example shows a common trap people fall into when they don't
understand the way the ROWNUM pseudocolumn and ORDER BY clause interact. Let's
assume we wanted to return the top 5 values in the ID column. We might decide to
order the data by descending ID and pick off the first five rows. That sounds correct, so
we go ahead and issue the following query.
SELECT val
FROM rownum_order_test
WHERE rownum <= 5
ORDER BY val DESC;

VAL
----------
5
4
3
2
1

83
PL/SQL Practices

5 rows selected.

That didn't do what we wanted!


The problem is that the ROWNUM assignment is performed prior to the ORDER BY operation,
resulting in potentially random data being returned.

As the data is in the desired order before the ROWNUM check is performed, we get the result we
wanted. Notice that we asked for 5 rows and we got five, even though there is a second row
with the value "8".
We can return the 5 smallest values by altering the ORDER BY clause to ascending.
SELECT val
FROM (SELECT val
FROM rownum_order_test
ORDER BY val)
WHERE rownum <= 5;

VAL
----------
1
1
2
2
3

5 rows selected.

WITH Clause and ROWNUM


The previous example can be rewritten to use a WITH clause in place of the inline view.
WITH ordered_query AS
(SELECT val
FROM rownum_order_test
ORDER BY val DESC)
SELECT val
FROM ordered_query
WHERE rownum <= 5;

VAL
----------
10
10
9
9
8

5 rows selected.

84
PL/SQL Practices

RANK()
The RANK analytic function assigns a sequential rank for each distinct value in the specified window.
SELECT val
FROM (SELECT val,
RANK() OVER (ORDER BY val DESC) AS val_rank
FROM rownum_order_test)
WHERE val_rank <= 5;

VAL
----------
10
10
9
9
8
8

6 rows selected.

** Select top Two Salaries from Employees table. Name the column as salary_rank.
SELECT SALARY SALARY_RANK
FROM (SELECT SALARY, RANK() OVER (ORDER BY SALARY DESC) AS SAL_RANK
FROM EMPLOYEES)
WHERE SAL_RANK<=2;

At first glance this looks like there may be a problem, but displaying the rank information
shows us what is happening.
SELECT val, val_rank
FROM (SELECT val,
RANK() OVER (ORDER BY val DESC) AS val_rank
FROM rownum_order_test)
WHERE val_rank <= 5;

VAL VAL_RANK
---------- ----------
10 1
10 1
9 3
9 3
8 5
8 5

6 rows selected.

85
PL/SQL Practices

From this we can see that duplicate rows are assigned the same rank, followed by a skip in the
sequence to keep the rank consistent. Similar to Olympic medal places. This means
the RANK function doesn't give us the "top N rows" or the "top N distinct values". The number of
rows returned is dependent on the number of duplicates in the data.

**Display top Ten employees employee number, last name, salary and rank from Employees table.
Name the rank column as top_rank.
SELECT EMPLOYEE_ID, LAST_NAME, SALARY, TOP_RANK
FROM (SELECT EMPLOYEE_ID, LAST_NAME,SALARY, RANK() OVER (ORDER BY SALARY DESC) AS
TOP_RANK
FROM EMPLOYEES)
WHERE TOP_RANK<=10;

DENSE_RANK
The DENSE_RANK analytic function is similar to the RANK analytic function in that it assigns
a sequential rank for each distinct value in the specified window. The difference being
the ranks are compacted, so there are no gaps.
SELECT val
FROM (SELECT val,
DENSE_RANK() OVER (ORDER BY val DESC) AS val_rank
FROM ROWNUM_ORDER_TEST)
WHERE val_rank <= 5;

VAL
----------
10
10
9
9
8
8
7
7
6
6

10 rows selected.

** Using DENSE_RANK () Select top Two Salaries from Employees table. Name the
column as salary_rank.
SELECT SALARY SALARY_RANK
FROM (SELECT SALARY, DENSE_RANK() OVER (ORDER BY SALARY DESC) AS SAL_RANK
FROM EMPLOYEES)

86
PL/SQL Practices

WHERE SAL_RANK<=2;

Using the DENSE_RANK() information shows us what is happening.


SELECT EMPLOYEE_ID, LAST_NAME, SALARY, TOP_RANK
FROM (SELECT EMPLOYEE_ID, LAST_NAME,SALARY, RANK() OVER (ORDER BY SALARY DESC) AS
TOP_RANK
FROM EMPLOYEES)
WHERE TOP_RANK<=10;

VAL VAL_RANK
---------- ----------
10 1
10 1
9 2
9 2
8 3
8 3
7 4
7 4
6 5
6 5

10 rows selected.

**Display top Ten employees employee number, last name, salary and rank from Employees table.
Name the rank column as top_rank.

SELECT EMPLOYEE_ID, LAST_NAME, SALARY, TOP_RANK


FROM
(SELECT EMPLOYEE_ID, LAST_NAME, SALARY, DENSE_RANK() OVER (ORDER BY SALARY DESC) AS
TOP_RANK
FROM EMPLOYEES)
WHERE TOP_RANK <=5;

ROW_NUMBER
The ROW_NUMBER analytic function is similar to the ROWNUM pseudocolumn in that it assigns
a unique number for each row returned, but like all analytic functions its action can be
limited to a specific window of data in the result set and based on the order of data in
that window. In this simple example using a window of the whole result set it functions
the same as the ROWNUM psuedocolumn.
SELECT val

87
PL/SQL Practices

FROM (SELECT val,


ROW_NUMBER() OVER (ORDER BY val DESC) AS val_row_number
FROM rownum_order_test)
WHERE val_row_number <= 5;

VAL
----------
10
10
9
9
8

5 rows selected.

PERCENT_RANK
The PERCENT_RANK analytic function assigns value between 0-1 which represents the
position of the current row relative to the set as a percentage. The following example
displays the top 80% of the rows based on the value.
SELECT val
FROM (SELECT val,
PERCENT_RANK() OVER (ORDER BY val) AS val_percent_rank
FROM rownum_order_test)
WHERE val_percent_rank >= 0.8;

VAL
----------
9
9
10
10

4 rows selected.

NTILE
The NTILE analytic function divides the rows into the specified number of buckets, each
with approximately equal numbers of rows. The following example divides the results
into three buckets, and shows the rows from the third bucket, or the top third of the
results.
SELECT val
FROM (SELECT val,
NTILE(3) OVER (ORDER BY val) AS val_ntile

88
PL/SQL Practices

FROM rownum_order_test)
WHERE val_ntile = 3;

VAL
----------
8
8
9
9
10
10

6 rows selected.
Introducing to PL/SQL Cursor

When you work with Oracle database, you work with a complete set of rows returned from
an SELECT statement. However the application in some cases cannot work effectively with
the entire result set, therefore, the database server needs to provide a mechanism for the
application to work with one row or a subset of the result set at a time. As the result, Oracle
created PL/SQL cursor to provide these extensions.
A PL/SQL cursor is a pointer that points to the result set of an SQL query against
database tables.

Working with PL/SQL Cursor

The following picture describes steps that you need to follow when you work with a PL/SQL
cursor:

PL/SQL Cursor

Let’s examine each step in greater detail.


Declaring PL/SQL Cursor

To use PL/SQL cursor, first you must declare it in the declaration section of PL/SQL block or in
a package as follows:

89
PL/SQL Practices

1 CURSOR cursor_name [ ( [ parameter_1 [, parameter_2 ...] ) ]


2 [ RETURN return_specification ]
3 IS Select_Statements
4
[FOR UPDATE [OF [column_list]];

First, you declare the name of the cursor cursor_name after the CURSOR keyword. The
name of the cursor can have up to 30 characters in length and follows the naming rules of
identifiers in PL/SQL. It is important to note that cursor’s name is not a variable so you cannot
use it as a variable such as assigning it to other cursor or using it in an expression.
The parameter1, parameter2… are optional elements in the cursor declaration. These
parameters allow you to pass arguments into the cursor. The RETURN
return_specification is also an optional part.
Second, you specify a valid SQL statement that returns a result set where the cursor points to.
Third, you can indicate a list of columns that you want to update after the FOR UPDATE OF.
This part is optional so you can omit it in the CURSOR declaration.

Here is an example of declaring a cursor:


1 CURSOR cur_chief
2 IS
3 SELECT employee_id, first_name, last_name, department_name
4
5 FROM employees e
6 INNER JOIN departments d
7 ON d.manager_id = e.employee_id;

We retrieved data from employees and departments tables using the SELECT
with the INNER JOIN clause to view the name of managers and their department
number with employee number , and set the cur_chief cursor to this result
set.

Opening a PL/SQL Cursor

After declaring a cursor, you can open it by using the following syntax:

1 OPEN cursor_name [ ( argument_1 [, argument_2 ...] ) ] ;


You have to specify the cursor’s name cursor_name after the keyword OPEN. If the cursor
was defined with a parameter list, you need to pass corresponding arguments to the cursor.
When you OPEN the cursor, PL/SQL executes the SELECT statement and identifies the active
result set. Notice that the OPEN action does not actually retrieve records from the database.
It happens in the FETCH step. If the cursor was declared with the FOR UPDATE clause,
PL/SQL locks all the records in the result set.
We can open the cur_chief cursor as follows:

90
PL/SQL Practices

1 OPEN cur_chief;

Fetching Records from PL/SQL Cursor


Once the cursor is open, you can fetch data from the cursor into a record that has the same structure as
the cursor. Instead of fetching data into a record, you can also fetch data from the cursor to a list
of variables.
The fetch action retrieves data and fills the record or the variable list. You can manipulate this data in
memory. You can fetch the data until there is no record found in active result set.

The syntax of FETCH is as follows:


1 FETCH cursor_name INTO record or variables

You can test the cursor’s attribute %FOUND or %NOTFOUND to check if the fetch against the
cursor is succeeded. The cursor has more attributes that we will cover in the next section.
We can use PL/SQL LOOP statement together with the FETCH to loop through all records in
active result set as follows:

LOOP
1 -- fetch information from cursor into record
2 FETCH cur_chief INTO rec_chief ;
3
4
5 EXIT WHEN cur_chief%NOTFOUND;
6
7 -- print department - chief
8
9 DBMS_OUTPUT.PUT_LINE(r_chief.department_name || ' - ' ||
10 r_chief.first_name || ',' ||
11 r_chief.last_name);
END LOOP;

Closing PL/SQL Cursor


You should always close the cursor when it is no longer used. Otherwise, you will have a memory leak in
your program, which is not expected.
To close a cursor, you use CLOSE statement as follows:
1 CLOSE cursor_name;
And here is an example of closing the cur_chief cursor:
1 CLOSE cur_chief;

91
PL/SQL Practices

A complete PL/SQL Cursor Example


The following is a complete example of cursor for printing a list of chief and name of
departments as follows:
SET SERVEROUTPUT ON SIZE 1000000;
DECLARE
-- declare a cursor
1 CURSOR cur_chief IS
2 SELECT first_name,
3 last_name,
4
5 department_name
6 FROM employees e
7 INNER JOIN departments d ON d.manager_id = e.employee_id;
8
9
10 r_chief cur_chief%ROWTYPE;
11 BEGIN
12
13 OPEN cur_chief;
14 LOOP
15 -- fetch information from cursor into record
16
17 FETCH cur_chief INTO r_chief;
18
19 EXIT WHEN cur_chief%NOTFOUND;
20
21
22 -- print department - chief
23 DBMS_OUTPUT.PUT_LINE(r_chief.department_name || ' - ' ||
24
25 r_chief.first_name || ',' ||
26 r_chief.last_name);
27 END LOOP;
28
-- close cursor cur_chief
CLOSE cur_chief;
END;
/

PL/SQL Cursor Attributes

These are the main attributes of a PL/SQL cursor and their descriptions.

Attribute Description
cursor_name%FOUND returns TRUE if record was fetched successfully by cursor
cursor_name
cursor_name%NOTFOUND returns TRUE if record was not fetched successfully by
cursor cursor_name
cursor_name%ROWCOUNT returns the number of records fetched from the cursor

92
PL/SQL Practices

Attribute Description
cursor_name at the time we test %ROWCOUNT attribute
cursor_name%ISOPEN returns TRUE if the cursor cursor_name is open

In this tutorial, you’ve learned how to use PL/SQL Cursor to loop through a set of rows with all
necessary steps that need to be done including DECLARE, OPEN, FETCH and CLOSE.

DECLARE
CURSOR CUR_CHIEF /* Declare the Cursor */
IS
SELECT EMPLOYEE_ID, FIRST_NAME||' '||LAST_NAME ENAME, DEPARTMENT_NAME
FROM EMPLOYEES E
INNER JOIN DEPARTMENTS D
ON E.EMPLOYEE_ID= D.MANAGER_ID;
REC_CHIEF CUR_CHIEF%ROWTYPE; /* Create the Record*/
BEGIN
OPEN CUR_CHIEF; /* Open the Cursor */
LOOP
FETCH CUR_CHIEF INTO REC_CHIEF; /* Fetch the Cursor */
EXIT WHEN CUR_CHIEF%NOTFOUND; /* Write Exit for Cursor */
DBMS_OUTPUT.PUT_LINE(REC_CHIEF.EMPLOYEE_ID||'-'||REC_CHIEF.ENAME||'-
'||REC_CHIEF.DEPARTMENT_NAME);
END LOOP;
CLOSE CUR_CHIEF; /* Close the Cursor */
END;
/

Declare
Cursor cursor_name
IS
Select Statement ...;

create record ;
Begin
Open cursor_name;
LOOP
Fetch cursor_name into record_name ;
Exit when cursor_name%NOTFOUND ;
DBMS_OUTPUT.PUT_LINE (record_name.field_name .........);
END LOOP;

93
PL/SQL Practices

CLOSE cursor_name;
END;
/

Cursor with Parameters:


Syntax:
CURSOR cursor_name
[ (parameter_name datatype, ........) ]
IS
Select Statement ;
 You can pass parameters to the cursor in a cursor FOR LOOP. This means that you can open
and close an explicit cursor several times in block, returning a different active set on each
occasion. For each execution , the previous cursor is closed and reopened with a new set of
parameters.
Each formal parameter in the cursor declaration must have a corresponding actual parameter in the
OPEN statement. Parameter Data Type are the same as those for scalar variables. but you do not give
them sizes. The parameter names are for references in the query expression of the cursor.

Cursor With Parameters ( Passing parameter with a cursor)


DECLARE
CURSOR emp_cursor --Declaring a Cursor with parameter p_deptno and p_job
(p_deptno NUMBER , p_job varchar2 ) IS
SELECT employee_id, last_name
FROM employes
WHERE department_id = p_deptno
AND job_id = p_job ;
BEGIN
OPEN emp_cursor (80, 'SA_REP') ; --Opening the cursor and returning different active sets.
. . . . .
CLOSE emp_cursor ; --Opening the cursor and returning different active sets.
OPEN emp_cursor (60, 'IT_PROG');
END ;
/

You can also pass parameters to the cursor used in a cursor FOR LOOP:
DECLARE
CURSOR emp_cursor (p_deptno NUMBER, p_job VARCHAR2) IS
SELECT . . . . .
BEGIN
FOR emp_record IN emp_cursor (50 , 'ST_CLERK') LOOP . . . . .

The FOR UPDATE Clause


* Retrieve the Employees who work in Department 80 and Update their Salary.
Syntax :
SELECT . . . . . . .
FROM
FOR UPDATE [ OF coulmn_reference ] [NOWAIT ] ;

94
PL/SQL Practices

NOWAIT Keyword : The optional NOWAIT keyword tells Oracle not to wait if requested rows have been
locked by another user. Control is immediately returned to your program so that it can do other work
before trying again to acquire the lock. IF you omit the NOWAIT keyword, Oracle waits until the rows
are available.
DECLARE
CURSOR emp_cursor IS
SELECT EMPLOYEE_ID, LAST_NAME ,DEPARTMENT_NAME
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID = D.DEPARTMENT_ID)
AND E.DEPARTMENT_ID = 80
FOR UPDATE OF SALARY NOWAIT ;

FOR UPDATE Clause : The FOR UPDATE clause identifies the rows that will be updated or deleted,
then locks each row in the result set. This is useful when you want to base an update on the existing
values in a row. In that case, you must make sure the row is not changes by another user before the
update.

The WHERE CURRENT OF Clause


Syntax:
WHERE CURRENT OF cursor ;

WHERE CURRENT OF -- When referencing the current row from an explicit cursor, use the WHERE
CURRENT OF clause. This allows you to apply updates and deletes to the row currently being addressed,
without the need to explicitly reference the ROWID. You must include the FOR UPDATE clause in the
cursor query so that the rows are locked on OPEN.

CURSOR -- Name of the cursor.

*Update the Salary by 10% of Copy_Emp table whose department name is IT and salary is less than
5000.

DECLARE
CURSOR CUR_SAL IS
SELECT EMPLOYEE_ID, LAST_NAME, SALARY , DEPARTMENT_NAME
FROM COPY_EMP E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID = D.DEPARTMENT_ID)
AND E.DEPARTMENT_ID = 60
FOR UPDATE OF SALARY NOWAIT ;
EMP_RECORD CUR_SAL%ROWTYPE;
BEGIN
FOR EMP_RECORD IN CUR_SAL
LOOP
IF EMP_RECORD.SALARY <5000 THEN
UPDATE COPY_EMP
SET SALARY = EMP_RECORD.SALARY *1.1
WHERE CURRENT OF CUR_SAL;
END IF;

95
PL/SQL Practices

END LOOP;
END;
/
anonymous block completed

* Display the departments from Employees table which have employee(s) at least one .

SELECT T1.DEPARTMENT_ID, T1.DEPARTMENT_NAME, T2.STAFF


FROM DEPARTMENTS T1,
(SELECT DEPARTMENT_ID, COUNT(*) AS STAFF
FROM EMPLOYEES GROUP BY DEPARTMENT_ID) T2
WHERE T1.DEPARTMENT_ID = T2.DEPARTMENT_ID
AND T2.STAFF >=1;

**Display the Departments which have no employees assigned to them.


==> Easy way 1.
SELECT D.DEPARTMENT_NAME
FROM DEPARTMENTS D
WHERE NOT EXISTS (SELECT * FROM EMPLOYEES E WHERE D.DEPARTMENT_ID=E.DEPARTMENT_ID);

Or
==> Another method 2
SELECT D.DEPARTMENT_NAME
FROM DEPARTMENTS D
LEFT JOIN EMPLOYEES E ON D.DEPARTMENT_ID=E.DEPARTMENT_ID
WHERE E.DEPARTMENT_ID IS NULL ;

Which Department has MOST NUMBER of employees?


Syntax:
SELECT COLUMN_NAME, COUNT(*)
FROM TABLE_NAME
GROUP BY
COLUMN_NAME HAVING COUNT(*)=(SELECT MAX(COUNT(*)) FROM TABLE_NAME
GROUP BY COLUMN_NAME);

SELECT COUNT(*) EMP_NUM#, DEPARTMENT_ID


FROM EMPLOYEES
GROUP BY DEPARTMENT_ID
HAVING COUNT(*) =
(SELECT MAX(COUNT(*))
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID) ;

==> Displaying department number , department name with most number of employees.
SELECT COUNT(*) EMP_NUM#, E.DEPARTMENT_ID , D.DEPARTMENT_NAME
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID=D.DEPARTMENT_ID)
GROUP BY E.DEPARTMENT_ID , D.DEPARTMENT_NAME
96
PL/SQL Practices

HAVING COUNT(*) =
(SELECT MAX(COUNT(*))
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID) ;

**Display the department(s) number , name and number of employees which have minimum number
of employees ;

SELECT COUNT(*) EMP_NUM#, E.DEPARTMENT_ID , D.DEPARTMENT_NAME


FROM EMPLOYEES E JOIN DEPARTMENTS D
ON (E.DEPARTMENT_ID=D.DEPARTMENT_ID)
GROUP BY E.DEPARTMENT_ID , D.DEPARTMENT_NAME
HAVING COUNT(*) =
(SELECT MIN(COUNT(*))
FROM EMPLOYEES
GROUP BY DEPARTMENT_ID );

Cursor With Subqueries

DECLARE
CURSOR MY_CURSOR IS
SELECT T1.DEPARTMENT_ID, T1.DEPARTMENT_NAME, T2.STAFF
FROM DEPARTMENTS T1, (SELECT DEPARTMENT_ID, COUNT(*) AS STAFF FROM EMPLOYEES GROUP BY
DEPARTMENT_ID) T2
WHERE T1.DEPARTMENT_ID = T2.DEPARTMENT_ID
AND T2.STAFF >=1;
.....

Practice - 7
1. In a loop, use a cursor to retrieve the department number and the department name from the
DEPARTMENTS table for those departments whose DEPARTMENT_ID is less than 100. Pass the
department number to another cursor to retrieve from the EMPLOYEES table the details of employee
last name, job, hire date and salary of those employees whose employee_id is less than 120 and who
work in that department.

SET SERVEROUTPUT ON
DECLARE
CURSOR DEPT_CUR IS
SELECT DEPARTMENT_ID, DEPARTMENT_NAME
FROM DEPARTMENTS
WHERE DEPARTMENT_ID <100;
CURSOR EMP_CUR (P_DEPARTMENT_ID NUMBER) IS
SELECT *
FROM EMPLOYEES
WHERE DEPARTMENT_ID=P_DEPARTMENT_ID AND EMPLOYEE_ID <120;
BEGIN
FOR DEPT_RECORD IN DEPT_CUR
97
PL/SQL Practices

LOOP
DBMS_OUTPUT.PUT_LINE('DEPARTMENT NUMBER: '||DEPT_RECORD.DEPARTMENT_ID||'
DEPARTMENT NAME: '||DEPT_RECORD.DEPARTMENT_NAME);

FOR EMP_RECORD IN EMP_CUR (DEPT_RECORD.DEPARTMENT_ID)


LOOP
DBMS_OUTPUT.PUT_LINE(EMP_RECORD.LAST_NAME||' '||EMP_RECORD.JOB_ID||'
'||EMP_RECORD.HIRE_DATE||' '||EMP_RECORD.SALARY);
END LOOP;
DBMS_OUTPUT.PUT_LINE(CHR(10)); --this will provide three line gap
END LOOP;
END;
/

Note : To Pass the department number to another cursor to retrieve.....


FOR EMP_RECORD IN EMP_CUR (DEPT_RECORD.DEPARTMENT_ID)

2. Modify the code written above to incorporate a cursor using the FOR UPDATE and WHERE CURRENT
OF functionality in cursor processing.
a. Define the host variables.
DEFINE p_empno=104
DEFINE p_empno=174
DEFINE p_empno=176
b. Execute the modified PL/SQL block.
c. Execute the following command to check if you PL/SQL block has worked successfully:
SELECT EMPLOYEE_ID, SALARY, STARS
FROM COPY_EMP
WHERE EMPLOYEE_ID IN (176,174,104);

Chapter - Eight Handling Exceptions

What is an Exception ?
An exception is an identifier is PL/SQL that is raised during execution.
How an Exception is raised ?
 When an Oracle Error occurs.
 You raise it explicitly.
How do you handle it ?
 Trap it with a handler.
 Propagate it to the calling environment.
What is Trapping an Exception?
If the exception is raised in the executable section of the block, ( Note: Executable section is between
BEGIN and EXCEPTION ) and if there is exception handler in the exception section of the block, the
pl/sql successfully handles the exception, then the exception does not propagate to the enclosing block
or environment. PL/SQL block terminates successfully.
What is Propagating Exception ?

98
PL/SQL Practices

If the exception is raised in the executable section of the block and there is no corresponding exception
handler, the PL/SQL terminates with failure and the exception is propagate to the calling environment.

Exception Type:
1.Predefined Oracle Server Error - One of the approximately 20 errors that occur most often in PL/SQL
code. Directions for handling such error is do not declare and allow the Oracle Server to raise them
implicitly.
2.Nonpredefined Oracle Server Error - Any other standard Oracle Server error. Directions for handling
such error is declare within the declarative section and allow the Oracle Server to raise them implicitly.
3.User-defined error - A condition that the developer determines is abnormal. Directions for handling
such error is declare within the declarative section and raise explicitly .

Trapping Exceptions :
Syntax :
Declare
......
Begin
........
EXCEPTION
WHEN exception1 [ OR exception 2 ....] THEN
statement1;
statement2;
........
[WHEN exception3 [ OR exception4....] THEN
statement1;
statement2;
........
[WHEN OTHERS THEN
statement1;
statement2;
..........]

In the syntax :

exception : is the standard name of a predefined exception or the name of a user-defined exception
declared within the declarative section.
statement: is one or more PL/SQL or SQL statements.
WHEN OTHERS Exception Handler :
The exception handling section traps only those exceptions that are specified ; any other exceptions are
not trapped unless you use the OTHERS exception handler. This traps any exception not yet handled. For
this reason, OTHERS is the last exception handler that is defined.
The OTHERS handler traps all exceptions not already trapped.

Trapping Exception Guidelines:


 The EXCEPTION keyword starts exception-handling section.
 Several exception handlers are allowed.
 Only one handler is processed before leaving the block.

99
PL/SQL Practices

 WHEN OTHERS is the last clause.


Predefined Exceptions:
Exception Name Oracle Description
Server
Error
Number
ACCESS_INTO_NULL ORA- Attempted to assign values to the attributes of an
06530 uninitialized object
CASE_NOT_FOUND ORA- None of the choices in the WHEN clauses of a CASE
06592 statement is selected, and there is no ELSE clause.
OLLECTION_IS_NULL ORA- Attempted to apply collection methods other than
06531 EXISTS to an uninitialized nested table or varray.
CURSOR_ALREADY_OPEN ORA- Attempted to open an already open cursor.
06511
DUP_VAL_ON_INDEX ORA- Attempted to insert a duplicate value.
00001
INVALID_CURSOR ORA- Illegal cursor operation occurred.
01001
INVALID_NUMBER ORA- Conversion of character string to number fails.
01722
LOGIN_DENIED ORA- Logging on to ORACLE with an invalid username or
01017 password.
NO_DATE_FOUND ORA- Single row SELECT returned no data
01403
NOT_LOGGED_ON ORA- PL/SQL program issues a database call without begin
01012 connected to Oracle.
PROGRAM_ERROR ORA- PL/SQL has an internal problem.
06501
ROWTYPE_MISMATCH ORA- Host cursor variable and PL/SQL cursor variable
06504 involved in an assignment have incompatible return
type.
STORAGE_ERROR ORA- PL/SQL ran out of memory or memory is corrupted.
06500
SUBSCRIPT_BEYOND_COUNT ORA- Referenced a nested table or varray element using an
06533 index number larger than the number of elements in
the collection.
SUBSCRIPT_OUTSIDE_LIMIT ORA- Referenced a nested table or varray element using an
06532 index number that is outside the legal range (-1).
SYS_INVALID_ROWID ORA- The conversion of a character string into a universal
01410 ROWID fails because the character string does not
represent a valid ROWID.
TIMEOUT_ON_RESOURCE ORA- Time out occurred while Oracle is waiting for a
00051 resource.
TOO_MAY_ROWS ORA- Single row SELECT statement returns more than one
01422 row.
VALUE_ERROR ORA- Arithmetic, conversion, truncation, or size constraint
06502 error occurred.

100
PL/SQL Practices

ZERO_DIVIDE ORA- Attempted to divide by zero.


01476

Predefined Exceptions:
Syntax:
DECLARE
..............
BEGIN
....................
EXCEPTION
WHEN NO DATA FOUND THEN
statement1;
statement2; =>handle the error
WHEN TOO MANY ROWS THEN
statement1;
statement2; =>handle the error
WHEN OTHERS THEN
statement1;
statement2;
statement3; =>handle all other error
END ;
/

Nonpredefined Error:
You can trap a nonpredefined Oracle server error by declaring it first, or by using the OTHERS handler.
The declared exception is raised implicitly. In PL/SQL , the PRAGMA EXCEPTION_INIT tells the
compiler to associate an exception name with an Oracle error number. That allows you to refer to any
internal exception by name and to write a specific handler for it.

*Trap for Oracle Server Error number -2292 , an integrity constraint violation. If there are employees
in a department, print a message to the user that the department cannot be removed.

DEFINE P_DEPTNO=10
DECLARE
E_EMPA_REMAINING EXCEPTION;
PRAGMA EXCEPTION_INIT (E_EMPA_REMAINING , -2292);
BEGIN
DELETE FROM DEPARTMENTS
WHERE DEPARTMENT_ID = &P_DEPTNO;
EXCEPTION
WHEN E_EMPA_REMAINING THEN
DBMS_OUTPUT.PUT_LINE('Cannot remove dept '||TO_CHAR(&P_DEPTNO) ||'. Employees Exists. ');
END;
/
anonymous block completed

101
PL/SQL Practices

Cannot remove dept 10 . Employees Exists.

Error Trapping Functions:


When an exception occurs, you can identify the associated error code or error message by using two
functions. Based on the values of the code or message, you can decide which subsequent action to take
based on the error.
SQLCODE returns the number of the ORACLE error for internal exceptions. You can pass an error
number to SQLERRM, when the returns the message associated with the error number.

Note :
1. SQLCODE : Returns the numeric value for the error code.
2.SQLERRM : Returns the message associated with the error number.
Example of SQLCODE Values
SQLCODE Value Description
0 No exception encountered
1 User-Defined exception
+100 NO_DATA_FOUND exception
Negative Number Another Oracle Server Error Number

You cannot use SQLCODE or SQLERRM directly in a SQL Statement. Instead, you must assign their
values to local variables, then use the variables in the SQL statement, as shown in the following
example:

CREATE TABLE ERRORS


(
ERROR_CODE NUMBER(12),
ERROR_MESSAGE VARCHAR2(150)
);

DECLARE
V_ERROR_CODE NUMBER;
V_ERROR_MSG VARCHAR2(150);
BEGIN
NULL;
EXCEPTION
WHEN OTHERS THEN
V_ERROR_CODE :=SQLCODE;
V_ERROR_MSG:=SUBSTR(SQLERRM,1,150);
INSERT INTO ERRORS (ERROR_CODE, ERROR_MESSAGE)
VALUES (V_ERROR_CODE, V_ERROR_MSG);
END;
/
anonymous block completed.

102
PL/SQL Practices

User - Defined Exceptions:


You trap a user-defined exception by declaring it and raising it explicitly.
1. Declare the name for the user defined exception within the declarative section.
Syntax:
exception_name EXCEPTION ;

2. Use the RAISE statement to raise the exception explicitly within the executable section.
Syntax:
RAISE exception_name ;

3. Reference the declarative exception within the corresponding exception handling routine.

Example :
Write a PL/SQL block that update the description of a department. The user supplies the department
number and the new name. If the user enters a department number that does not exist, no rows will be
updated in the DEPARTMENTS table. Raise an exception and print a message for the user that an invalid
department number was entered.

DEFINE P_DEPARTMENT_NAME = 'Information Technology'


DEFINE P_DEPARTMENT_NUMBER = 300

DECLARE
ERR_INVALID_DEPT EXCEPTION;
BEGIN
UPDATE DEPARTMENTS
SET DEPARTMENT_NAME = '&P_DEPARTMENT_NAME'
WHERE DEPARTMENT_ID = &P_DEPARTMENT_NUMBER;
IF SQL%NOTFOUND THEN
RAISE ERR_INVALID_DEPT;
END IF;
SAVEPOINT A;
EXCEPTION
WHEN ERR_INVALID_DEPT THEN
DBMS_OUTPUT.PUT_LINE('No Such Department ID.');
END;
/
anonymous block completed
No Such Department ID.

The RAISE_APPLICATION_ERROR procedure .


Syntax:
raise_application_error (error_number, message [, {TRUE|FALSE});

Use the RAISE_APPLICATION_ERROR procedure to communicate a predefined exception interactively


by returning a nonstandard error code and error message. With RAISE_APPLICATION_ERROR, you can
report errors to your application and avoid returning unhandled exceptions.

103
PL/SQL Practices

In the Syntax :
error_number is a user specified number for the exception between -20000 and -20999.
message is the user specified message for the exception. It is a character string up to 2048 bytes long.
TRUE|FALSE is an optional parameter (if True , the error is placed on the stock of previous errors. If
FALSE , the default , the error replaces all previous errors. )

 Used in two different places :


- Executable section
-Exception section
 Returns error conditions to the user in a manner consistent with other Oracle Server Errors.
Example :
Executable Section :
BEGIN
.............
DELETE FROM employees
WHERE manager_id = v_mgr ;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR (-20202 , 'This is not a valid manager');
END IF;
.....................

Exception Section :
BEGIN
..............
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20202,'This is not a valid manager');
END ;
/

Practice - 8

1. Write a PL/SQL block to select the name of the employee with a given salary value.
a. Use the DEFINE command to provide the salary.
SET VERIFY OFF
SET SERVEROUTPUT ON
DEFINE P_SAL = 6000
b. Pass the value to the PL/SQL block through a substitution variable. If the salary entered return more
than one row, handle the exception with an appropriate exception handler and insert into the message
"More than one employee with a salary of <salary>."
c. If the salary entered does not return any rows, handle the exception with an appropriate exception
handler and insert into the MESSAGES table the message "No employee with a salary of <salary>."
d. If the salary entered returns only one row, insert into the MESSAGES table the employee's name and
salary amount.

104
PL/SQL Practices

e. Handle any other exception with an appropriate exception handler and insert into the MESSAGES
table the message "Some other error occurred."
f. Test the block for a variety of test cases. Display the rows from the MESSAGES table to check whether
the PL/SQL block has executed successfully.
MESSAGES table would have only one column RESULTS with Varchar2(55) data type.

CREATE TABLE MESSAGES


(
RESULTS VARCHAR2(255)
);

SET ECHO OFF


DEFINE P_SAL = 6000
DECLARE
V_ENAME EMPLOYEES.LAST_NAME%TYPE;
V_SAL EMPLOYEES.SALARY%TYPE:=&P_SAL;
BEGIN
SELECT LAST_NAME
INTO V_ENAME
FROM EMPLOYEES
WHERE SALARY = V_SAL;
INSERT INTO MESSAGES (RESULTS)
VALUES (V_ENAME||'-'||TO_CHAR(V_SAL));
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO MESSAGES (RESULTS)
VALUES ('No Employee with a Salary of '||TO_CHAR(V_SAL));

WHEN TOO_MANY_ROWS THEN


INSERT INTO MESSAGES (RESULTS)
VALUES ('More than one row with the salary of '||TO_CHAR(V_SAL));

WHEN OTHERS THEN


INSERT INTO MESSAGES (RESULTS)
VALUES ('Some other error occurred.');
END;
/
anonymous block completed

2.Modify the above PL/SQL code to add an exception handler.


a. Use the DEFINE command to provide the department Id and department location. Pass the values to
the PL/SQL block through a substitution variable.
SET VARIFY OFF
VARIABLE g_message VARCHAR2(100)
DEFINE P_DEPTNO = 200
DEFINE P_LOC = 1400
b. Write an exception handler for the error to pass a message to the user that the specified department
does not exist. Use a bind variable to pass the message to the user.

105
PL/SQL Practices

c. Execute the PL/SQL block by entering a department that does not exist.

SET VERIFY OFF


DEFINE P_DEPTNO = 200
DEFINE P_LOCID = 1400
VARIABLE G_MESSAGE VARCHAR2(255);
DECLARE
V_DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE:=&P_DEPTNO;
ERR_INVALID_DEPT EXCEPTION;
BEGIN
UPDATE DEPARTMENTS
SET LOCATION_ID = &P_LOCID
WHERE DEPARTMENT_ID = V_DEPTNO ;
SAVEPOINT A;
IF SQL%NOTFOUND THEN
RAISE ERR_INVALID_DEPT;
END IF;
EXCEPTION
WHEN ERR_INVALID_DEPT THEN
:G_MESSAGE :='Department '||TO_CHAR(V_DEPTNO)||' is an invalid department.';
DBMS_OUTPUT.PUT_LINE ('Department '||TO_CHAR(V_DEPTNO)||' is invalid.');
END;
/
anonymous block completed
Department 200 is invalid

3. Write a PL/SQL block that prints the number of employees who earn more or less $1000 as Salary. Use
substitution variable to provide the salary value at run time.
a. If there is No employee within that salary range , print a message to the user indicating that 'There is
no employee salary between the lower and upper value.
b. If there are one or more employees within that range , the message should indicate how many
employees are in that salary range.
c. Handle any other exception with an appropriate exception handler. The message should indicate
'Some other error occurred '.

DECLARE
V_SAL EMPLOYEES.SALARY%TYPE:=&P_SAL;
V_LOWEST_SAL EMPLOYEES.SALARY%TYPE:=V_SAL+1000;
V_HIGHEST_SAL EMPLOYEES.SALARY%TYPE:=V_SAL-1000;
V_EMP_NUM NUMBER(8);
V_NO_EMP_RETURN EXCEPTION;
V_MORE_EMP_RETURN EXCEPTION;
BEGIN
SELECT COUNT(*)
INTO V_EMP_NUM
FROM EMPLOYEES
WHERE SALARY BETWEEN V_LOWEST_SAL AND V_HIGHEST_SAL;
IF V_EMP_NUM = 0 THEN

106
PL/SQL Practices

RAISE V_NO_EMP_RETURN;
ELSIF V_EMP_NUM >0 THEN
RAISE V_MORE_EMP_RETURN;
END IF;
EXCEPTION
WHEN V_NO_EMP_RETURN THEN
DBMS_OUTPUT.PUT_LINE('There is no employee salary between '||TO_CHAR(V_LOWEST_SAL)||' and
'||TO_CHAR(V_HIGHEST_SAL));
WHEN V_MORE_EMP_RETURN THEN
DBMS_OUTPUT.PUT_LINE ('There is more employee salary between '||TO_CHAR(V_LOWEST_SAL)||'
and '||TO_CHAR(V_HIGHEST_SAL)
||', number of employee is '||TO_CHAR(V_EMP_NUM));
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Some Other Error Occurred.');
END;
/

PL/SQL Tables (Index by tables):


PL/SQL Tables are not the same as a database table. They are essentially a one dimensional
array that has two columns. The first column is the primary key column of type
BINARY_INTEGER, the second column is the data column of any data-type.
Declaring tables in PL/SQL is a two step process. First Declare a table TYPE, then declare a
variable of that type.
Syntax:
TYPE type_name IS TABLE OF data-type INDEX BY BINARY_INTEGER ;
Variable_name type_name ;

Example:
DECLARE
TYPE sal_table_type IS TABLE OF NUMBER(8,2) INDEX BY BINARY_INTEGER;
TYPE EmpListType IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER;
sal_table sal_table_type;
BEGIN
...

PL/SQL Records:
Not the same as a database row. Composed of one or more fields that can be of any data-
type. Once again declaring a PL/SQL Record is a two step process. First declare a record TYPE,
next declare a variable of that type.
Syntax:
TYPE type_name IS RECORD (col1 data-type, col2 data-type,......);
Variable_name type_name;
Example:

DECLARE

107
PL/SQL Practices

TYPE comp_record_type IS RECORD


(
Sal NUMBER (8,2) NOT NULL,
Comm NUMBER (8,2) NULL,
Total_comp NUMBER(8,2) NOT NULL
);

Comp_record comp_record_type
TYPE comp_list_type IS TABLE OF comp_record_type INDEX BY BINARY_INTEGER
BEGIN
...

Declare PL/SQL Record Type from a Table Definition (%ROWTYPE):


The %ROWTYPE construct allows you to create a PL/SQL record with the same fields as the
table it references. This much simplifies record declaration when you want to emulate a table
inside your PL/SQL code.

Syntax:
DECLARE
Sales_rep_record sales_rep%ROWTYPE;
BEGIN
...
Assigning Variables in Code
V_variable := expression;
Table_name(index) := expression;
Record_name.field_name := expression;
empList (index).column_name := value;

CURSORS:
Cursors are used for processing multiple rows being returned from a SELECT statement.
Creating a cursor:
- Declare the cursor.
- Open the cursor.
- Fetch data from the cursor.
- Close the cursor.

Declaring Cursors, Syntax:


DECLARE
CURSOR cursor_name IS
SELECT columns
FROM tables
WHERE condition;

108
PL/SQL Practices

Notes:
- Do not include the INTO clause in the SELECT.
- Declare variables before cursors.

Opening Cursors, Syntax:


OPEN cursor_name;
Note: This is done in the BEGIN section and is used to prepare the cursor for
execution.
Fetching Data from the Cursor, Syntax:
FETCH cursor_name INTO variable1, variable2, etc.;
Notes:
- Include the same number of variables as is included in the SELECT clause of the
cursor.
- Variables are mapped to columns in the SELECT in the order they are listed.

Closing Cursors, Syntax:


CLOSE cursor_name;
Notes:
- Close the cursor after all the rows are processed.
- You can't fetch data from the cursor once you have closed it.

Cursor Status Attributes:


- %ISOPEN: True if cursor is OPEN.
- %NOTFOUND: True if the most recent FETCH returns nothing.
- %FOUND: True until most recent FETCH returns nothing.
- %ROWCOUNT: Total rows fetched so far.
Example:
Create a Table name TOTAL_ORDER_PRICE in OE schema , with two columns
total_quantity, and total_price. Display Product ID, Unit Price, total quantity
ordered and total ordered price of any order id supplied at run time, from table
Order_Items. Also insert the total quantity and price in table Total_Order_Price.

CREATE TABLE TOTAL_ORDER_PRICE


(
TOTAL_QUANTITY NUMBER(8),
TOTAL_PRICE NUMBER(8,2)
);

SET SERVEROUTPUT ON

109
PL/SQL Practices

ACCEPT P_ORDER_ID PROMPT 'Enter Order#:';


DECLARE
V_ORDER_ID ORDER_ITEMS.ORDER_ID%TYPE:=&P_ORDER_ID;
V_PRODUCT_ID ORDER_ITEMS.PRODUCT_ID%TYPE;
V_UNIT_PRICE ORDER_ITEMS.UNIT_PRICE%TYPE;
V_QUANTITY ORDER_ITEMS.QUANTITY%TYPE;
V_TOTAL_PRICE ORDER_ITEMS.UNIT_PRICE%TYPE;
CURSOR CUR_ORDER_ITEMS IS
SELECT PRODUCT_ID, UNIT_PRICE, QUANTITY
FROM ORDER_ITEMS
WHERE ORDER_ID = V_ORDER_ID;
BEGIN
OPEN CUR_ORDER_ITEMS;
LOOP
FETCH CUR_ORDER_ITEMS INTO V_PRODUCT_ID, V_UNIT_PRICE, V_QUANTITY ;
EXIT WHEN CUR_ORDER_ITEMS%NOTFOUND;
V_TOTAL_PRICE:=V_UNIT_PRICE * V_QUANTITY;
DBMS_OUTPUT.PUT_LINE('Product ID:'||V_PRODUCT_ID);
DBMS_OUTPUT.PUT_LINE('Unit Price:'||V_UNIT_PRICE);
DBMS_OUTPUT.PUT_LINE('Quantity Ordered:'||V_QUANTITY);
DBMS_OUTPUT.PUT_LINE('Total Order Price:'||V_TOTAL_PRICE);
INSERT INTO TOTAL_ORDER_PRICE (TOTAL_QUANTITY, TOTAL_PRICE)
VALUES(V_QUANTITY, V_TOTAL_PRICE);
END LOOP;
CLOSE CUR_ORDER_ITEMS;
COMMIT;
END;
/

Cursor FOR Loop:


A cursor for loop is specifically designed to stream line cursor processing.

A cursor For Loop:


- Explicitly Declare
- Implicitly Opens
- Implicitly Fetches
- Implicitly Declares Record for returned row
- Implicitly Closes Cursor when for loop ends.

Syntax:

110
PL/SQL Practices

FOR record_name IN cursor_name LOOP


Statement1;
Statement2;
END LOOP;

Example :

SET SERVEROUTPUT ON
DECLARE
V_ORDER_ID ORDER_ITEMS.ORDER_ID%TYPE:=&P_ORDER_ID;
V_TOTAL_QUANTITY ORDER_ITEMS.QUANTITY%TYPE;
V_TOTAL_PRICE ORDER_ITEMS.UNIT_PRICE%TYPE;
CURSOR TOTAL_ORDER_CUR IS
SELECT UNIT_PRICE, QUANTITY
FROM ORDER_ITEMS
WHERE ORDER_ID=V_ORDER_ID;
REC_TOTAL_ORDER TOTAL_ORDER_CUR%ROWTYPE;
BEGIN
FOR REC_TOTAL_ORDER IN TOTAL_ORDER_CUR LOOP
V_TOTAL_QUANTITY:= REC_TOTAL_ORDER.QUANTITY;
V_TOTAL_PRICE:=V_TOTAL_PRICE + (REC_TOTAL_ORDER.QUANTITY *
REC_TOTAL_ORDER.UNIT_PRICE);
DBMS_OUTPUT.PUT_LINE('Order Total Quantity:'||V_TOTAL_QUANTITY||', '||'Total
Price:'||V_TOTAL_PRICE);
END LOOP;
END;
/

Processing Nested Cursors

SET SERVEROUTPUT ON
DECLARE
v_zip zipcode.zip%TYPE;
CURSOR c_zip IS
SELECT zip, city, state FROM zipcode WHERE state = 'CT';
CURSOR c_student IS
SELECT first_name, last_name FROM student WHERE zip = v_zip;
BEGIN
FOR r_zip IN c_zip LOOP
v_zip := r_zip.zip;

111
PL/SQL Practices

DBMS_OUTPUT.PUT_LINE (CHR(10));
DBMS_OUTPUT.PUT_LINE ('Students living in ' || r_zip.city);
FOR r_student IN c_student LOOP
DBMS_OUTPUT.PUT_LINE (r_student.first_name||' '||r_student.last_name);
END LOOP;
END LOOP;
END;
/
SET SERVEROUTPUT ON
DECLARE
v_amount course.cost%TYPE;
v_instructor_id instructor.instructor_id%TYPE;
CURSOR c_inst IS
SELECT first_name, last_name, instructor_id
FROM instructor;
CURSOR c_cost IS
SELECT c.cost
FROM course c, section s, enrollment e
WHERE s.instructor_id = v_instructor_id
AND c.course_no = s.course_no
AND s.section_id = e.section_id;
BEGIN
FOR r_inst IN c_inst LOOP
v_instructor_id := r_inst.instructor_id;
v_amount := 0;
DBMS_OUTPUT.PUT_LINE ('Generated by instructor ' ||
r_inst.first_name || ' ' || r_inst.last_name);
FOR r_cost IN c_cost LOOP
v_amount := v_amount + NVL(r_cost.cost, 0);
END LOOP;
DBMS_OUTPUT.PUT_LINE (TO_CHAR(v_amount, '$999,999'));
END LOOP;
END;
/

Using Parameters with cursors

SET SERVEROUTPUT ON
DECLARE
CURSOR c_student IS
SELECT first_name, last_name, student_id
FROM student
WHERE last_name LIKE 'J%';
112
PL/SQL Practices

CURSOR c_course (p_studid IN student.student_id%TYPE) IS


SELECT c.description, s.section_id sec_id
FROM course c, section s, enrollment e
WHERE e.student_id = p_studid
AND c.course_no = s.course_no
AND s.section_id = e.section_id;
CURSOR c_grade (p_sid IN section.section_id%TYPE,
p_stuid IN student.student_id%TYPE)
IS
SELECT gt.description grd_desc,
TO_CHAR(AVG(g.numeric_grade), '999') num_grd
FROM enrollment e, grade g, grade_type gt
WHERE e.section_id = p_sid
AND e.student_id = g.student_id
AND e.student_id = p_stuid
AND e.section_id = g.section_id
AND g.grade_type_code = gt.grade_type_code
GROUP BY gt.description;
BEGIN
FOR r_student IN c_student LOOP
DBMS_OUTPUT.PUT_LINE(CHR(10));
DBMS_OUTPUT.PUT_LINE(r_student.first_name||' '||r_student.last_name);
FOR r_course IN c_course(r_student.student_id) LOOP
DBMS_OUTPUT.PUT_LINE ('Grades for course: '|| r_course.description);
FOR r_grade IN c_grade(r_course.sec_id, r_student.student_id) LOOP
DBMS_OUTPUT.PUT_LINE (r_grade.num_grd||' '||r_grade.grd_desc);
END LOOP;
END LOOP;
END LOOP;
END;
/

For Update Cursors

DECLARE
CURSOR c_course IS
SELECT course_no, cost
FROM course
FOR UPDATE;
BEGIN
FOR r_course IN c_course LOOP
IF r_course.cost < 2500 THEN

113
PL/SQL Practices

UPDATE course
SET cost = r_course.cost + 10 -- There is a typo in the book
WHERE course_no = r_course.course_no;
END IF;
END LOOP;
END;

DECLARE
CURSOR c_stud_zip IS
SELECT s.student_id, z.city
FROM student s, zipcode z
WHERE z.city = 'Brooklyn'
AND s.zip = z.zip
FOR UPDATE OF phone;
BEGIN
FOR r_stud_zip IN c_stud_zip LOOP
UPDATE student SET phone = '718'||SUBSTR(phone, 4) -- There is a typo in the book
WHERE student_id = r_stud_zip.student_id; -- There is a typo in the book
END LOOP;
END;
/

Where Current of Clause

DECLARE
CURSOR c_stud_zip IS
SELECT s.student_id, z.city
FROM student s, zipcode z
WHERE z.city = 'Brooklyn'
AND s.zip = z.zip
FOR UPDATE OF s.phone;
BEGIN
FOR r_stud_zip IN c_stud_zip LOOP
DBMS_OUTPUT.PUT_LINE (r_stud_zip.student_id);
UPDATE student
SET phone = '718'||SUBSTR(phone, 4) -- There is a typo in the book
WHERE CURRENT OF c_stud_zip;
END LOOP;
END;
/

114
PL/SQL Practices

Implicit Cursors with Sub-Queries

You don't have to declare a cursor to deal with a SQL statement returning more than
one row. In PL/SQL you can embed a SQL-Select statement inside a for loop.

SET SERVEROUTPUT ON
BEGIN
FOR REC_EMPLOYEES IN (SELECT * FROM EMPLOYEES ORDER BY LAST_NAME, FIRST_NAME)
LOOP
DBMS_OUTPUT.PUT_LINE(REC_EMPLOYEES.FIRST_NAME||' '||REC_EMPLOYEES.LAST_NAME);
DBMS_OUTPUT.PUT_LINE(REC_EMPLOYEES.JOB_ID);
DBMS_OUTPUT.PUT_LINE(REC_EMPLOYEES.SALARY);
END LOOP;
END;
/

What Are Cursor Variables?

Cursor variables are pointers, which hold the memory location (address) of some item
instead of the item itself. So, declaring a cursor variable creates a pointer, not an item.
In PL/SQL, a pointer has datatype REF X, where REF is short
or REFERENCE and X stands for a class of objects. Therefore, a cursor variable has
datatype REF CURSOR.

To execute a multi-row query, Oracle opens an unnamed work area that stores
processing information. To access the information, you can use an explicit cursor,
which names the work area. Or, you can use a cursor variable, which points to the
work area. Whereas a cursor always refers to the same query work area, a cursor
variable can refer to different work areas. So, cursors and cursor variables
are not interoperable; that is, you cannot use one where the other is expected.

Why Use Cursor Variables?

Mainly, you use cursor variables to pass query result sets between PL/SQL stored
subprograms and various clients. Neither PL/SQL nor any of its clients owns a result
set; they simply share a pointer to the query work area in which the result set is stored.

A query work area remains accessible as long as any cursor variable points to it.
Therefore, you can pass the value of a cursor variable freely from one scope to
another. For example, if you pass a host cursor variable to a PL/SQL block embedded
in a Pro*C program, the work area to which the cursor variable points remains
accessible after the block completes.

115
PL/SQL Practices

Oracle Practice - Extra Time

PL/SQL stands for Procedural Language Extension to SQL.


REGEXP_COUNT : The REGEXP_COUNT function returns how many times the search
pattern 'ora' appears in the source string 'Oracle PL/SQL...' 1 indicates the
position of the source string where the search begins, and 'i' indicates case-
insensitive matching.

Example :
SELECT
REGEXP_COUNT ('Oracle PL/SQL By Example Updated for Oracle 11g',
'oracle', 1, 'i') "Oracle Count"
FROM dual ;

Note : the i is case sensitive , it must be small i.


REGEXP_COUNT = registry expression count.

Built in Functions: REGEXP_INSTR and REGEXP_SUBSTR


SELECT REGEXP_INSTR ('Oracle 9i updated to Oracle 11g','((ora)(cle))',1,2,0,'i') "regexp instr"
FROM DUAL;

It is important for you to realize that PL/SQL is not a stand-alone programming language. PL/SQL is a
part of the Oracle RDBMS, and it can reside in two environments, the client and the server. As a result, it
is very easy to move PL/SQL modules between server-side and client-side applications.

* Display the first name and last name (as combined full name) of member form Employees Table. Also
display the employee number of that employee.
SET ECHO OFF
SET VERIFY OFF
DECLARE
V_FIRST_NAME EMPLOYEES.FIRST_NAME%TYPE;
V_LAST_NAME EMPLOYEES.LAST_NAME%TYPE;
V_EMPID NUMBER(5):=&P_EMPID;
BEGIN
SELECT FIRST_NAME, LAST_NAME
INTO V_FIRST_NAME, V_LAST_NAME
FROM EMPLOYEES
WHERE EMPLOYEE_ID = V_EMPID;
DBMS_OUTPUT.PUT_LINE('Employee Number:'||V_EMPID);
DBMS_OUTPUT.PUT_LINE('Name:'||V_FIRST_NAME||' '||V_LAST_NAME);
END;
/

116
PL/SQL Practices

SET VERIFY OFF


SET ECHO OFF
SET SERVEROUTPUT ON
DECLARE
V_FIRST_NAME EMPLOYEES.FIRST_NAME%TYPE;
V_LAST_NAME EMPLOYEES.LAST_NAME%TYPE;
V_EMPID NUMBER(5):=&P_EMPID;
BEGIN
SELECT FIRST_NAME, LAST_NAME
INTO V_FIRST_NAME, V_LAST_NAME
FROM EMPLOYEES
WHERE EMPLOYEE_ID = V_EMPID;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No Employee with this id '||V_EMPID);
DBMS_OUTPUT.PUT_LINE('Employee Number:'||V_EMPID);
DBMS_OUTPUT.PUT_LINE('Name:'||V_FIRST_NAME||' '||V_LAST_NAME);
END;
/

117

Potrebbero piacerti anche