Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
31 views

PL SQL

PLSQL custom notes

Uploaded by

Rohit Solanki
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
31 views

PL SQL

PLSQL custom notes

Uploaded by

Rohit Solanki
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 62

Section 1.

Getting started with PL/SQL


What is PL/SQL?

 It stands for procedural language extension to the structured query language.


 Oracle created PL/SQL that extends some limitations of SQL to provide a more comprehensive solution for
building mission-critical applications running on the oracle database.

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

It does not really define how things need to be


PL/SQL defines how things need to be done
done, rather defines what needs to be done

It executes a single statement It executes a block of statements at once.

PL/SQL, on the other hand, is used to create


SQL is mainly used to manipulate the data
applications

Since it is a SQL extension, it can contain SQL


It cannot contain PL/SQL code
code in it

PL/SQL architecture

The following picture illustrates the PL/SQL architecture:


 PL/SQL engine is in charge of compiling PL/SQL code into byte-code and executes the executable code. The
PL/SQL engine can only be installed in an Oracle Database server or an application development tool such
as Oracle Forms.
 Once you submit a PL/SQL block to the Oracle Database server, the PL/SQL engine separates the
procedural statements from the SQL statements. Procedural statements are send to Procedural statement
executor while the SQL statements are send to SQL statement executor.

PL/SQL Block Structure

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.

PL/SQL data types

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:

 Number (NUMBER, BINARY_FLOAT , BINARY_DOUBLE and PLS_INTEGER.)


 Boolean(3 data values: TRUE, FALSE, and NULL)
 Character(CHAR, VARCHAR2, LONG, RAW, LONG RAW, ROWID, and UROWID)
 Datetime( DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME
ZONE, INTERVAL YEAR TO MONTH, and INTERVAL DAY TO SECOND)

PL/SQL Variables

The variable in PL/SQL is basically a name that varies or temporary storage location that supports a particular data
type.

Variable Naming Rules


PL/SQL follows the following rules for naming variables.

 The variable cannot be more than 31 characters

 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.

Prefix Data Type

v_ VARCHAR2

n_ NUMBER

t_ TABLE

r_ ROW

d_ DATE

b_ BOOLEAN

Declaration

The syntax for a variable declaration is as follows:

variable_name datatype [NOT NULL] [:= initial_value];


Default values

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

l_product_name VARCHAR2(100) DEFAULT 'Laptop';

BEGIN

NULL;

END;

NOT NULL constraint

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

l_shipping_status VARCHAR2( 25 ) NOT NULL := 'Shipped';

BEGIN

l_shipping_status := '';

END;

Output:

ORA-06502: PL/SQL: numeric or value error

Variable assignments

To assign a value to a variable, you use the assignment operator (:=), for example:

DECLARE

l_customer_group VARCHAR2(100) := 'Silver';

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

SELECT name, credit_limit

INTO l_customer_name, l_credit_limit

FROM customers

WHERE customer_id = 38;

DBMS_OUTPUT.PUT_LINE (l_customer_name || ':' || l_credit_limit );

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

constant_name CONSTANT datatype [NOT NULL] := expression

To declare a constant, you specify the name, CONSTANT keyword, data type, and the default value. For Eg:

DECLARE

co_payment_term CONSTANT NUMBER := 45; -- days

co_payment_status CONSTANT BOOLEAN := FALSE;

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.

SELECT INTO – selecting data into variables example

DECLARE

l_customer_name customers.name%TYPE;

l_contact_first_name contacts.first_name%TYPE;

l_contact_last_name contacts.last_name%TYPE;

BEGIN

-- get customer and contact names

SELECT name, first_name, last_name

INTO l_customer_name, l_contact_first_name, l_contact_last_name

FROM customers INNER JOIN contacts USING( customer_id )

WHERE customer_id = 100;

-- show the information

dbms_output.put_line(l_customer_name || ',Contact Person: ' ||l_contact_first_name || ' ' ||contact_last_name );

END;

Output:

Verizon, Contact Person: Elisha Lloyd

SELECT INTO – selecting a complete row example

DECLARE

r_customer customers%ROWTYPE;

BEGIN

-- get the information of the customer 100


SELECT * INTO r_customer

FROM customers

WHERE customer_id = 100;

-- show the customer info

dbms_output.put_line( r_customer.name || ', website: ' || r_customer.website );

END;

Output:

Verizon, website: http://www.verizon.com

SELECT INTO common errors

 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:

ORA-06502: PL/SQL: numeric or value error


Section 2. Conditional control & Loops

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

The following illustrates the structure of the 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 THEN ELSE statement

The IF THEN ELSE statement has the following structure:

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 THEN ELSIF statement

The following illustrates the structure of the IF THEN ELSIF statement:

IF condition_1 THEN

statements_1

ELSIF condition_2 THEN


statements_2

