PL SQL
PL SQL
Features
PL/SQL provides the functionality of a procedural language such as decision making, iteration, etc.
We can also reuse PL/SQL units such as functions, triggers, procedures, etc that are stored in the database
after the creation.
PL/SQL also has an exception handling block that handles the exceptions in PL/SQL.
The applications written in PL/SQL are portable to other hardware and operating systems provided oracle
must be operational
PL/SQL vs SQL
SQL PL/SQL
SQL is a single query that is used to perform PL/SQL is a block of codes that is used to define
DDL and DML operations an entire program or procedure/function, etc
PL/SQL architecture
PL/SQL typically organizes the code into blocks. The code block with no name is known as an anonymous block. It is
known as the anonymous block because it is not saved in the oracle database. Let us take a look at an anonymous
block in PL/SQL.
1
[DECLARE]
2 declaration statements;
3 [BEGIN]
4 execution statements;
5 [EXCEPTION]
exception statements;
6
END;
7 /
8
Out of all these 4 sections, the execution section is mandatory and rest all are optional.
DECLARE keyword is used to for the declaration section is used to declare data types and structures such
as variables, functions, etc.
BEGIN keyword is used for the execution section. It is mandatory and contains all the statements that
need to be executed. This block is where the business logic is defined; we can use both procedural and
SQL statements in this block.
EXCEPTION keyword is used for the exception section. It contains all the exception statements.
END keyword marks the end of the block and the backward slash ‘/’ tells the tool that you are using
(Oracle Database Tool) to execute the PL/SQL block.
Each value in PL/SQL such as a constant, variable and parameter has a data type that determines the storage
format, valid values, and allowed operations.
PL/SQL has two kinds of data types: scalar and composite. The scalar types are types that store single values such
as number, Boolean, character, and datetime whereas the composite types are types that store multiple values, for
example, record and collection.
PL/SQL divides the scalar data types into four families:
PL/SQL Variables
The variable in PL/SQL is basically a name that varies or temporary storage location that supports a particular data
type.
The name of the variable should start with an ASCII character. Since PL/SQL is case-sensitive, an
uppercase letter and a lowercase letter will be different variables.
After the first character, there has to be a special character($,_ ) or any number.
Naming Conventions
Use the following naming conventions listed below to use the variables.
v_ VARCHAR2
n_ NUMBER
t_ TABLE
r_ ROW
d_ DATE
b_ BOOLEAN
Declaration
PL/SQL allows you to set a default value for a variable at the declaration time. To assign a default value to a
variable, you use the assignment operator (:=) or the DEFAULT keyword.
DECLARE
BEGIN
NULL;
END;
If you impose the NOT NULL constraint on a value, then the variable cannot accept a NULL value. Besides, a
variable declared with the NOT NULL must be initialized with a non-null value. Note that PL/SQL treats a zero-length
string as a NULL value.
DECLARE
BEGIN
l_shipping_status := '';
END;
Output:
Variable assignments
To assign a value to a variable, you use the assignment operator (:=), for example:
DECLARE
BEGIN
l_customer_group := 'Gold';
DBMS_OUTPUT.PUT_LINE(l_customer_group);
END;
Anchored declarations
Typically, you declare a variable and select a value from a table column to this variable. If the data type of the table
column changes, you must adjust the program to make it work with the new type.
PL/SQL allows you to declare a variable whose data type anchor to a table column or another variable.
DECLARE
l_customer_name customers.name%TYPE;
l_credit_limit customers.credit_limit%TYPE;
BEGIN
FROM customers
END;
Constants
Unlike a variable, a constant holds a value that does not change throughout the execution of the program.
Constants make your code more readable
To declare a constant, you specify the name, CONSTANT keyword, data type, and the default value. For Eg:
DECLARE
BEGIN
NULL;
END;
SELECT INTO
SELECT INTO statement is the simplest and fastest way to fetch a single row or data from a table into variables
SELECT select_list
INTO variable_list
FROM table_name
WHERE condition;
In this syntax, the number of columns in the variable_list must be the same as the number of variables in
the select_list. In addition, their corresponding data type must be compatible.
If the SELECT statement returns more than one row, Oracle will raise the TOO_MANY_ROWS exception.
If the SELECT statement does not return any row, Oracle will raise the NO_DATA_FOUND exception.
DECLARE
l_customer_name customers.name%TYPE;
l_contact_first_name contacts.first_name%TYPE;
l_contact_last_name contacts.last_name%TYPE;
BEGIN
END;
Output:
DECLARE
r_customer customers%ROWTYPE;
BEGIN
FROM customers
END;
Output:
If the number of columns and expression in the SELECT clause is greater than the number of variables in
the INTO clause, Oracle issues this error:
ORA-00947: not enough values The INTO list contains fewer variables than the SELECT list.
Oracle issues the following error if the number of columns and expression in the SELECT clause is less than
the number of variables in the INTO clause:
ORA-00913: too many values The INTO list contains more variables than the SELECT list.
If the number of variables and element in the select list are the same, but their corresponding datatypes
are not compatible so that Oracle cannot implicitly convert from one type to the other, it will issue the
following error:
IF Statement
The IF statement allows you to either execute or skip a sequence of statements, depending on a
condition. The IF statement has the three forms:
– IF THEN
– IF THEN ELSE
– IF THEN ELSIF
IF THEN statement
IF condition THEN
statements;
END IF;
The condition is a Boolean expression that always evaluates to TRUE, FALSE, or NULL. If the condition evaluates to
TRUE, the statements after the THEN execute. Otherwise , the IF statement does nothing.
IF condition THEN
statements;
else_statements c
END IF;
If the condition evaluates to TRUE, then the statements between THEN and ELSE execute. In case the condition
evaluates to FALSE or NULL, the else_statements between ELSE and END IF executes.
IF condition_1 THEN
statements_1
statements_3
...
[ELSIF
else_statements
END IF;
Starting from 1st condition, each condition between ELSEIF and THEN is evaluated only if the preceding condition is
FALSE. If a condition is true, other subsequent conditions are not evaluated. If no condition is true,
the else_statements between the ELSE and ENDIF execute. In case you skip the the ELSE clause and no condition is
TRUE, then the IF THEN ELSIF does nothing.
Nested IF statement
IF condition_1 THEN
IF condition_2 THEN
nested_if_statements;
END IF;
ELSE
else_statements;
END IF;
CASE statement
A simple CASE statement evaluates a single expression and compares the result with some values.
CASE selector
statements_1
WHEN selector_value_2 THEN
statement_2
...
ELSE
else_statements
END CASE;
Unlike IF THEN statement, PL/SQL raises a CASE_NOT_FOUND error if you don’t specify an ELSE clause and the
result of the CASE expression does not match any value in the WHEN clauses.
Example:
DECLARE
c_grade CHAR( 1 );
c_rank VARCHAR2( 20 );
BEGIN
c_grade := 'B';
CASE c_grade
c_rank := 'Excellent' ;
c_rank := 'Good' ;
c_rank := 'Fair' ;
c_rank := 'Poor' ;
ELSE
END CASE;
DBMS_OUTPUT.PUT_LINE ( c_rank );
END;
The searched CASE statement evaluates multiple Boolean expressions and executes the sequence of statements
associated with the first condition that evaluates to TRUE.
CASE
...
[ELSE
else_statements ]
END CASE;
The conditions in the WHEN clauses are evaluated in order, from top to bottom.
The sequence of statements associated with the WHEN clause whose condition evaluates to TRUE is
executed. If more than one condition evaluates to TRUE, only the first one executes.
If no condition evaluates to TRUE, the else_statements in the ELSE clause executes. If you skip
the ELSE clause and no expressions are TRUE, a CASE_NOT_FOUND exception is raised.
DECLARE
n_sales NUMBER;
n_commission NUMBER;
BEGIN
n_sales := 150000;
CASE
n_commission := 0.2;
n_commission := 0.15;
n_commission := 0.05;
ELSE
n_commission := 0;
END CASE;
END;
As a thumb rule, use a simple CASE statement when you want to execute a sequence of statements based on the
result of a single expression and use a searched CASE statement when you want to execute a sequence of
statements based on the results of multiple Boolean expressions.
GOTO statement
The GOTO statement allows you to transfer control to a labeled block or statement. Following is the syntax:
GOTO label_name;
The label_name is the name of a label that identifies the target statement. In the program, you surround the label
name with double enclosing angle brackets as shown below:
<<label_name>>;
Example:
BEGIN
GOTO second_message;
<<first_message>>
DBMS_OUTPUT.PUT_LINE ( 'Hello' );
GOTO the_end;
<<second_message>>
GOTO first_message;
<<the_end>>
1. You cannot use a GOTO statement to transfer control into an IF, CASE or LOOP statement, the same for
sub-block.
DECLARE
n_sales NUMBER;
n_tax NUMBER;
BEGIN
GOTO inside_if_statement;
<<inside_if_statement>>
END IF;
END;
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'INSIDE_IF_STATEMENT'
2. You cannot use a GOTO statement to transfer control from one clause to another in the IF statement e.g.,
from IF clause to ELSIF or ELSE clause, or from one WHEN clause to another in the CASE statement.
DECLARE
n_sales NUMBER;
n_commission NUMBER;
BEGIN
n_sales := 120000;
n_commission := 0.2;
GOTO zero_commission;
n_commission := 0.15;
n_commission := 0.1;
ELSE
<<zero_commission>>
n_commission := 0;
END IF;
END;
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'ZERO_COMMISSION'
3. You cannot use a GOTO statement to transfer control out of a subprogram or into an exception handler
4. You cannot use a GOTO statement to transfer control from an exception handler back into the current
block.
NULL statement
The NULL statement is a NULL keyword followed by a semicolon (;). The NULL statement does nothing except that it
passes control to the next statement.
send_email;
ELSE
NULL;
END IF;
DECLARE
b_status BOOLEAN
BEGIN
IF b_status THEN
GOTO end_of_program;
END IF;
-- ...
<<end_of_program>>
NULL;
END;
AS
BEGIN
NULL;
END;
LOOP syntax
<<label>> LOOP
statements;
The LOOP statement can have an optional label that appears at the beginning and the end of the statement.
It is a good practice to use the LOOP statement when:
You are not sure the number of times you want the loop to execute.
EXIT statement
The EXIT statement allows you to unconditionally exit the current iteration of a loop.
The following example illustrates how to use the LOOP statement to execute a sequence of code
and EXIT statement to terminate the loop
DECLARE
l_counter NUMBER := 0;
BEGIN
LOOP
l_counter := l_counter + 1;
EXIT;
END IF;
END LOOP;
END;
output:
Inside loop: 1
Inside loop: 2
Inside loop: 3
After loop: 4
The EXIT WHEN statement exits the current iteration of a loop when the condition in the WHEN clause is TRUE.
Essentially, the EXIT WHEN statement is a combination of an EXIT and an IF THEN statement.
DECLARE
l_counter NUMBER := 0;
BEGIN
LOOP
l_counter := l_counter + 1;
END LOOP;
END;
Nested loops
It is possible to nest a LOOP statement within another LOOP statement as shown in the following example:
DECLARE
l_i NUMBER := 0;
l_j NUMBER := 0;
BEGIN
<<outer_loop>>
LOOP
l_i := l_i + 1;
l_j := 0;
<<inner_loop>> LOOP
l_j := l_j + 1;
END;
In this example, the loop index is l_counter, lower_bound is one, and upper_bound is five. The loop shows a list of
integers from 1 to 5.
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE( l_counter );
END LOOP;
END;
Output:
1
2
3
4
5
The loop index is increased by one after each loop iteration.You can change the increment e.g., two, three and four
by using an additional variable to simulate the increment by two, three, four, etc., as shown in the example below:
DECLARE
l_step PLS_INTEGER := 2;
BEGIN
dbms_output.put_line (l_counter*l_step);
END LOOP;
END;
Result:
2
4
6
8
10
DECLARE
BEGIN
DBMS_OUTPUT.PUT_LINE (l_counter);
END LOOP;
DBMS_OUTPUT.PUT_LINE (l_counter);
END;
1
2
3
4
5
10
To reference the variable l_counter inside the loop, you must qualify it using a block label as shown below:
<<outer>>
DECLARE
BEGIN
outer.l_counter := l_counter;
END LOOP;
END outer;
Referencing loop index outside the FOR LOOP
BEGIN
DBMS_OUTPUT.PUT_LINE (l_index);
END LOOP;
DBMS_OUTPUT.PUT_LINE (l_index);
END;
BEGIN
DBMS_OUTPUT.PUT_LINE( l_counter );
END LOOP;
END;
Result:
3
2
1
WHILE loop
PL/SQL WHILE loop statement executes a sequence of statements as long as a specified condition is TRUE.
Syntax:
WHILE condition
LOOP
statements;
END LOOP;
Example:
DECLARE
n_counter NUMBER := 1;
BEGIN
LOOP
n_counter := n_counter + 1;
END LOOP;
END;
CONTINUE statement
The CONTINUE statement allows you to exit the current loop iteration and immediately continue on to the next
iteration of that loop. Typically, the CONTINUE statement is used within an IF THEN statement to exit the current
loop iteration based on a specified condition.
Example:
BEGIN
FOR n_index IN 1 .. 10
LOOP
CONTINUE;
END IF;
DBMS_OUTPUT.PUT_LINE( n_index );
END LOOP;
END;
output is:
2
4
6
8
10
BEGIN
FOR n_index IN 1 .. 10
LOOP
DBMS_OUTPUT.PUT_LINE( n_index );
END LOOP;
END;
output:
1
3
5
7
9
PL/SQL treats all errors that occur in an anonymous block, procedure, or function as exceptions which can
have different causes such as coding mistakes, bugs, even hardware failures.
A PL/SQL block can have an exception-handling section, which can have one or more exception handlers.
Syntax:
BEGIN
-- executable section
...
-- exception-handling section
EXCEPTION
WHEN e1 THEN
-- exception_handler1
WHEN e2 THEN
-- exception_handler1
-- other_exception_handler
END;
Example:
DECLARE
l_name customers.NAME%TYPE;
BEGIN
FROM customers
EXCEPTION
END;
Internally defined exceptions are errors which arise from the Oracle Database environment. The
runtime system raises the exception automatically for e.g. ORA-27102 (out of memory). Note: Internally
defined exceptions do not have names, but an error code.
Predefined exceptions are errors which occur during the execution of the program. The predefined
exceptions are internally defined exceptions that PL/SQL has given names
e.g., NO_DATA_FOUND, TOO_MANY_ROWS.
User-defined exceptions are custom exception defined by users like you. User-defined exceptions must
be raised explicitly.
Raise a Exception
RAISE statement to raise a user-defined exception, internally defined exception, and re-raising an exception.
SYNTAX:
DECLARE
exception_name EXCEPTION;
If you want to include a custom message, you can replace the line:
RAISE EXCEPTIONNAME;
raise_application_error(ERROR_CODE,ERROR_MESSAGE);
EXAMPLE:
DECLARE
ex_custom EXCEPTION;
PRAGMA EXCEPTION_INIT( ex_custom, -20001 );
BEGIN
END;
You can explicitly raise an internally defined exception with the RAISE statement if the exception has a
name:
RAISE exception_name;
Example
DECLARE
BEGIN
RAISE invalid_number;
END IF;
END;
If you execute the block and enter the customer id -10, you will get the following error:
You can re-raise the current exception with the RAISE statement (no need to specify the exception name).
Re-raising an exception passes it to the enclosing block, which later can be handled further.
Example:
DECLARE
e_credit_too_high EXCEPTION;
l_max_credit customers.credit_limit%TYPE;
BEGIN
BEGIN
SELECT MAX(credit_limit)
INTO l_max_credit
FROM customers;
RAISE e_credit_too_high;
END IF;
EXCEPTION
END;
EXCEPTION
SELECT avg(credit_limit)
into l_credit
from customers;
-- adjust the credit limit to the average
UPDATE customers
COMMIT;
END;
PRAGMA Keyword
The PRAGMA keyword is generally a line of source code prescribing an action you want the compiler to
take. A PRAGMA simply passes information to the compiler rather than getting transformed into a
particular execution ( byte-code).
Syntax:
PRAGMA instruction_to_compiler;
a. AUTONOMOUS_TRANSACTION
This pragma instructs the PL/SQL compiler to establish a PL/SQL block as autonomous or independent.
Once started, an autonomous transaction is fully independent. It shares no locks, resources, or
commit-dependencies with the main transaction.
You can log events, increment retry counters, and so on, even if the main transaction rolls back.
b. EXCEPTION_INIT
Tells the compiler to associate a particular error number with an identifier you have declared as an
c. RESTRICT_REFERENCES
Used to control the side effects of PL/SQL Subprograms. Every PL/SQL Subprograms must follow some
SYNTAX:
TRUST]);
DEFAULT: Specifies that the pragma applies to all subprograms in the package specification or object
type specification.
RNDS (Read No Database State): Asserts that the subprogram reads no database state (does not
WNDS (Write No Database State): Asserts that the subprogram writes no database state (does not
modify tables).
RNPS (Read No Package State): Asserts that the subprogram reads no package state (does not
WNPS (Write No Package State): Asserts that the subprogram writes no package state (does not
TRUST: Asserts that the subprogram can be trusted not to violate one or more rules.
d. SERIALLY_REUSABLE
The SERIALLY_REUSABLE Pragma specifies that the package state is needed for only one call to the
server.
After this call, the storage for the package variables can be reused, reducing the memory overhead
This Pragma is appropriate for packages that declare large temporary work areas that are used once
raise_application_error procedure:
The procedure raise_application_error allows you to issue an user-defined error from a code block or stored
program.
SYNTAX:
raise_application_error (error_number, message, [{TRUE | FALSE}]);
When the procedure raise_application_error executes, Oracle halts the execution of the current block
immediately. It also reverses all changes made to the OUT or IN OUT parameters.
Changes made to the global data structure such as packaged variables, and database objects like tables
will not be rolled back. Therefore, you must explicitly execute the ROLLBACK statement to reverse the
effect of the DML.
EXCEPTION PROPAGATION
When an exception occurs, PL/SQL looks for an exception handler in the current block e.g., anonymous
block, procedure, or function of the exception. If it does not find a match, PL/SQL propagates the exception
to the enclosing block of the current block.
PL/SQL then attempts to handle the exception by raising it once more in the enclosing block. This process
continues in each successive enclosing block until there is no remaining block in which to raise the
exception. If there is no exception handler in all blocks, PL/SQL returns an unhandled exception (which
stops the execution of the block) to the application environment.
EXAMPLE:
DECLARE
e1 EXCEPTION;
PRAGMA exception_init (e1, -20001);
e2 EXCEPTION;
PRAGMA exception_init (e2, -20002);
e3 EXCEPTION;
PRAGMA exception_init (e2, -20003);
l_input NUMBER := &input_number;
BEGIN
-- inner block
BEGIN
IF l_input = 1 THEN
raise_application_error(-20001,'Exception: the input number is 1');
ELSIF l_input = 2 THEN
raise_application_error(-20002,'Exception: the input number is 2');
ELSE
raise_application_error(-20003,'Exception: the input number is not 1 or 2');
END IF;
-- exception handling of the inner block
EXCEPTION
WHEN e1 THEN
dbms_output.put_line('Handle exception when the input number is 1');
END;
-- exception handling of the outer block
EXCEPTION
WHEN e2 THEN
dbms_output.put_line('Handle exception when the input number is 2');
END;
/
Because the input is 1, the inner block raises the e1 exception. The exception-handling part of the inner block
handles the e1 exception locally, therefore, the execution of the block resumes in the enclosing block.
CASE2: Passing 2 as the input number.
The inner block raises the e2 exception. Because the inner block does not have an exception handler to handle the
e2 exception, PL/SQL propagates the e2 exception to the enclosing block.
The enclosing block has an exception handler that handles e2 exception. Therefore control passes to the host
environment (SQL*Plus or SQL Developer).
In this case, both inner block and enclosing block has no exception handler to handle the e3 exception. Therefore,
the block returns an unhandled exception to the host environment.
Handling Other Unhandled Exceptions
In exception handling section, you can include the WHEN OTHERS clause to catch any otherwise
unhandled exceptions. You can take leverage of the built-in error functions such
as SQLCODE and SQLERRM
Note that you cannot use SQLCODE or SQLERRM function directly in an SQL statement. Instead, you must
first assign their returned values to variables, and then use the variables in the SQL statement.
SQLCODE function
o The SQLCODE function accepts no argument and returns a number code of the most recent exception.
o If the exceptions are internal, SQLCODE returns a negative number (except NO_DATA_FOUND exception which
has +100).If the exception is user-defined, SQLCODE returns +1 or the number that you associated with the
exception via the pragma EXCEPTION_INIT.
o The SQLCODE is only usable in the exception-handling section. If you use the SQLCODE function outside an
exception handler, it always returns zero.
o Example:
DECLARE
l_code NUMBER;
r_customer customers%rowtype;
BEGIN
SELECT * INTO r_customer FROM customers;
EXCEPTION
WHEN OTHERS THEN
l_code := SQLCODE;
dbms_output.put_line('Error code:' || l_code);
END;
/
Output:
Error code:-1422
SQLERRM function
o The function SQLERRM takes an argument as an error number (valid Oracle error number) and returns the
error message associated with that error number:
o If you omit the error_number argument, the function will return the error message associated with the
current value of SQLCODE.
o Note that the SQLERRM function with no argument is only useful in an exception handler.
o Example:
DECLARE
l_msg VARCHAR2(255);
r_customer customers%rowtype;
BEGIN
SELECT * INTO r_customer FROM customers;
EXCEPTION
WHEN OTHERS THEN
l_msg := SQLERRM;
dbms_output.put_line(l_msg);
END;
/
Output:
ORA-01422: exact fetch returns more than requested number of rows
PL/SQL Record
A PL/SQL record is a composite data structure which consists of multiple fields; each has its own value. E.g. :
Before using a record, you must declare it.PL/SQL has three types of records:
I. Table-based
II. Cursor-based,
III. Programmer-defined
Table-based record
To declare a table-based record, you use the %ROWTYPE attribute with a table name.
A table-based record has each field corresponding to a column in a table.
SYNTAX:
DECLARE
record_name table_name%ROWTYPE;
Cursor-based record
A cursor-based record has each field corresponding a column or alias in the cursor SELECT statement.
To declare a cursor-based record, you use the %ROWTYPE attribute with an explicit cursor as shown below:
DECLARE
CURSOR c_contacts IS
SELECT first_name, last_name, phone
FROM contacts;
r_contact c_contacts%ROWTYPE;
Programmer-defined record
If you want to create a record whose structure is not based on the existing ones, then you use
programmer-defined record.
To declare a programmer-defined record, you use the following steps:
1. Define a record type that contains the structure you want in your record.
2. Declare a record based on the record type.
DECLARE
-- define a record type
TYPE r_customer_contact_t IS
RECORD (
customer_name customers.name%TYPE,
first_name contacts.first_name%TYPE,
last_name contacts.last_name%TYPE );
r_customer_contacts r_customer_contact_t; -- declare a record
BEGIN
NULL;
END;
You reference a field in a record via the dot notation as shown below :
record_name.field_name
You can assign a record to another record of the same type, for example :
r_contact1 := r_contact2;
You cannot compare two records of the same type via a comparison operator. The following example is an
invalid comparison:
In this case, you need to compare individual fields of the record instead:
You can use SELECT INTO a whole record (or individual fields):
You can insert a new row into a table using a %ROWTYPE record without having to specify each field.
CREATE TABLE persons (
person_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
first_name VARCHAR2( 50 ) NOT NULL,
last_name VARCHAR2( 50 ) NOT NULL,
primary key (person_id)
);
The following block inserts a new row into the persons table using a %ROWTYPE record:
DECLARE
r_person persons%ROWTYPE;
BEGIN
-- assign values to person record
r_person.person_id := 1;
r_person.first_name := 'John';
r_person.last_name := 'Doe';
To update a row from a %ROWTYPE record, you use the SET ROW keywords as shown in the following
example:
DECLARE
r_person persons%ROWTYPE;
BEGIN
-- get person data of person id 1
SELECT * INTO r_person
FROM persons
WHERE person_id = 1;
Nested record
A record can contain a field which is another record. Nesting records is a powerful way to structure your
program data and hide complexity in your code.
DECLARE
TYPE address IS RECORD (
street_name VARCHAR2(255),
city VARCHAR2(100),
state VARCHAR2(100),
postal_code VARCHAR(10),
country VARCHAR2(100)
);
TYPE customer IS RECORD(
customer_name VARCHAR2(100),
ship_to address,
bill_to address
);
r_one_time_customer customer;
BEGIN
Cursor
Databases such as ORACLE have a memory area, where processing of instructions and fetched data takes place. A
cursor is a pointer which is pointing to this .PL/SQL has two types of cursors: implicit cursors and explicit cursors.
I. Implicit cursors
Whenever Oracle executes an SQL statement such as SELECT INTO, INSERT, UPDATE, and DELETE, it
automatically creates an implicit cursor.
Oracle internally manages the whole execution cycle of implicit cursors and reveals only the cursor’s
information and statuses such as SQL%ROWCOUNT, SQL%ISOPEN, SQL%FOUND, and SQL%NOTFOUND.
The implicit cursor is not elegant when the query returns zero or multiple rows which
cause NO_DATA_FOUND or TOO_MANY_ROWS exception respectively.
An explicit cursor is an SELECT statement declared explicitly in the declaration section of the current block
or a package specification.
For an explicit cursor, you have control over its execution cycle from OPEN, FETCH, and CLOSE.
Oracle defines an execution cycle that executes an SQL statement and associates a cursor with it.The
following illustration shows the execution cycle of an explicit cursor:
a. Declare a cursor
Before using an explicit cursor, you must declare it in the declaration section of a block or package as
follows:
In this syntax:
First, specify the name of the cursor after the CURSOR keyword.
Second, define a query to fetch data after the IS keyword.
b. Open a cursor
Before start fetching rows from the cursor, you must open it. To open a cursor, you use the following
syntax:
OPEN cursor_name;
When you open a cursor, Oracle parses the query, binds variables, and executes the associated SQL
statement.
Oracle also determines an execution plan, associates host variables and cursor parameters with the
placeholders in the SQL statement, determines the result set, and sets the cursor to the first row in the
result set.
The FETCH statement places the contents of the current row into variables. The syntax
of FETCH statement is as follows:
To retrieve all rows in a result set, you need to fetch each row till the last one
d. Closing a cursor
After fetching all rows, you need to close the cursor with the CLOSE statement:
CLOSE cursor_name;
If you declare a cursor in an anonymous block, procedure, or function, the cursor will automatically be
closed when the execution of these objects end.
However, you must explicitly close package-based cursors. Note that if you close a cursor that has not
opened yet, Oracle will raise an INVALID_CURSOR exception.
Explicit Cursor Attributes
A cursor has four attributes to which you can reference in the following format:
cursor_name%attribute
i. %ISOPEN
ii. %FOUND
The %ROWCOUNT attribute returns the number of rows fetched from the cursor. If the cursor is not
opened, this attribute returns INVALID_CURSOR
Cursor Example
The following is a complete example of cursor for printing a list of chief and name of departments as follows:
DECLARE
-- declare a cursor
CURSOR cur_chief IS
SELECT first_name, last_name, department_name
FROM employees e
INNER JOIN departments d ON d.manager_id = e.employee_id;
r_chief cur_chief%ROWTYPE;
BEGIN
OPEN cur_chief;
LOOP
END;
/
The cursor FOR LOOP executes the body of the loop once for each row returned by the query associated
with the cursor.
A nice feature of the cursor FOR LOOP statement is that it allows you to fetch every row from a cursor
without manually managing the execution cycle i.e., OPEN, FETCH, and CLOSE.
The cursor FOR LOOP implicitly creates its loop index as a record variable with the row type in which the
cursor returns and then opens the cursor.
In each loop iteration, the cursor FOR LOOP statement fetches a row from the result set into its loop index.
If there is no row to fetch, the cursor FOR LOOP closes the cursor.
The cursor is also closed if a statement inside the loop transfers control outside the loop,
e.g., EXIT and GOTO, or raises an exception.
SYNTAX:
a. record
The record is the name of the index that the cursor FOR LOOP statement declares implicitly as
a %ROWTYPE record variable of the type of the cursor.
b. cursor_name
The cursor_name is the name of an explicit cursor that is not opened when the loop starts.
Note that besides the cursor name, you can use a SELECT statement as shown below:
DECLARE
CURSOR c_product IS
SELECT product_name, list_price
FROM products
ORDER BY list_price DESC;
BEGIN
FOR r_product IN c_product
LOOP
dbms_output.put_line( r_product.product_name || ': $' || r_product.list_price );
END LOOP;
END;
BEGIN
FOR r_product IN (
SELECT product_name, list_price
FROM products
ORDER BY list_price DESC
)
LOOP
dbms_output.put_line( r_product.product_name ||': $' || r_product.list_price );
END LOOP;
END;
Parameterized Cursor
An explicit cursor may accept a list of parameters. Each time you open the cursor, you can pass different
arguments to the cursor, which results in different result sets.
Example:
DECLARE
r_product products%rowtype;
BEGIN
OPEN c_product(50,100);
LOOP
FETCH c_product INTO r_product;
EXIT WHEN c_product%notfound;
CLOSE c_product;
END;
/
A parameterized cursor can have default values for its parameters as shown below:
CURSOR cursor_name (
parameter_name datatype := default_value,
parameter_name datatype := default_value,
...
) IS
cursor_query;
DECLARE
CURSOR c_revenue (in_year NUMBER :=2017 , in_customer_id NUMBER := 1)
IS
SELECT SUM(quantity * unit_price) revenue
FROM order_items
INNER JOIN orders USING (order_id)
WHERE status = 'Shipped' AND EXTRACT( YEAR FROM order_date) = in_year
GROUP BY customer_id
HAVING customer_id = in_customer_id;
r_revenue c_revenue%rowtype;
BEGIN
OPEN c_revenue;
LOOP
FETCH c_revenue INTO r_revenue;
EXIT WHEN c_revenue%notfound;
-- show the revenue
dbms_output.put_line(r_revenue.revenue);
END LOOP;
CLOSE c_revenue;
END;
Cursor variables
A cursor variable is a variable that references to a cursor. Different from implicit and explicit cursors, a
cursor variable is not tied to any specific query (a cursor variable can be opened for any query).
The most important benefit of a cursor variable is that it enables passing the result of a query between
PL/SQL programs. Without a cursor variable, you have to fetch all data from a cursor, store it in a variable
e.g., a collection, and pass this variable as an argument. With a cursor variable, you simply pass the
reference to that cursor.
To declare a cursor variable, you use the REF CURSOR is the data type. PL/SQL has two forms of REF
CURSOR typeS: strong typed and weak typed REF CURSOR where strong typed cursor variable is always
associated with a specific record structure or type while a weak typed cursor variable is not associated
with any specific structure.
DECLARE
TYPE customer_t IS REF CURSOR RETURN customers%ROWTYPE;
c_customer customer_t;
DECLARE
TYPE customer_t IS REF CURSOR;
c_customer customer_t;
RETURN c_direct_reports;
END;
The following anonymous block calls the get_direct_reports() function and processes the cursor variable to
display the direct reports of the manager with id of 46
DECLARE
c_direct_reports SYS_REFCURSOR;
l_employee_id employees.employee_id%TYPE;
l_first_name employees.first_name%TYPE;
l_last_name employees.last_name%TYPE;
l_email employees.email%TYPE;
BEGIN
END LOOP;
Example:
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line( SQLERRM );
END;
Procedure header
A procedure begins with a header that specifies its name and an optional parameter list.
Each parameter can be in either IN, OUT, or INOUT mode. The parameter mode specifies whether a
parameter can be read from or write to.
a. IN
An IN parameter is read-only. You can reference an IN parameter inside a procedure, but you cannot
change its value. Oracle uses IN as the default mode.
b. OUT
An OUT parameter is writable. Typically, you set a returned value for the OUT parameter and return it
to the calling program.
c. INOUT
An INOUT parameter is both readable and writable. The procedure can read and modify it.
The OR REPLACE option allows you to overwrite the current procedure with the new code.
Procedure body
Similar to an anonymous block, the procedure body has three parts. The executable part is mandatory
whereas the declarative and exception-handling parts are optional.
a. Declarative part: declare variables, constants, cursors, etc. Unlike an anonymous block, it doesn’t
start with the DECLARE keyword.
b. Executable part: contains one or more statements that implement specific business logic
c. Exception-handling part: This part contains the code that handles exceptions.
Executing a procedure
Or
Removing a procedure
To delete a procedure, you use the DROP PROCEDURE followed by the procedure’s name as shown in the following
syntax:
dbms_sql.return_result(c_total_row);
dbms_sql.return_result(c_customers);
END;
get_next_result() procedure
If you want to process result sets using PL/SQL, you can use the get_next_resultset() procedure in
the DBMS_SQL package.
The following anonymous block calls the get_customers() procedure and uses
the get_next_resultset() procedure to process the result sets:
SET SERVEROUTPUT ON
DECLARE
l_sql_cursor PLS_INTEGER;
c_cursor SYS_REFCURSOR;
l_return PLS_INTEGER;
l_column_count PLS_INTEGER;
l_desc_tab dbms_sql.desc_tab;
l_total_rows NUMBER;
l_customer_id customers.customer_id%TYPE;
l_name customers.NAME%TYPE;
BEGIN
-- Execute the function.
l_sql_cursor := dbms_sql.open_cursor(treat_as_client_for_results => TRUE);
dbms_output.put_line(l_total_rows);
CLOSE c_cursor;
WHEN 2 THEN
dbms_output.put_line('The customer list:');
LOOP
FETCH c_cursor
INTO l_customer_id, l_name;
Function
Similar to a procedure, a PL/SQL function is a reusable program unit stored as a schema object in the
Oracle Database
1) In an assignment statement:
DECLARE
l_sales_2017 NUMBER := 0;
BEGIN
l_sales_2017 := get_total_sales (2017);
DBMS_OUTPUT.PUT_LINE('Sales 2017: ' || l_sales_2017);
END;
2) In a Boolean expression:
BEGIN
END;
3) In a SQL statement:
SELECT get_total_sales(2017)
FROM dual;
Removing a function
The DROP FUNCTION deletes a function from the Oracle Database. The syntax for removing a function is
straightforward:
Packages
A package is a schema object that contains definitions for a group of related functionalities.
A package includes variables, constants, cursors, exceptions, procedures, functions, and subprograms.
A PL/SQL package has two parts: package specification and package body.
A package specification is the public interface of your applications. The public means the stored
function, procedures, types, etc., are accessible from other applications.
A package body contains the code that implements the package specification. It can also contain
private variables, cursors, etc., used only by package body itself.
Package Specification
The package specification is where you declare public items. By default, the scope of package items is
the schema of the package.
A package specification does not contain any implementations of the public items.
A package specification can exist independently if their items do not require implementations .
Procedures
Functions
Cursors
Types, variables, and constants
Records
Collections
SYNTAX:
EXAMPLE:
END order_mgmt;
package_name.item_name
Package body
Package body contains implementation of every cursor or subprogram declared in the package
specification. It may also have private items that are accessible only within itself.
A package body can have an initialization part which consists of statements that initialize
public variables and do other one-time setup tasks. The initialization part only runs once at the first time
the package is referenced. It can also include an exception handler.
SYNTAX:
RETURN p_order_id;
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );
END get_net_value;
RETURN ln_net_value;
EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );
END get_net_value_by_customer;
END order_mgmt;
DROP PACKAGE
To drop a package, you use the DROP PACKAGE statement with the following syntax:
If you want to drop only the body of the package, you need to specify the BODY keyword. If you omit
the BODY keyword, then the statement drops both the body and specification of the package.
The schema specifies the name of the schema that contains the package. If you omit schema, then Oracle
assumes the package is in your own schema.
Procedure can return zero Function can return only single value
2
or more values as output. as output
RETURN will simply exit the RETURN will exit the control from
6
control from subprogram subprogram and also returns the value
Section 6. Triggers
A trigger is a named PL/SQL block stored in the Oracle Database and executed automatically when a
triggering event takes place. The event can be any of the following:
o A data manipulation language (DML) statement executed against a table e.g., INSERT, UPDATE,
or DELETE. For example, if you define a trigger that fires before an INSERT statement on
the customers table, the trigger will fire once before a new row is inserted into
the customers table.
o A data definition language (DDL) statement executes e.g., CREATE or ALTER statement. These
triggers are often used for auditing purposes to record changes of the schema.
o A system event such as startup or shutdown of the Oracle Database.
o A user event such as login or logout.
Triggers are useful in many cases such as the following:
o Enforcing complex business rules that cannot be established using integrity constraint such
as UNIQUE, NOT NULL, and CHECK.
o Preventing invalid transactions.
o Gathering statistical information on table accesses.
o Generating value automatically for derived columns.
o Auditing sensitive data.
Create trigger:
o BEFORE | AFTER: specifies when the trigger fires, either before or after a triggering event
o FOR EACH ROW: Specifies that the trigger is a row-level trigger. A row-level trigger fires once
for each row inserted, updated, or deleted.
Besides the row-level triggers, we have statement-level triggers which fires once regardless of
the number of rows affected by the triggering event. If you omit the FOR EACH ROW clause,
the CREATE TRIGGER statement will create a statement-level trigger.
o ENABLE / DISABLE: It specifies whether the trigger is created in the enabled or disabled state.
Default is enabled
o FOLLOWS | PRECEDES another_trigger: For each triggering, you can define multiple triggers to fire
along with specifying the firing sequence using FOLLOWS or PRECEDES.
o Example
END;
/
Statement-level Triggers
If we want restrict users to update credit of customers from 28th to 31st of every month so
that you can close the financial month.
CREATE OR REPLACE TRIGGER customers_credit_trg
BEFORE UPDATE OF credit_limit
ON customers
DECLARE
l_day_of_month NUMBER;
BEGIN
-- determine the transaction type
l_day_of_month := EXTRACT(DAY FROM sysdate);
Output:
Row-level triggers
Row-level triggers fires once for each row affected by the triggering event such as INSERT, UPDATE,
or DELETE.
Row-level triggers are useful for data-related activities such as data auditing and data validation.
To create a new row-level trigger, you use the CREATE TRIGGER statement with the FOR EACH ROW clause.
On top of that, row-level triggers allow you to track the BEFORE and AFTER values.
SYNTAX :
Because row-level triggers execute within the context of a single row, you can access the old and new
column values using the following syntax:
:OLD.column_name
:NEW.column_name
This table illustrates the availability of :NEW and :OLD variables by the triggering event:
INSERT Yes No
DELETE No yes
A BEFORE row-level trigger can modify the new column values, but an AFTER row-level trigger cannot.
Correlation names
OLD and NEW are the default correlation names. But you can override them using
the REFERENCING clause. When you reference OLD and NEW in the trigger body, you must precede them
with a colon (:) because OLD and NEW are external variable references.
Performance consideration
A row-level trigger fires each time a row is affected by a triggering event, which potentially cause a
performance issue.
To specify a condition of when to fire the trigger, you can use the WHEN clause. In some situations, using
the condition in the WHEN can significantly improve the performance of the database.
You can use both OLD and NEW in the WHEN clause (no need to use a colon (:) as the prefix)
Input: UPDATE customers SET credit_limit = 5000 WHERE customer_id = 10;// current credit_limit = 2000
Output :
ORA-20101: The new credit 5000 cannot increase more than double, the current credit
2000
ORA-06512: at "OT.CUSTOMERS_UPDATE_CREDIT_TRG", line 4
ORA-04088: error during execution of trigger 'OT.CUSTOMERS_UPDATE_CREDIT_TRG'
INSTEAD OF Triggers
An INSTEAD OF trigger allows you to update data in tables via their view which cannot be modified directly
through DML statements.
When you issue a DML statement such as INSERT, UPDATE, or DELETE to a non-updatable view, Oracle will
issue an error. If the view has an INSTEAD OF trigger, it will automatically skip the DML statement and
execute other DML statements instead.
Note that an INSTEAD OF trigger is fired for each row of the view that gets modified.
In Oracle, you can create an INSTEAD OF trigger for a view only. You cannot create an INSTEAD OF trigger
for a table.
SYNTAX:
INSERT INTO
vw_customers( name, address, website, credit_limit, first_name, last_name, email, phone)
VALUES( 'Lam Research', 'Fremont, California, USA', 'https://www.lamresearch.com/',2000,
'John', 'Smith', 'john.smith@lamresearch.com', '+1-510-572-0200');
END;
*Using the same insert query as above will result in insertion of record.
Disable Triggers
ALTER TRIGGER trigger_name DISABLE; // Disable single trigger
Sometimes, you may want to create a disabled trigger which is a trigger is the disabled state .
For example, you want to create a trigger during the business hours and do not want to impact the
current transactions.
To do it safely, you can create a trigger in the disabled state first. And then you enable it later during
the maintenance hours or at the weekend.
Example:
Enable trigger
To enable a previously disabled trigger, you use the ALTER TRIGGER ENABLE statement:
DROP TRIGGER
The DROP TRIGGER statement allows you to remove a trigger from the database.
If you attempt to remove a trigger that does not exist, Oracle will issue the error ORA-04080, indicating
that the trigger does not exist. You can develop a procedure that combines the DROP TRIGGER statement
with dynamic SQL to drop a trigger only if it exists as follows:
END;
/
Section 7. PL/SQL Collections
Associative arrays
Associative arrays are single-dimensional, unbounded (predetermined limits number of elements), sparse
collections of homogeneous elements.
It is sparse because its elements are not sequential and may have gaps between elements.
SYNTAX:
Associative arrays have a number of useful methods for accessing array element index and manipulating elements.
a. FIRST: returns the first index of the array. If an array is empty, the FIRST method returns NULL.
b. NEXT(n): returns the index that succeeds the index n. If n has no successor, then
the NEXT(n) returns NULL.
DECLARE
-- declare an associative array type
TYPE t_capital_type
IS TABLE OF VARCHAR2(100)
INDEX BY VARCHAR2(50);
l_country := t_capital.FIRST;
output:
nested_table_variable nested_table_type;
It is possible to create a nested table type located in the database:
CREATE [OR REPLACE] TYPE nested_table_type
IS TABLE OF element_datatype [NOT NULL];
nested_table_variable := nested_table_type();
Declaring and initializing a nested table it in one step using the following syntax:
nested_table_variable nested_table_type := nested_table_type();
To add an element to a nested table, you first use the EXTEND method:
nested_table_variable.EXTEND;
Then, use the assignment operator (:=) to add an element
nested_table_variable := element;
If you want to add multiple elements, you use the EXTEND(n) method, where n is the number
of elements that you want to add:
nested_table_variable.EXTEND(n);
nested_table_variable := element_1;
nested_table_variable := element_2;
..
nested_table_variable := element_n;
Nested tables have the FIRST and LAST methods that return the first and last indexes of elements
respectively.These methods can be used to iterate over the elements using a FOR loop:
Example:
DECLARE
-- declare a cursor that return customer name
CURSOR c_customer IS
SELECT name
FROM customers
ORDER BY name
FETCH FIRST 10 ROWS ONLY;
BEGIN
-- populate customer names from a cursor
FOR r_customer IN c_customer
LOOP
t_customer_names.EXTEND;
t_customer_names(t_customer_names.LAST) := r_customer.name;
END LOOP;
output:
3M
ADP
AECOM
AES
AIG
AT&T
AbbVie
Abbott Laboratories
type_name:VARRAY type
Before using a VARRAY variable, you must initialize it. Otherwise, you will receive the following error: ORA-
06531: reference to uninitialized collection
Initialize a VARRAY variable to an empty collection (zero elements):
varray_name type_name := type_name();
Specify elements for the VARRAY variable while initializing it
varray_name type_name := type_name(element1, element2, ...);
Access an array element
varray_name(n);//n is the index of the element, which begins with 1 and ends with the max_elements
Delete all elements of a VARRAY
varray_name.DELETE;
Remove one element from the end of a VARRAY
varray_name.TRIM;
Remove n elements from the end of a VARRAY
varray_name.TRIM(n)
DECLARE
TYPE t_name_type IS VARRAY(2)
OF VARCHAR2(20) NOT NULL;
t_names t_name_type := t_name_type('John','Jane');
t_enames t_name_type := t_name_type();
BEGIN
-- initialize to an empty array
dbms_output.put_line("The number of elements in t_enames " || t_enames.COUNT);
DECLARE
TYPE r_customer_type IS RECORD(
customer_name customers.NAME%TYPE,
credit_limit customers.credit_limit%TYPE
);
t_customers.EXTEND;
t_customers(t_customers.LAST).customer_name := 'XYZ Inc';
t_customers(t_customers.LAST).credit_limit := 20000;
DECLARE
TYPE r_customer_type IS RECORD(
customer_name customers.name%TYPE,
credit_limit customers.credit_limit%TYPE
);
CURSOR c_customer IS
SELECT NAME, credit_limit
FROM customers
ORDER BY credit_limit DESC
FETCH FIRST 5 ROWS ONLY;
BEGIN
-- fetch data from a cursor
FOR r_customer IN c_customer LOOP
t_customers.EXTEND;
t_customers(t_customers.LAST).customer_name := r_customer.name;
t_customers(t_customers.LAST).credit_limit := r_customer.credit_limit;
END LOOP;
END;
/