This post tells basic things about PL/SQL in Oracle.
What’s PL/SQL
PL/SQL stands for Procedural Language extensions to the Structured Query Language.
PL/SQL Block
PL/SQL is a block-structured language whose code is organized into blocks. A PL/SQL block consists of three sections: declaration, executable, and exception-handling sections.
In a block, the executable section is mandatory while the declaration and exception-handling sections are optional.
A PL/SQL block has a name. Functions or Procedures is an example of a named block. A named block is stored into the Oracle Database server and can be reused later.
A block without a name is an anonymous block. An anonymous block is not saved in the Oracle Database server, so it is just for one-time use. However, PL/SQL anonymous blocks can be useful for testing purposes.
The following picture illustrates the structure of a PL/SQL block:
Declaration section
A PL/SQL block has a declaration section where you declare variables, allocate memory for cursors, and define data types.
Executable section
A PL/SQL block has an executable section. An executable section starts with the keyword BEGIN
and ends with the keyword END
. The executable section must have a least one executable statement, even if it is the NULL
statement which does nothing.
Note a block itself is an executable statement, therefore you can nest a block within other blocks.
Exception-handling section
A PL/SQL block has an exception-handling section that starts with the keyword EXCEPTION
. The exception-handling section is where you catch and handle exceptions raised by the code in the execution section.
Example of an anonymous block
1 | BEGIN |
The executable section calls the DMBS_OUTPUT.PUT_LINE
procedure to display the “Hello World” message on the screen.
Here is another example.
1 | DECLARE |
PL/SQL data types
Introduction to 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
- Boolean
- Character
- Datetime
A scalar data type may have subtypes. A subtype is a data type that is a subset of another data type, which is its base type. A subtype further defines a base type by restricting the value or size of the base data type.
Note that PL/SQL scalar data types include SQL data types and its own data type such as Boolean.
Numeric data types
The numeric data types represent real numbers, integers, and floating-point numbers. They are stored as NUMBER
, IEEE floating-point storage types (BINARY_FLOAT
and BINARY_DOUBLE
), and PLS_INTEGER
.
The data types NUMBER
, BINARY_FLOAT
, and BINARY_DOUBLE
are SQL data types.
The PLS_INTEGER
datatype is specific to PL/SQL. It represents signed 32 bits integers that range from -2,147,483,648
to 2,147,483,647
.
Because PLS_INTEGER
datatype uses hardware arithmetic, they are faster than NUMBER
operations, which uses software arithmetic.
In addition, PLS_INTEGER
values require less storage than NUMBER
. Hence, you should always use PLS_INTEGER
values for all calculation in its range to increase the efficiency of programs.
The PLS_INTEGER
datatype has the following predefined subtypes:
Boolean data type
The BOOLEAN
datatype has three data values: TRUE
, FALSE
, and NULL
. Boolean values are typically used in control flow structure such as IF-THEN
, CASE
, and loop statements like LOOP
, FOR LOOP
, and WHILE LOOP
.
Note that SQL does not have the BOOLEAN
data type.
Character data types
The character data types represent alphanumeric text. PL/SQL uses the SQL character data types such as CHAR
, VARCHAR2
, LONG
, RAW
, LONG RAW
, ROWID
, and UROWID
.
Datetime data types
The datetime data types represent dates, timestamp with or without time zone and intervals. PL/SQL datetime data types are DATE
, TIMESTAMP
, TIMESTAMP WITH TIME ZONE
, TIMESTAMP WITH LOCAL TIME ZONE
, INTERVAL YEAR TO MONTH
, and INTERVAL DAY TO SECOND
.
PL/SQL Variables
Introduction to PL/SQL variables
In PL/SQL, a variable is named storage location that stores a value of a particular data type. The value of the variable changes through the program. Before using a variable, you must declare it in the declaration section of a block.
Declaring variables
The syntax for a variable declaration is as follows:
1 | variable_name datatype [NOT NULL] [:= initial_value]; |
In this syntax:
First, specify the name of the variable. The name of the variable should be as descriptive as possible.
Second, choose an appropriate data type for the variable, depending on the kind of value which you want to store, for example, number, character, Boolean, and datetime.
By convention, local variable names should start with
l_
and global variable names should have a prefix ofg_
.
Here is an example.
1 | DECLARE |
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.
Here are 2 examples to declare a variable named l_product_name
with an initial value ‘Laptop’:
1 | DECLARE |
1 | DECLARE |
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.
Variable assignments
To assign a value to a variable, you use the assignment operator (:=
), for example:
1 | DECLARE |
You can assign a value of a variable to another as shown in the following example:
1 | DECLARE |
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. Consider the following example:
1 | DECLARE |
In this example:
- First, declare two variables
l_customer_name
andl_credit_limit
whose data type anchors to thename
andcredit_limit
columns respectively, in the declaration section of the block. - Second, query customer name and credit limit of the customer id
38
and assign these column values to thel_customer_name
andl_credit_limit
variables in the execution block. - Third, display the customer name and credit limit.
This example illustrates how to declare variables that anchor to another variable:
1 | DECLARE |
PL/SQL Comments
Introduction to PL/SQL comments
PL/SQL comments allow you to describe the purpose of a line or a block of PL/SQL code.
PL/SQL has two comment styles: single-line and multi-line comments.
Single-line comments
A single-line comment starts with a double hyphen (--
) that can appear anywhere on a line and extends to the end of the line.
Here is an example.
1 | -- valued added tax 10% |
Multi-line comments
A multi-line comment starts with a slash-asterisk ( /* ) and ends with an asterisk-slash ( */ ), and can span multiple lines:
1 | /* |
PL/SQL Constants
Introduction to PL/SQL 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. The following illustrates the syntax of declaring a constant:
1 | constant_name CONSTANT datatype [NOT NULL] := expression |
In this syntax:
constant_name
is the name of the constant that you are declaring.datatype
specifies the type of value that the constant will hold.NOT NULL
optionally imposes aNOT NULL
constraint on the constant. This prevents the constant from storing NULL or an empty string.expression
is used as the initial value for the constant. The type of the return value of the expression must be compatible with the data type of the constant.
PL/SQL constant examples
The following example declares two constants co_payment_term
and co_payment_status
:
1 | DECLARE |
The following illustrates how to declare a constant whose value is derived from an expression:
1 | DECLARE |
PL/SQL IF Statement
Introduction to PL/SQL 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
PL/SQL IF THEN statement
The following illustrates the structure of the IF THEN
statement:
1 | IF condition THEN |
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.
In the following example, the statements between THEN
and END IF
execute because the sales revenue is greater than 100,000.
1 | DECLARE n_sales NUMBER := 2000000; |
PL/SQL IF THEN ELSE statement
The IF THEN ELSE
statement has the following structure:
1 | IF condition THEN |
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.
The following example sets the sales commission to 10% if the sales revenue is greater than 200,000. Otherwise, the sales commission is set to 5%.
1 | DECLARE |
PL/SQL IF THEN ELSIF statement
The following illustrates the structure of the IF THEN ELSIF
statement:
1 | IF condition_1 THEN |
In this structure, the condition between IF
and THEN
, which is the first condition, is always evaluated. Each other condition between ELSIF
and THEN
is evaluated only if the preceding condition is FALSE
. For example, the condition_2
is evaluated only if the condition_1
is false, the condition_3
is evaluated only if the condition_2
is false, and so on.
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.
The following example uses the IF THEN ELSIF
statement to set the sales commission based on the sales revenue.
1 | DECLARE |
Nested IF statement
You can nest an IF
statement within another IF
statement as shown below:
1 | IF condition_1 THEN |
However, if you have too many levels of nesting, the code will be hard to read and maintain, so you should avoid nesting the IF
statements.
PL/SQL CASE Statement
Introduction to PL/SQL CASE statement
The CASE
statement chooses one sequence of statements to execute out of many possible sequences.
The CASE
statement has two types: simple CASE statement and searched CASE
statement. Both types of the CASE
statements support an optional ELSE
clause.
Simple CASE statement
A simple CASE
statement evaluates a single expression and compares the result with some values.
The simple CASE
statement has the following structure:
1 | CASE selector |
In this syntax:
The
selector
is an expression which is evaluated once. The result of the selector is used to select one of the several alternatives e.g.,selector_value_1
andselector_value_2
.The selector values i.e.,
selector_value_1
,selector_value_2
, etc., are evaluated sequentially. If the result of a selector value equals the result of theselector
, then the associated sequence of statements executes and theCASE
statement ends. In addition, the subsequent selector values are not evaluated.If no values in
WHEN
clauses match the result of the selector in theCASE
clause, the sequence of statements in theELSE
clause executes.
Because the ELSE
clause is optional, you can skip it. However, if you do so, PL/SQL will implicitly use the following:
1 | ELSE |
The following example compares single value (c_grade
) with many possible values ‘A’, ‘B’, ‘C’, ‘D’, and ‘F’:
1 | DECLARE |
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
.
The searched CASE
statement has the following structure:
1 | CASE |
The searched CASE
statement follows the rules below:
- The conditions in the
WHEN
clauses in are evaluated in order, from top to bottom. - The sequence of statements associated with the
WHEN
clause whose condition evaluates toTRUE
is executed. If more than one condition evaluates toTRUE
, only the first one executes. - If no condition evaluates to
TRUE
, the else_statements in theELSE
clause executes. If you skip theELSE
clause and no expressions areTRUE
, aCASE_NOT_FOUND
exception is raised.
The following example illustrates how to use the searched CASE
statement to calculate sales commission based on sales revenue.
1 | DECLARE |
PL/SQL stops evaluating the subsequent condition once it finds the first condition that evaluates to TRUE
. Therefore, in this example, PL/SQL will never evaluate the last two conditions in the CASE
statement. The ELSE
statement clause will also never execute.
PL/SQL GOTO Statement
Introduction to PL/SQL GOTO statement
The GOTO
statement allows you to transfer control to a labeled block or statement. The following illustrates the syntax of the GOTO
statement:
1 | 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:
1 | <<label_name>>; |
When PL/SQL encounters a GOTO
statement, it transfers control to the first executable statement after the label.
PL/SQL GOTO statement example
The following shows an example of using the GOTO
statements.
1 | BEGIN |
The following explains the sequence of the block in detail:
- First, the
GOTO second_message
statement is encountered, therefore, the control is passed to the statement after thesecond_message
label. - Second, the
GOTO first_message
is encountered, so the control is transferred to the statement after thefirst_message
label. - Third, the
GOTO the_end
is reached, hence the control is passed to the statement after thethe_end
label.
PL/SQL GOTO statement restrictions
The GOTO statement is subject to the following restrictions.
First, you cannot use a GOTO
statement to transfer control into an IF
, CASE
or LOOP
statement, the same for sub-block.
Second, 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.
Third, you cannot use a GOTO
statement to transfer control out of a subprogram or into an exception handler.
Fourth, you cannot use a GOTO
statement to transfer control from an exception handler back into the current block.
PL/SQL NULL Statement
Introduction to PL/SQL NULL statement
The PL/SQL NULL
statement has the following format:
1 | NULL; |
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.
Usages of PL/SQL NULL statement
The NULL
statement is useful to:
- Improve code readability.
- Provide a target for a
GOTO
statement. - Create placeholders for subprograms.
PL/SQL LOOP Statement
PL/SQL LOOP statement syntax
The PL/SQL LOOP
statement has the following structure:
1 | [<<label>>] LOOP |
This structure is the most basic of all the loop constructs including FOR LOOP
and WHILE LOOP
. This basic LOOP
statement consists of a LOOP
keyword, a body of executable code, and the END LOOP
keywords.
The LOOP
statement executes the statements in its body and returns control to the top of the loop. Typically, the body of the loop contains at least one EXIT
or EXIT WHEN
statement for terminating the loop. Otherwise, the loop becomes an infinite loop.
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.
1 | LOOP |
Typically, you use the EXIT
statement with an IF
statement to terminate a loop when a condition is true:
1 | LOOP |
The following example illustrates how to use the LOOP
statement to execute a sequence of code and EXIT
statement to terminate the loop.
1 | DECLARE |
The following explains the logic of the code:
- First, declare and initialize a variable
l_counter
to zero. - Second, increase the
l_counter
by one inside the loop and exit the loop if thel_counter
is greater than three. If thel_counter
is less than or equal three, show thel_counter
value. Because the initial value ofl_counter
is zero, the code in the body of the loop executes three times before it is terminated. - Third, display the value of the
l_counter
after the loop.
EXIT WHEN statement
The EXIT WHEN
statement has the following syntax:
1 | 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.
Each time the control reaches the EXIT WHEN
statement, the condition is evaluated. If the condition evaluates to TRUE
, then the loop terminates. Otherwise, the EXIT WHEN
clause does nothing. Inside the loop body, you must make the condition TRUE
at some point to prevent an infinite loop.
The following example uses the EXIT WHEN
statement to terminate a loop.
1 | DECLARE |
Nested loops
It is possible to nest a LOOP
statement within another LOOP
statement as shown in the following example:
1 | DECLARE |
PL/SQL FOR LOOP Statement
Introduction to PL/SQL FOR LOOP statement
PL/SQL FOR LOOP
executes a sequence of statements a specified number of times. The PL/SQL FOR LOOP
statement has the following structure:
1 | FOR index IN lower_bound .. upper_bound |
The index
is an implicit variable. It is local to the FOR LOOP
statement. In other words, you cannot reference it outside the loop.
Inside the loop, you can reference index
but you cannot change its value. After the FOR LOOP
statement executes, the index becomes undefined.
Both lower_bound
and upper_bound
are numbers or expressions that evaluate to numbers. The lower_bound
and upper_bound
are evaluated once when the FOR LOOP
statement starts. Their values are stored as temporary PLS_INTEGER
values. The results of lower_bound
and upper_bound
are rounded to the nearest integer if necessary.
If you modify the values of lower_bound
or upper_bound
inside the loop, the change will have no effect because they are evaluated once only before the first loop iteration starts.
Typically, lower_bound
is less than upper_bound
. In this case, index
is set to lower_bound
, the statements
execute, and control returns to the top of the loop, where index
is compared to upper_bound
. If index
is less than upper_bound
, index
is incremented by one, the statements
execute, and control again returns to the top of the loop. When index
is greater than upper_bound
, the loop terminates, and control transfers to the statement after the FOR LOOP
statement.
If lower_bound
is equal to upper_bound
, the statements execute only once. When lower_bound
is greater than upper_bound
, the statements do not execute at all.
PL/SQL FOR LOOP examples
Let’s take some examples of using the FOR LOOP
statement to understand how it works.
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.
1 | BEGIN |
Simulating STEP clause in FOR LOOP statement
The loop index is increased by one after each loop iteration and you cannot change the increment e.g., two, three and four. However, you can use an additional variable to simulate the increment by two, three, four, etc., as shown in the example below:
1 | DECLARE |
Referencing variable with the same name as the loop index
In this example, we had a variable named l_counter
, which is also the name of the index. To reference the variable l_counter
inside the loop, you must qualify it using a block label as shown below:
1 | <<outer>> |
FOR LOOP with REVERSE keyword
The following shows the structure of the FOR LOOP
statement with REVERSE
keyword:
1 | FOR index IN REVERSE lower_bound .. upper_bound |
With the REVERSE
keyword, the index
is set to upper_bound
and decreased by one in each loop iteration until it reaches lower_bound
.
See the following example:
1 | BEGIN |
PL/SQL WHILE Loop Statement
The WHILE loop statement syntax
Here is the syntax for the WHILE loop statement:
1 | WHILE condition |
The condition in the WHILE
is a Boolean expression that evaluates to TRUE
, FALSE
or NULL
.
The WHILE
loop statement continues to execute the statements between the LOOP
and END LOOP
as long as the condition in the WHILE
clause evaluates to TRUE
.
PL/SQL evaluates the condition in the WHILE
clause before each loop iteration. If the condition is TRUE
, then the loop body executes. In case it is FALSE
or NULL
, the loop terminates.
If the condition is FALSE
before entering the loop, the WHILE
loop does not execute at all. This behavior is different from the LOOP
statement whose loop body always executes once.
To terminate the loop prematurely, you use an EXIT
or EXIT WHEN
statement.
PL/SQL WHILE loop examples
Let’s take some examples of using the WHILE
loop statement to see how it works.
Simple WHILE loop example
The following example illustrates how to use the WHILE
loop statement:
1 | DECLARE |
WHILE loop example terminated by EXIT WHEN statement
The following example is the same as the one above except that it has an additional EXIT WHEN
statement.
1 | DECLARE |
PL/SQL CONTINUE Statement
PL/SQL CONTINUE statement
The CONTINUE
statement allows you to exit the current loop iteration and immediately continue on to the next iteration of that loop.
The CONTINUE
statement has a simple syntax:
1 | CONTINUE; |
Typically, the CONTINUE
statement is used within an IF THEN
statement to exit the current loop iteration based on a specified condition as shown below:
1 | IF condition THEN |
The CONTINUE
can be used in all loop constructs including LOOP
, FOR LOOP
and WHILE LOOP
.
The following is a simple example of using the CONTINUE
statement to skip over loop body execution for odd numbers:
1 | BEGIN |
PL/SQL 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.
The syntax of CONTINUE WHEN
statement is:
1 | CONTINUE WHEN condition; |
The condition in the WHEN
clause is evaluated each time the CONTINUE WHEN
statement is reached. If the condition is TRUE
, the current loop is skipped, and control is transferred to the next iteration of the loop. If the condition is not TRUE
, either FALSE
or NULL
, the CONTINUE WHEN
statement does nothing.
Essentially, the CONTINUE WHEN
statement is the combination of an IF THEN
statement and CONTINUE
statement:
1 | IF condition THEN |
Similar to the CONTINUE
statement, you can use the CONTINUE WHEN
statement in LOOP
, FOR LOOP
and WHILE LOOP
.
The following example illustrates how to use the CONTINUE WHEN
statement to skip over loop body execution for even numbers:
1 | BEGIN |
PL/SQL SELECT INTO Statement
Introduction to PL/SQL SELECT INTO statement
PL/SQL SELECT INTO
statement is the simplest and fastest way to fetch a single row from a table into variables. The following illustrates the syntax of the PL/SQL SELECT INTO
statement:
1 | SELECT |
In this syntax, the number of columns in the variable_list
must be the same as the number of variables (or the number of components of a record) in the select_list
. In addition, their corresponding data type must be compatible.
Besides the WHERE
clause, you can use other clauses in the SELECT
statement such as INNER JOIN
, GROUP BY
, HAVING
, and UNION
.
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.
PL/SQL SELECT INTO examples
Let’s use the customers
and contacts
tables in the sample database for demonstration.
PL/SQL SELECT INTO – selecting one column example
The following example uses a SELECT INTO
statement to get the name of a customer based on the customer id, which is the primary key of the customers
table.
1 | DECLARE |
In this example:
- First, declare a variable
l_customer_name
whose data type anchors to the name columns of the customers table. This variable will hold the customer name. - Second, use the
SELECT INTO
statement to select value from the name column and assign it to thel_customer_name
variable. - Third, show the customer name using the
dbms_output.put_line
procedure.
PL/SQL SELECT INTO – selecting a complete row example
The following example fetches the entire row from the customers
table for a specific customer ID:
1 | DECLARE |
In this example:
- First, declare a record based on the row of the
customers
table. This record will hold the entire row of thecustomers
table. - Second, select the customer whose id is 100 into the
r_customer
record. - Third, show the customer’s name and website.
PL/SQL SELECT INTO – selecting data into multiple variables example
The following example fetches the names of customer and contact from the customers
and contacts
tables for a specific customer id.
1 | DECLARE |
In this example:
- First, declare three variables
l_customer_name
,l_contact_first_name
,l_contact_last_name
to hold the customer and contact’s name.
Second, use theSELECT INTO
statement to fetch customer and contact names of the customer id 100 from thecustomers
andcontacts
tables into the corresponding variablesl_customer_name
,l_contact_first_name
,l_contact_last_name
. - Third, display the customer and contact names.
PL/SQL Record
Introduction to PL/SQL record
A PL/SQL record is a composite data structure which consists of multiple fields; each has its own value.
PL/SQL record helps you simplify your code by shifting from field-level to record-level operations.
PL/SQL has three types of records: table-based, cursor-based, programmer-defined. Before using a record, you must declare it.
Declaring records
You define and declare records in the declaration section of a block or via package specification.
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.
1 | DECLARE |
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:
1 | DECLARE |
Programmer-defined record
The table-based and cursor-based records are good enough when you need to create records based on existing structures.
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:
Define a record type that contains the structure you want in your record.
Declare a record based on the record type.
1 | TYPE record_type IS RECORD ( |
To declare a record based on the predefined record type, you use the following syntax:
1 | record_name record_type; |
Referencing a record’s field
You reference a field in a record via the dot notation as shown below:
1 | record_name.field_name |
Assigning records
You can assign a record to another record of the same type, for example:
1 | r_contact1 := r_contact2; |
But you cannot compare two records of the same type via a comparisn operator.
You can assign a value to the individual field of a record, for example:
1 | r_contact.first_name := 'John'; |
You can use SELECT INTO
a whole record (or individual fields):
1 | SELECT |
You can FETCH INTO
a whole record or individual fields:
1 | -- fetch a whole record |
Records and INSERT / UPDATE statements
You can insert a new row into a table using a %ROWTYPE
record without having to specify each field. The following statement creates a new table named persons
for demonstration:
1 | CREATE TABLE persons ( |
The following block inserts a new row into the persons
table using a %ROWTYPE
record:
1 | DECLARE |
To update a row from a %ROWTYPE
record, you use the SET ROW
keywords as shown in the following example:
1 | DECLARE |
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.
The following example declares a record type named address
. Then in the record type customer
has two fields ship_to
and bill_to
which are based on the address record type.
1 | DECLARE |
PL/SQL Exception
Introduction to PL/SQL exceptions
PL/SQL treats all errors that occur in an anonymous block, procedure, or function as exceptions. The exceptions can have different causes such as coding mistakes, bugs, even hardware failures.
It is not possible to anticipate all potential exceptions, however, you can write code to handle exceptions to enable the program to continue running as normal.
The code that you write to handle exceptions is called an exception handler.
A PL/SQL block can have an exception-handling section, which can have one or more exception handlers.
Here is the basic syntax of the exception-handling section:
1 | BEGIN |
In this syntax, e1
, e2
are exceptions.
When an exception occurs in the executable section, the execution of the current block stops and control transfers to the exception-handling section.
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.
After an exception handler executes, control transfers to the next statement of the enclosing block. If there is no enclosing block, then the control returns to the invoker if the exception handler is in a subprogram or host environment (SQL Developer or SQL*Plus) if the exception handler is in an anonymous block.
If an exception occurs but there is no exception handler, then the exception propagates.
PL/SQL exception examples
Let’s take some examples of handling exceptions.
PL/SQL NO_DATA_FOUND exception example
The following block accepts a customer id as an input and returns the customer name :
1 | DECLARE |
If you execute the block and enter the customer id as zero, Oracle will issue the following error:
1 | ORA-01403: no data found |
The ORA-01403
is a predefined exception.
Note that the following line does not execute at all because control transferred to the exception handling section.
1 | dbms_output.put_line('Customer name is ' || l_name); |
To issue a more meaningful message, you can add an exception-handling section as follows:
1 | DECLARE |
PL/SQL TOO_MANY_ROWS exception example
First, modify the code block in the above example as follows and execute it:
1 | DECLARE |
Second, enter the customer id 10 and you’ll get the following error:
1 | ORA-01422: exact fetch returns more than requested number of rows |
This is another exception called TOO_MANY_ROWS
which was not handled by the code.
Third, add the exception handler for the TOO_MANY_ROWS
exception:
1 | DECLARE |
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 internally defined exceptions automatically.
ORA-27102
(out of memory) is one example of Internally defined exceptions. Note that 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.
The following table illustrates the differences between exception categories.
PL/SQL Raise Exceptions
Introduction to PL/SQL raise exceptions
To raise an exception explicitly, you use the RAISE
statement. The RAISE
statement allows you to:
- Raise a user-defined exception.
- Raise an internally defined exception.
- Reraising the current exception.
Raising a user-defined exception
A user-defined exception is defined by users like you or other developers in the declaration section of a block or subprogram.
To define a user-defined exception, you use the following syntax:
1 | DECLARE |
Similar to the variable declaration, you declare an exception in the declaration section of a block.
A user-defined exception must have assigned error_code
. In this syntax, the error_code
is an integer that ranges from -20,999
to -20,000
.
The following example illustrates how to declare a user-defined exception and associate it with an error code.
1 | DECLARE |
In this example,
- First, declare a user-defined exception
e_credit_too_high
and associates it with the error number-20001
. - Second, select maximum credit from the
customers
table using theMAX()
function and assign this value to thel_max_credit
variable. - Third, check if the input credit with the maximum credit, if the input credit is greater than the max, then raise the
e_credit_too_high
exception. - Finally, update the customer whose id is entered by the user with the new credit limit.
Here is the output if you enter customer id 100 and credit limit 20000:
1 | ORA-20001: |
If you want to include a custom message, you can replace the line:
1 | RAISE e_credit_too_high; |
by the following line:
1 | raise_application_error(-20001,'Credit is too high'); |
And execute the code block again, you will receive the following error:
1 | ORA-20001: Credit is too high |
Raising an internally defined exception
Typically, the runtime system raises internally defined exceptions implicitly when they occur. Besides, you can explicitly raise an internally defined exception with the RAISE
statement if the exception has a name:
1 | RAISE exception_name; |
This example shows how to raise an internally defined exception INVALID_NUMBER
:
1 | DECLARE |
Reraising the current exception
You can re-raise the current exception with the RAISE
statement. Reraising an exception passes it to the enclosing block, which later can be handled further. To reraise an exception, you don’t need to specify the exception name.
1 | DECLARE |
In this example:
- First, get the max credit limit from the
customers
table. - Second, compare the max credit with the user-input credit. If the user-input credit is greater than the max credit, then raise the
e_credit_too_high
exception. - Third, display a message and reraise the exception in the exception-handling section in the inner block.
- Finally, in the outer block, reassign the average credit to the
l_credit
variable and update the customer with the newly adjusted credit.
PL/SQL RAISE_APPLICATION_ERROR
Introduction to raise_application_error procedure
The procedure raise_application_error
allows you to issue an user-defined error from a code block or stored program.
By using this procedure, you can report errors to the callers instead of returning unhandled exceptions.
The raise_application_error
has the following syntax:
1 | raise_application_error( |
In this syntax:
- The
error_code
is a negative integer with the range from-20999
to-20000
. - The
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 isTRUE
, the error is added to the stack of previous errors.
The raise_application_error
belongs to the package DBMS_STANDARD
, therefore, you do not need to qualify references to it.
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.
Note that the 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.
PL/SQL raise_application_error example
Let’s take a look at an example of using the raise_application_error
procedure to raise exceptions.
This example uses the raise_application_error
procedure to raise an exception with id -20111
and message Credit Limit Exceeded
:
1 | DECLARE |
In this example:
- First, declare a user-defined exception
credit_limit_exceed
associated with the error number-20111
. - Second, declare two variables
l_customer_id
andl_credit_limit
to store customer id and credit limit entered by users. - Third, get the customer credit limit based on the customer id.
- Finally, check the input credit with the customer credit and use the
raise_application_error
to raise an exception.
PL/SQL Exception Propagation
Introduction to the 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 to the application environment that executed the outermost PL/SQL block.
Note that an unhandled exception stops the execution of the block.
Unhandled exception propagation examples
See the following anonymous block:
1 | DECLARE |
First, execute the block and enter 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.
This picture illustrates the process:
Second, execute the block and enter 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).
The following picture illustrates the propagation of the unhandled exception from the inner block to its enclosing block:
Third, execute the block and enter 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.
The following picture illustrates the propagation of the unhandled exception from the inner block to its enclosing block, and then host environment:
Handling other unhandled exceptions
In this exception handling section, you can include the WHEN OTHERS
clause to catch any otherwise unhandled exceptions:
1 | EXCEPTION |
Because you handle other non-specific exceptions in the WHEN OTHERS
clause, you will need to take advantages 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
The SQLCODE
function accepts no argument and returns a number code of the most recent exception.
If the exceptions are internal, SQLCODE
returns a negative number except for the NO_DATA_FOUND
exception which has the number code +100.
If the exception is user-defined, SQLCODE
returns +1 or the number that you associated with the exception via the pragma EXCEPTION_INIT
.
The SQLCODE
is only usable in the exception-handling section. If you use the SQLCODE
function outside an exception handler, it always returns zero.
This example illustrates how to use the SQLCODE
function:
1 | DECLARE |
In this example, we try to fetch too many rows into a record, which results in a error with the following error code:
1 | Error code:-1422 |
SQLERRM function
The function SQLERRM
takes an argument as an error code and returns the error message associated with that error code:
1 | SQLERRM([error_code]) |
In this syntax, the error_code
can be any valid Oracle error code.
If you omit the error_code
argument, the function will return the error message associated with the current value of SQLCODE
.
Note that the SQLERRM
function with no argument is only useful in an exception handler.
This example illustrates how to use the function SQLERRM
in an exception handler:
1 | DECLARE |
Here is the output:
1 | ORA-01422: exact fetch returns more than requested number of rows |
Using SQLCODE and SQLERRM functions example
The following example inserts a new contact into the contacts table in the sample database. It has an exception handler in the WHERE OTHERS clause of the exception handling section.
1 | DECLARE |
In this example, the exception handler traps two exceptions -1 (duplicate email ) and -2291 (parent key not found). It shows a custom message and reraises the exception using the RAISE
statement.
PL/SQL Cursor
Introduction to PL/SQL cursors
A cursor is a pointer that points to a result of a query. PL/SQL has two types of cursors: implicit cursors and explicit cursors.
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.
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.
Let’s examine each step in detail.
Declaring a cursor
Before using an explicit cursor, you must declare it in the declaration section of a block or package as follows:
1 | 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.
Opening a cursor
Before start fetching rows from the cursor, you must open it. To open a cursor, you use the following syntax:
1 | OPEN cursor_name; |
In this syntax, the cursor_name
is the name of the cursor declared in the declaration section.
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.
Fetching from a cursor
The FETCH
statement places the contents of the current row into variables. The syntax of FETCH
statement is as follows:
1 | FETCH cursor_name INTO variable_list; |
To retrieve all rows in a result set, you need to fetch each row till the last one.
Closing a cursor
After fetching all rows, you need to close the cursor with the CLOSE
statement:
1 | 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.
Attributes
A cursor has four attributes to which you can reference in the following format:
1 | cursor_name%attribute |
where cursor_name
is the name of the explicit cursor.
%ISOPEN
This attribute is
TRUE
if the cursor is open orFALSE
if it is not.%FOUND
This attribute has four values:
NULL
before the first fetchTRUE
if a record was fetched successfullyFALSE
if no row returnedINVALID_CURSOR
if the cursor is not opened
%NOTFOUND
NULL
before the first fetchFALSE
if a record was fetched successfullyTRUE
if no row returnedINVALID_CURSOR
if the cursor is not opened
%ROWCOUNT
The
%ROWCOUNT
attribute returns the number of rows fetched from the cursor. If the cursor is not opened, this attribute returnsINVALID_CURSOR
.
PL/SQL cursor example
We will use the orders
and order_items
tables from the sample database for the demonstration.
The following statement creates a view that returns the sales revenues by customers:
1 | CREATE VIEW sales AS |
The values of the credit column are 5% of the total sales revenues.
Suppose you need to develop an anonymous block that:
- Reset credit limits of all customers to zero.
- Fetch customers sorted by sales in descending order and gives them new credit limits from a budget of 1 million.
The following anonymous block illustrates the logic:
1 | DECLARE |
To be continued …