[ELSIF condition_3 THEN

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

You can nest an IF statement within another IF statement as shown below:

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.

Simple CASE statement

CASE selector

WHEN selector_value_1 THEN

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

WHEN 'A' THEN

c_rank := 'Excellent' ;

WHEN 'B' THEN

c_rank := 'Very Good' ;

WHEN 'C' THEN

c_rank := 'Good' ;

WHEN 'D' THEN

c_rank := 'Fair' ;

WHEN 'F' THEN

c_rank := 'Poor' ;

ELSE

c_rank := 'No such grade' ;

END CASE;
DBMS_OUTPUT.PUT_LINE ( c_rank );

END;

Searched CASE statement

The searched CASE statement evaluates multiple Boolean expressions and executes the sequence of statements
associated with the first condition that evaluates to TRUE.

CASE

WHEN condition_1 THEN statements_1

WHEN condition_2 THEN statements_2

...

WHEN condition_n THEN statements_n

[ELSE

else_statements ]

END CASE;

The searched CASE statement follows the rules below:

 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

WHEN n_sales > 200000 THEN

n_commission := 0.2;

WHEN n_sales >= 100000 AND n_sales < 200000 THEN

n_commission := 0.15;

WHEN n_sales >= 50000 AND n_sales < 100000 THEN


n_commission := 0.1;

WHEN n_sales > 30000 THEN

n_commission := 0.05;

ELSE

n_commission := 0;

END CASE;

DBMS_OUTPUT.PUT_LINE( 'Commission is ' || n_commission * 100 || '%' );

END;

Simple CASE or searched CASE statement

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>>

DBMS_OUTPUT.PUT_LINE ( 'PL/SQL GOTO Demo' );

GOTO first_message;

<<the_end>>

DBMS_OUTPUT.PUT_LINE ( 'and good bye...' );


END;

The output is:

PL/SQL GOTO Demo


Hello
and good Bye...

The picture below illustrates the sequence:

GOTO statement restrictions

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;

IF n_sales > 0 THEN

<<inside_if_statement>>

n_tax := n_sales * 0.1;

END IF;

END;

Oracle issued the following error:

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;

IF n_sales > 100000 THEN

n_commission := 0.2;

GOTO zero_commission;

elsif n_sales > 50000 THEN

n_commission := 0.15;

elsif n_sales > 20000 THEN

n_commission := 0.1;

ELSE

<<zero_commission>>

n_commission := 0;

END IF;

END;

Oracle issued the following error.

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.

The NULL statement is useful to:

1. Improve code readability

IF job_title = 'Sales Representative' THEN

send_email;
ELSE

NULL;

END IF;

2. Provide a target for a GOTO statement

DECLARE

b_status BOOLEAN

BEGIN

IF b_status THEN

GOTO end_of_program;

END IF;

-- further processing here

-- ...

<<end_of_program>>

NULL;

END;

3. Create placeholders for subprograms

CREATE PROCEDURE request_for_aproval( customer_id NUMBER )

AS

BEGIN

NULL;

END;

LOOP syntax

The PL/SQL LOOP statement has the following structure:

<<label>> LOOP

statements;

END LOOP loop_label;

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 want to execute the loop body at least once.

 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;

IF l_counter > 3 THEN

EXIT;

END IF;

dbms_output.put_line( 'Inside loop: ' || l_counter ) ;

END LOOP;

-- Control resumes here after EXIT

dbms_output.put_line( 'After loop: ' || l_counter );

END;

output:

Inside loop: 1
Inside loop: 2
Inside loop: 3
After loop: 4

EXIT WHEN statement

The EXIT WHEN statement has the following syntax:

EXIT WHEN condition;

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;

EXIT WHEN l_counter > 3;

dbms_output.put_line( 'Inside loop: ' || l_counter ) ;

END LOOP;

-- Control resumes here after EXIT

dbms_output.put_line( 'After loop: ' || l_counter );

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;

EXIT outer_loop WHEN l_i > 2;

dbms_output.put_line('Outer counter ' || l_i);

-- reset inner counter

l_j := 0;

<<inner_loop>> LOOP

l_j := l_j + 1;

EXIT inner_loop WHEN l_j > 3;

dbms_output.put_line(' Inner counter ' || l_j);

END LOOP inner_loop;


END LOOP outer_loop;

END;

FOR LOOP statement

PL/SQL FOR LOOP executes a sequence of statements a specified number of times.

Simple PL/SQL FOR LOOP example

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

FOR l_counter IN 1..5

LOOP

DBMS_OUTPUT.PUT_LINE( l_counter );

END LOOP;

END;

Output:

1
2
3
4
5

Simulating STEP clause in FOR LOOP statement

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

FOR l_counter IN 1..5 LOOP

dbms_output.put_line (l_counter*l_step);

END LOOP;

END;

Result:

2
4
6
8
10

Referencing variable with the same name as the loop index

DECLARE

l_counter PLS_INTEGER := 10;

BEGIN

FOR l_counter IN 1.. 5 loop

DBMS_OUTPUT.PUT_LINE (l_counter);

END LOOP;

-- after the loop

DBMS_OUTPUT.PUT_LINE (l_counter);

END;

Here is the result:

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

l_counter PLS_INTEGER := 10;

BEGIN

FOR l_counter IN 1.. 5 loop

DBMS_OUTPUT.PUT_LINE ('Local counter:' || l_counter);

outer.l_counter := l_counter;

END LOOP;

-- after the loop

DBMS_OUTPUT.PUT_LINE ('Global counter' || l_counter);

END outer;
Referencing loop index outside the FOR LOOP

BEGIN

FOR l_index IN 1..3 loop

DBMS_OUTPUT.PUT_LINE (l_index);

END LOOP;

-- referencing index after the loop

DBMS_OUTPUT.PUT_LINE (l_index);

END;

Oracle issued the following error:

PLS-00201: identifier 'L_INDEX' must be declared


PLS-00201: identifier 'L_INDEX' must be declared

FOR LOOP with REVERSE keyword

BEGIN

FOR l_counter IN REVERSE 1..3 LOOP

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

WHILE n_counter <= 5

LOOP

DBMS_OUTPUT.PUT_LINE( 'Counter : ' || n_counter );

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

-- skip odd numbers

IF MOD( n_index, 2 ) = 1 THEN

CONTINUE;

END IF;

DBMS_OUTPUT.PUT_LINE( n_index );

END LOOP;

END;

output is:

2
4
6
8
10

CONTINUE WHEN statement


The CONTINUE WHEN statement exits the current loop iteration based on a condition and immediately continue to
the next iteration of that loop.

BEGIN

FOR n_index IN 1 .. 10

LOOP

-- skip even numbers

CONTINUE WHEN MOD( n_index, 2 ) = 0;

DBMS_OUTPUT.PUT_LINE( n_index );

END LOOP;

END;

output:

1
3
5
7
9

Section 3. Exception handlers

Introduction to PL/SQL Exceptions

 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

WHEN OTHERS THEN

-- other_exception_handler

END;

If the exception e1 occurred, the exception_handler1 runs. If the exception e2 occurred,


the exception_handler2 executes. In case any other exception raises, then
the other_exception_handler runs.

Example:

DECLARE

l_name customers.NAME%TYPE;

l_customer_id customers.customer_id%TYPE := &customer_id;

BEGIN

-- get the customer

SELECT NAME INTO l_name

FROM customers

WHERE customer_id = l_customer_id;

-- show the customer name

dbms_output.put_line('customer name is ' || l_name);

EXCEPTION

WHEN NO_DATA_FOUND THEN

dbms_output.put_line('Customer ' || l_customer_id || ' does not exist');

WHEN TOO_MANY_ROWS THEN

dbms_output.put_line('The database returns more than one customer');

END;

PL/SQL exception categories


PL/SQL has three exception categories:

 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.

Category Definer Has Error Code Has Name Raised Raised


Implicitly Explicitly

Internally Runtime Always Only if you Yes Optionally


defined system assign one

Predefined Runtime Always Always Yes Optionally


system

User-defined User Only if you Always No Always


assign one

Raise a Exception

RAISE statement to raise a user-defined exception, internally defined exception, and re-raising an exception.

A. Raising a user-defined exception

A user-defined exception is an exception defined by developers in the declaration section of a block or


subprogram.

 SYNTAX:

DECLARE

exception_name EXCEPTION;

PRAGMA EXCEPTION_INIT (exception_name, error_number); --ASSIGN ERROR_CODE & MESSAGE

 If you want to include a custom message, you can replace the line:

RAISE EXCEPTIONNAME;

by the following line:

raise_application_error(ERROR_CODE,ERROR_MESSAGE);
EXAMPLE:

DECLARE

ex_custom EXCEPTION;
PRAGMA EXCEPTION_INIT( ex_custom, -20001 );

BEGIN

raise_application_error( -20001, 'This is a custom error' );


exception
when ex_custom
then
dbms_output.put_line( sqlerrm );

END;

B. Raising an internally defined exception

 You can explicitly raise an internally defined exception with the RAISE statement if the exception has a
name:

RAISE exception_name;

 Example

DECLARE

l_customer_id customers.customer_id%TYPE := &customer_id;

BEGIN

-- get the meax credit limit

IF l_customer_id < 0 THEN

RAISE invalid_number;

END IF;

END;

If you execute the block and enter the customer id -10, you will get the following error:

ORA-01722: invalid number

C. Re-raising the current exception

 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;

PRAGMA exception_init( e_credit_too_high, -20001 );

l_max_credit customers.credit_limit%TYPE;

l_customer_id customers.customer_id%TYPE := &customer_id;

l_credit customers.credit_limit%TYPE := &credit_limit;

BEGIN

BEGIN

-- get the max credit limit

SELECT MAX(credit_limit)

INTO l_max_credit

FROM customers;

-- check if input credit is greater than the max credit

IF l_credit > l_max_credit THEN

RAISE e_credit_too_high;

END IF;

EXCEPTION

WHEN e_credit_too_high THEN

dbms_output.put_line('The credit is too high' || l_credit);

RAISE; -- re-raise the exception

END;

EXCEPTION

WHEN e_credit_too_high THEN

-- get average credit limit

SELECT avg(credit_limit)

into l_credit

from customers;
-- adjust the credit limit to the average

dbms_output.put_line('Adjusted credit to ' || l_credit);

-- update credit limit

UPDATE customers

SET credit_limit = l_credit

WHERE customer_id = l_customer_id;

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;

 PL/SQL offers the following pragmas:

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

exception in your program.

c. RESTRICT_REFERENCES

 Used to control the side effects of PL/SQL Subprograms. Every PL/SQL Subprograms must follow some

rules in terms of transaction control and security.

SYNTAX:

PRAGMA RESTRICT_REFERENCES ([SUBPROGRAM_NAME/DEFAULT] , [RNDS, WNDS, RNPS, WNPS,

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

query database tables).

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

reference the values of packaged variables)

WNPS (Write No Package State): Asserts that the subprogram writes no package state (does not

change the values of packaged variables).

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

for long-running sessions.

 This Pragma is appropriate for packages that declare large temporary work areas that are used once

in the same session.

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}]);

 error_number is a negative integer with the range from -20999 to -20000.


 message is a character string that represents the error message. Its length is up to 2048
bytes.
 If the third parameter is FALSE, the error replaces all previous errors. If it is TRUE, the error is
added to the stack of previous errors.

 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;
/

CASE1: Passing 1 as the input number.

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).

CASE3: Passing 3 as the input number.

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

Section 4. Records, Cursors

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;

Referencing/assigning record values

 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:

IF r_contact1 = r_contact2 THEN


...
END IF;

In this case, you need to compare individual fields of the record instead:

IF r.contact1.first_name = r.contact2.first_name AND


r.contact1.last_name = r.contact2.last_name AND
r.contact1.phone = r.contact2.phone THEN
...
END IF;

 You can use SELECT INTO a whole record (or individual fields):

SELECT first_name, last_name, phone


INTO r_contact
FROM contacts
WHERE contact_id = 100;

Records and INSERT / UPDATE statements

 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';

-- insert a new person


INSERT INTO persons VALUES r_person;
END;

 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;

-- change the person's last name


r_person.last_name := 'Smith';

-- update the person


UPDATE persons
SET ROW = r_person
WHERE person_id = r_person.person_id;
END;

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

r_one_time_customer.customer_name := 'John Doe';


-- assign address
r_one_time_customer.ship_to.street_name := '4000 North 1st street';
r_one_time_customer.ship_to.city := 'San Jose';
r_one_time_customer.ship_to.state := 'CA';
r_one_time_customer.ship_to.postal_code := '95134';
r_one_time_customer.ship_to.country := 'USA';
-- bill-to address is same as ship-to address
r_one_time_customer.bill_to := one_time_customer.ship_to;
END;

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.

II. Explicit cursors

 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:

CURSOR cursor_name IS query;

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.

c. Fetch from a cursor

The FETCH statement places the contents of the current row into variables. The syntax
of FETCH statement is as follows:

FETCH cursor_name INTO variable_list;

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;

Closing a cursor instructs Oracle to release allocated memory at an appropriate time.

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

where cursor_name is the name of the explicit cursor

i. %ISOPEN

This attribute is TRUE if the cursor is open or FALSE if it is not.

ii. %FOUND

This attribute has four values:

 NULL before the first fetch


 TRUE if a record was fetched successfully
 FALSE if no row returned
 INVALID_CURSOR if the cursor is not opened
iii. %NOTFOUND

This attribute has four values:

 NULL before the first fetch


 FALSE if a record was fetched successfully
 TRUE if no row returned
 INVALID_CURSOR if the cursor is not opened
iv. %ROWCOUNT

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:

SET SERVEROUTPUT ON SIZE 1000000;

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

-- fetch information from cursor into record


FETCH cur_chief INTO r_chief;
EXIT WHEN cur_chief%NOTFOUND;

-- print department - chief


DBMS_OUTPUT.PUT_LINE(r_chief.department_name || ' - ' || r_chief.first_name || ',' ||
r_chief.last_name);
END LOOP;

-- close cursor cur_chief


CLOSE cur_chief;

END;
/

Cursor FOR LOOP statement

 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:

FOR record IN cursor_name


LOOP
process_record_statements;
END LOOP;

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.

The record variable is local to the cursor FOR LOOP statement

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:

FOR record IN (select_statement)


LOOP
process_record_statements;
END LOOP;

Cursor FOR LOOP example:

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;

FOR LOOP with a SELECT statement example:

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.

 The following shows the syntax of a declaring a cursor with parameters:

CURSOR cursor_name (parameter_list)


IS
cursor_query;

 To open a cursor with parameters, you use the following syntax:

OPEN cursor_name (value_list);

 Example:

DECLARE
r_product products%rowtype;

CURSOR c_product (low_price NUMBER, high_price NUMBER)


IS
SELECT *
FROM products
WHERE list_price BETWEEN low_price AND high_price;

BEGIN

-- show mass products


dbms_output.put_line('Mass products: ');

OPEN c_product(50,100);
LOOP
FETCH c_product INTO r_product;
EXIT WHEN c_product%notfound;

dbms_output.put_line(r_product.product_name || ': ' ||r_product.list_price);


END LOOP;

CLOSE c_product;
END;
/

Parameterized cursor with default values

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.

Strong Typed variable

DECLARE
TYPE customer_t IS REF CURSOR RETURN customers%ROWTYPE;
c_customer customer_t;

Weak Typed variable

DECLARE
TYPE customer_t IS REF CURSOR;
c_customer customer_t;

 cursor variable examples:


CREATE OR REPLACE FUNCTION get_direct_reports(
in_manager_id IN employees.manager_id%TYPE)
RETURN SYS_REFCURSOR
AS
c_direct_reports SYS_REFCURSOR;
BEGIN

OPEN c_direct_reports FOR

SELECT employee_id, first_name, last_name, email


FROM employees
WHERE manager_id = in_manager_id
ORDER BY first_name, last_name;

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

-- get the ref cursor from function


c_direct_reports := get_direct_reports(46);

-- process each employee


LOOP
FETCH
c_direct_reports
INTO l_employee_id, l_first_name, l_last_name, l_email;
EXIT
WHEN c_direct_reports%notfound;
dbms_output.put_line(l_first_name || ' ' || l_last_name || ' - ' || l_email );

END LOOP;

-- close the cursor


CLOSE c_direct_reports;
END;
/
Section 5. Stored procedures, Functions & Packages
Procedure
A PL/SQL procedure is a reusable unit that encapsulates specific business logic of the application. Technically
speaking, a PL/SQL procedure is a named block stored as a schema object in the Oracle Database.
SYNTAX:

CREATE [OR REPLACE ] PROCEDURE procedure_name (parameter_list)


IS
[declaration statements]
BEGIN
[execution statements]
EXCEPTION
[exception handler]
END [procedure_name ];

Example:

CREATE OR REPLACE PROCEDURE print_contact( in_customer_id NUMBER )


IS
r_contact contacts%ROWTYPE;
BEGIN
-- get contact based on customer id
SELECT *
INTO r_contact
FROM contacts
WHERE customer_id = p_customer_id;

-- print out contact's information


dbms_output.put_line( r_contact.first_name || ' ' ||
r_contact.last_name || '<' || r_contact.email ||'>' );

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.

 The executable part must contain at least one executable statement.

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

The following shows the syntax for executing a procedure:

EXECUTE procedure_name( arguments);

Or

EXEC procedure_name( arguments);


s);

Removing a procedure
To delete a procedure, you use the DROP PROCEDURE followed by the procedure’s name as shown in the following
syntax:

DROP PROCEDURE procedure_name;

Implicit Statement Results


 Oracle Database 12c Release 1 added a new feature called implicit statement result that allows you to
return one or more result sets from a stored procedure by using the dbms_sql package.
 Example:

CREATE OR REPLACE PROCEDURE get_customers( page_no NUMBER, page_size NUMBER)


AS
c_customers SYS_REFCURSOR;
c_total_row SYS_REFCURSOR;
BEGIN

-- return the total of customers


OPEN c_total_row FOR
SELECT COUNT(*)
FROM customers;

dbms_sql.return_result(c_total_row);

-- return the customers


OPEN c_customers FOR
SELECT customer_id, name
FROM customers
ORDER BY name
OFFSET page_size * (page_no - 1) ROWS
FETCH NEXT page_size ROWS ONLY;

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_sql.parse(C => l_sql_cursor,


STATEMENT => 'BEGIN get_customers(1,10); END;',
language_flag => dbms_sql.NATIVE);
l_return := dbms_sql.EXECUTE(l_sql_cursor);

-- Loop over the result sets.


LOOP
-- Get the next resultset.
BEGIN
dbms_sql.get_next_result(l_sql_cursor, c_cursor);
EXCEPTION
WHEN no_data_found THEN
EXIT;
END;

-- Get the number of columns in each result set.


l_return := dbms_sql.to_cursor_number(c_cursor);
dbms_sql.describe_columns (l_return, l_column_count, l_desc_tab);
c_cursor := dbms_sql.to_refcursor(l_return);

-- Handle the result set based on the number of columns.


CASE l_column_count
WHEN 1 THEN
dbms_output.put_line('The total number of customers:');
FETCH c_cursor
INTO l_total_rows;

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;

EXIT WHEN c_cursor%notfound;


dbms_output.put_line(l_customer_id || ' ' || l_name);
END LOOP;
CLOSE c_cursor;
ELSE
dbms_output.put_line('An error occurred!');
END CASE;
END LOOP;
END;
/

Function

 Similar to a procedure, a PL/SQL function is a reusable program unit stored as a schema object in the
Oracle Database

CREATE [OR REPLACE] FUNCTION function_name (parameter_list)


RETURN return_type
IS
[declarative section]
BEGIN
[executable section]
[EXCEPTION]
[exception-handling section]
END;
 A function consists of a header and body.
 The function header has the function name and a RETURN clause that specifies the datatype of the
returned value. Each parameter of the function can be either in the IN, OUT, or INOUT mode.
 The function body is the same as the procedure body which has three sections: declarative section,
executable section, and exception-handling section.
 The declarative section is between the IS and BEGIN keywords. It is where you
declare variables, constants, cursors, and user-defined types.
 The executable section is between the BEGIN and END keywords. It is where you place the executable
statements. Unlike a procedure, you must have at least one RETURN statement in the executable
statement.
 The exception-handling section is where you put the exception handler code.
 Example:

CREATE OR REPLACE FUNCTION get_total_sales( in_year PLS_INTEGER)
RETURN NUMBER
IS
l_total_sales NUMBER := 0;
BEGIN
-- get total sales
SELECT SUM(unit_price * quantity)
INTO l_total_sales
FROM order_items
INNER JOIN orders USING (order_id)
WHERE status = 'Shipped'
GROUP BY EXTRACT(YEAR FROM order_date)
HAVING EXTRACT(YEAR FROM order_date) = in_year;

-- return the total sales


RETURN l_total_sales;
END;

Calling a PL/SQL function

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

IF get_total_sales (2017) > 10000000 THEN


DBMS_OUTPUT.PUT_LINE('Sales 2017 is above target');
END IF;

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:

DROP FUNCTION function_name;

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 .

 A package specification contains the following items:

 Procedures
 Functions
 Cursors
 Types, variables, and constants
 Records
 Collections

 SYNTAX:

CREATE [OR REPLACE] PACKAGE [schema_name.]<package_name> IS | AS


declarations;
END [<package_name>];

 EXAMPLE:

CREATE OR REPLACE PACKAGE order_mgmt


AS
gc_shipped_status CONSTANT VARCHAR(10) := 'Shipped';
gc_pending_status CONSTANT VARCHAR(10) := 'Pending';
gc_canceled_status CONSTANT VARCHAR(10) := 'Canceled';

-- cursor that returns the order detail


CURSOR g_cur_order(p_order_id NUMBER)
IS
SELECT
customer_id,
status,
salesman_id,
order_date,
item_id,
product_name,
quantity,
unit_price
FROM
order_items
INNER JOIN orders USING (order_id)
INNER JOIN products USING (product_id)
WHERE
order_id = p_order_id;

-- get net value of a order


FUNCTION get_net_value(
p_order_id NUMBER)
RETURN NUMBER;

-- Get net value by customer


FUNCTION get_net_value_by_customer(
p_customer_id NUMBER,
p_year NUMBER)
RETURN NUMBER;

END order_mgmt;

 To refer to an item using the following syntax:

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:

CREATE [OR REPLACE] PACKAGE BODY [schema_name.]<package_name> IS | AS


declarations
implementations;
[BEGIN
EXCEPTION]
END [<package_name>];
 EXAMPLE:

CREATE OR REPLACE PACKAGE BODY order_mgmt


AS

-- get net value of a order


FUNCTION get_net_value(p_order_id NUMBER)
RETURN NUMBER
IS
ln_net_value NUMBER
BEGIN
SELECT SUM(unit_price * quantity)
INTO ln_net_value
FROM
order_items
WHERE
order_id = p_order_id;

RETURN p_order_id;

EXCEPTION
WHEN no_data_found THEN
DBMS_OUTPUT.PUT_LINE( SQLERRM );

END get_net_value;

-- Get net value by customer


FUNCTION get_net_value_by_customer(p_customer_id NUMBER, p_year NUMBER)
RETURN NUMBER
IS
ln_net_value NUMBER
BEGIN
SELECT SUM(quantity * unit_price)
INTO ln_net_value
FROM order_items
INNER JOIN orders USING (order_id)
WHERE extract(YEAR FROM order_date) = p_year
AND customer_id = p_customer_id
AND status = gc_shipped_status;

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:

DROP PACKAGE [BODY] schema_name.package_name;

 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.

S.No PROCEDURE FUNCTION

Used mainly to execute Used mainly to perform some


1 certain business logic with computational process and returning
DML and DRL statements the result of that process.

Procedure can return zero Function can return only single value
2
or more values as output. as output

Function can call with select statement


, if function doesnot contain any DML
Procedure cannot call with
statements and DDL statements..
select statement, but can
3 function with DML and DDL statements
call from a block or from a
can call with select statement with
procedure
some special cases (using Pragma
autonomous transaction)
OUT keyword is used to
RETURN keyword is used to return a
4 return a value from
value from a function.
procedure

It is not mandatory to return


5 It is mandatory to return the value
the value

RETURN will simply exit the RETURN will exit the control from
6
control from subprogram subprogram and also returns the value

Return datatype will not be


Return datatype is mandatory at the
7 specified at the time of
time of creation
creation

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:

CREATE [OR REPLACE] TRIGGER trigger_name


{BEFORE | AFTER } triggering_event ON table_name
[FOR EACH ROW]
[FOLLOWS | PRECEDES another_trigger]
[ENABLE / DISABLE ]
[WHEN condition]
DECLARE
declaration statements
BEGIN
executable statements
EXCEPTION
exception_handling statements
END;

o BEFORE | AFTER: specifies when the trigger fires, either before or after a triggering event

o ON table_name: name of the table associated with the trigger.

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

CREATE OR REPLACE TRIGGER customers_audit_trg


AFTER UPDATE OR DELETE ON customers
FOR EACH ROW
DECLARE
l_transaction VARCHAR2(10);
BEGIN
-- determine the transaction type
l_transaction := CASE
WHEN UPDATING THEN 'UPDATE'
WHEN DELETING THEN 'DELETE'
END;

-- insert a row into the audit table


INSERT INTO audits (table_name, transaction_name, by_user, transaction_date)
VALUES('CUSTOMERS', l_transaction, USER, SYSDATE);

END;
/

Statement-level Triggers

 A statement-level trigger executes once for each transaction.


 Due to its features, a statement-level trigger is not often used for data-related activities like auditing the
changes of the data in the associated table.
 It’s typically used to enforce extra security measures on the kind of transaction that may be performed on
a table.
 By default, the statement CREATE TRIGGER creates a statement-level trigger when you omit the FOR EACH
ROW clause.
 Example :

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);

IF l_day_of_month BETWEEN 28 AND 31 THEN


raise_application_error(-20100,'Cannot update customer credit from 28th to 31st');
END IF;
END;

Output:

ORA-20100: Cannot update customer credit from 28th to 31st


ORA-06512: at "OT.CUSTOMERS_CREDIT_TRG", line 8
ORA-04088: error during execution of trigger 'OT.CUSTOMERS_CREDIT_TRG'

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 :

CREATE OR REPLACE TRIGGER trigger_name


BEFORE | AFTER
INSERT OR DELETE OR UPDATE OF column1, column2, …
ON table_name
FOR EACH ROW
REFERENCING OLD AS old_name
NEW AS new_name
WHEN (condition)
DECLARE

BEGIN

EXCEPTION

END;
:OLD & :NEW column values

 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:

Triggering Event :NEW :OLD

INSERT Yes No

UPDATE Yes Yes

DELETE No yes

 Modifying :OLD & :NEW values

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)

CREATE OR REPLACE TRIGGER


BEFORE UPDATE OF credit_limit
ON customers
FOR EACH ROW
WHEN NEW.credit_limit > 10000;

Row Level Trigger Example:

CREATE OR REPLACE TRIGGER customers_update_credit_trg


BEFORE UPDATE OF credit_limit
ON customers
FOR EACH ROW
WHEN (NEW.credit_limit > 0)
BEGIN
-- check the credit limit
IF :NEW.credit_limit >= 2 * :OLD.credit_limit THEN
raise_application_error(-20101,'The new credit ' || :NEW.credit_limit ||
' cannot increase to more than double, the current credit ' || :OLD.credit_limit);
END IF;
END;

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:

CREATE [OR REPLACE] TRIGGER trigger_name


INSTEAD OF {INSERT | UPDATE | DELETE}
ON view_name
FOR EACH ROW
BEGIN
EXCEPTION
...
END;
 EXAMPLE:
CREATE VIEW vw_customers AS
SELECT
name,
address,
website,
credit_limit,
first_name,
last_name,
email,
phone
FROM
customers
INNER JOIN contacts USING (customer_id);

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');

Oracle issued the following error:


SQL Error: ORA-01779: cannot modify a column which maps to a non key-preserved
table
01779. 00000 - "cannot modify a column which maps to a non key-preserved table"
*Cause: An attempt was made to insert or update columns of a join view which
map to a non-key-preserved table.
*Action: Modify the underlying base tables directly.

Create an INSTEAD OF trigger on the view vw_customers:

CREATE OR REPLACE TRIGGER new_customer_trg


INSTEAD OF INSERT ON vw_customers
FOR EACH ROW
DECLARE
l_customer_id NUMBER;
BEGIN

-- insert a new customer first


INSERT INTO customers(name, address, website, credit_limit)
VALUES(:NEW.NAME, :NEW.address, :NEW.website, :NEW.credit_limit)
RETURNING customer_id INTO l_customer_id;

-- insert the contact


INSERT INTO contacts(first_name, last_name, email, phone, customer_id)
VALUES(:NEW.first_name, :NEW.last_name, :NEW.email, :NEW.phone, l_customer_id);

END;

*Using the same insert query as above will result in insertion of record.

Disable Triggers
ALTER TRIGGER trigger_name DISABLE; // Disable single trigger

ALTER TABLE table_name DISABLE ALL TRIGGERS; // Disable all triggers

Create a disabled 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:

CREATE OR REPLACE TRIGGER customers_bd_trg


BEFORE DELETE
ON customers
FOR EACH ROW
DISABLE
DECLARE
l_order_count PLS_INTEGER;
BEGIN
-- check if the customer has a transaction
SELECT COUNT(*) INTO l_order_count
FROM orders
WHERE customer_id = :OLD.customer_id;

-- raise an exception if the customer has at least one order


IF l_order_count > 0 THEN
raise_application_error(-20010,'Cannot delete customer ' || :OLD.NAME ||
' because it already has transactions');
END IF;
END;

Enable trigger
 To enable a previously disabled trigger, you use the ALTER TRIGGER ENABLE statement:

ALTER TRIGGER trigger_name ENABLE; // Enable single trigger

ALTER TABLE table_name ENABLE ALL TRIGGERS; //Enable all triggers

DROP TRIGGER
 The DROP TRIGGER statement allows you to remove a trigger from the database.

DROP TRIGGER [schema_name.]trigger_name;

 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:

CREATE OR REPLACE PROCEDURE drop_trigger_if_exists( in_trigger_name VARCHAR2)


AS
l_exist PLS_INTEGER;
BEGIN
-- get the trigger count
SELECT COUNT(*) INTO l_exist
FROM user_triggers
WHERE trigger_name = UPPER(in_trigger_name);

-- if the trigger exist, drop it


IF l_exist > 0 THEN
EXECUTE IMMEDIATE 'DROP TRIGGER ' || in_trigger_name;
END IF;

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:

TYPE associative_array_type //associative_array_type is the name of the associative array type.


IS TABLE OF datatype [NOT NULL] //datatype is the data type of the elements in the array.
INDEX BY index_type; //index_type is data type of index used to organize the elements in the array.

associative_array associative_array_type //Declaring an associative array variable

 array_name(index) // Accessing associative array elements


 array_name(index) := value; //Assigning value to associative array elements

Associative array methods:

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.

Associative Array Example

DECLARE
-- declare an associative array type
TYPE t_capital_type
IS TABLE OF VARCHAR2(100)
INDEX BY VARCHAR2(50);

t_capital t_capital_type; -- declare a variable of the t_capital_type


l_country VARCHAR2(50); -- local variable
BEGIN

t_capital('USA') := 'Washington, D.C.';


t_capital('United Kingdom') := 'London';
t_capital('Japan') := 'Tokyo';

l_country := t_capital.FIRST;

WHILE l_country IS NOT NULL LOOP


dbms_output.put_line('The capital of ' || l_country || ' is ' || t_capital(l_country));
l_country := t_capital.NEXT(l_country);
END LOOP;
END;
/

output:

The capital of Japan is Tokyo


The capital of USA is Washington, D.C.
The capital of United Kingdom is London
Nested Tables
 Nested tables are single-dimensional, unbounded collections of homogeneous elements.
 A nested table is initially dense. However, it can become sparse through the removal of elements.
 SYNTAX:
TYPE nested_table_type
IS TABLE OF element_datatype [NOT NULL];

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];

 DROP TYPE SYNTAX:


DROP TYPE type_name [FORCE];

Initializing a nested table

 When you declare a nested table variable, it is initialized to NULL.


 To initialize a nested table, you can use a constructor function. The constructor function has the same
name as the type:

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();

Add/access/iterate nested table elements

 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_table_variable(index);// Accessing elements by their indexes

 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:

FOR l_index IN nested_table_variable.FIRST..nested_table_variable.LAST


LOOP
-- access element
END 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;

-- declare a nested table type


TYPE t_customer_name_type
IS TABLE OF customers.name%TYPE;

-- declare and initialize a nested table variable


t_customer_names t_customer_name_type := t_customer_name_type();

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;

-- display customer names


FOR l_index IN t_customer_names.FIRST..t_customer_names.LAST
LOOP
dbms_output.put_line(t_customer_names(l_index));
END LOOP;
END;

output:

3M
ADP
AECOM
AES
AIG
AT&T
AbbVie
Abbott Laboratories

VARRAY (variable-sized array)


 A VARRAY is single-dimensional collections of elements with the same data type. Unlike an associative
array and nested table, a VARRAY always has a fixed number of elements (bounded) and never has gaps
between the elements (not sparse).
 SYNTAX:

TYPE type_name IS VARRAY(max_elements)


OF element_type [NOT NULL];

type_name:VARRAY type

max_elements: max no of elements allowed

element_type : type of elements of the VARRAY


 Create a VARRAY which can be accessed globally

CREATE [OR REPLACE ] TYPE type_name AS | IS


VARRAY(max_elements) OF element_type [NOT NULL];

 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)

1) Simple PL/SQL VARRAY example

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);

-- initialize to an array of a elements


dbms_output.put_line("The number of elements in t_names " || t_names.COUNT);
END;
/

2) PL/SQL VARRAY of records example

DECLARE
TYPE r_customer_type IS RECORD(
customer_name customers.NAME%TYPE,
credit_limit customers.credit_limit%TYPE
);

TYPE t_customer_type IS VARRAY(2)


OF r_customer_type;

t_customers t_customer_type := t_customer_type();


BEGIN
t_customers.EXTEND;
t_customers(t_customers.LAST).customer_name := 'ABC Corp';
t_customers(t_customers.LAST).credit_limit := 10000;

t_customers.EXTEND;
t_customers(t_customers.LAST).customer_name := 'XYZ Inc';
t_customers(t_customers.LAST).credit_limit := 20000;

dbms_output.put_line('The number of customers is ' || t_customers.COUNT);


END;
/

3) Adding elements to VARRAY from a cursor example

DECLARE
TYPE r_customer_type IS RECORD(
customer_name customers.name%TYPE,
credit_limit customers.credit_limit%TYPE
);

TYPE t_customer_type IS VARRAY(5)


OF r_customer_type;

t_customers t_customer_type := t_customer_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;

-- show all customers


FOR l_index IN t_customers .FIRST..t_customers.LAST
LOOP
dbms_output.put_line(
'The customer ' ||
t_customers(l_index).customer_name ||
' has a credit of ' ||
t_customers(l_index).credit_limit
);
END LOOP;

END;
/

You might also like