PL/SQL Basics
Benefits of PL/SQL
Improved Performance

Parts of a PL/SQL block: declaration, executable, exception handling
set serveroutput on -- required if we want to see the output
DECLARE
v NUMBER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('It''s ok ...'); -- notice the double quotes
v := 1/v;
DBMS_OUTPUT.PUT_LINE('It is not ...');
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Division by zero');
END;
/ -- !!! Always end PL/SQL blocks with a '/' character !!!
Results: --------- It's ok ... Division by zero
Block Types

Lexical elements of the PL/SQL language:
Delimiters, identifiers, literals, comments
Examples for delimiters:
- '+' Addition operator
- ':=' Assignment operator
- '<<' Label delimiter (begin)
- '>>' Label delimiter (end)
- '!=' Relational operator (not equal)
Identifiers can denote the following PL/SQL objects:
- Constants, Cursors, Exceptions, Keywords, Labels, Packages Reserved words, Subprograms, Variables, Types
- Predefined identifiers in STANDARD package, e.g. ZERO_DIVIDE exception
Literals (numeric, character, string, logical, date)
- integer: 12
- real: 12.0
- char: 'a'
- string: 'abc', '' (null string, actually NULL)
- logical: TRUE, FALSE, NULL
- date: DATE '2011-12-25'
Comments: single line and multiline
-- single line comment
/ multiline comment /
Variables
-
PL/SQL variables:
- Scalar
- Composite
- Reference
- LOB (large objects)
-
Non-PL/SQL variables: Bind and host variables
-- we cannot put a space into delimiters ( := )
BEGIN
count := count + 1; -- correct
count : = count + 1; -- incorrect
END;
/
-- DATETIME and INTERVAL literals
DECLARE
d1 DATE := DATE '1998-12-25';
t1 TIMESTAMP := TIMESTAMP '1997-10-22 13:01:01';
t2 TIMESTAMP WITH TIME ZONE := TIMESTAMP '1997-01-31 09:26:56.66 +02:00';
-- Three years and two months
-- For greater precision, use the day-to-second interval
i1 INTERVAL YEAR TO MONTH := INTERVAL '3-2' YEAR TO MONTH;
-- Five days, four hours, three minutes, two and 1/100 seconds
i2 INTERVAL DAY TO SECOND := INTERVAL '5 04:03:02.01' DAY TO SECOND;
BEGIN
NULL;
END;
/
Variable Initialization and Keywords
Using:
- Assignment operator (:=)
- DEFAULT keyword
- NOT NULL constraint
Base Scalar Datatypes
- VARCHAR2 (maximum_length)
- NUMBER [(precision, scale)]
- DATE
- CHAR [(maximum_length)]
- LONG
- LONG RAW
- BOOLEAN
- BINARY_INTEGER
- PLS_INTEGER
-- variable declarations
DECLARE
part_number NUMBER(6); -- SQL data type
part_name VARCHAR2(20); -- SQL data type
in_stock BOOLEAN; -- PL/SQL-only data type
part_price NUMBER(6,2); -- SQL data type
part_description VARCHAR2(50); -- SQL data type
BEGIN
NULL;
END;
/
-- Constant declarations
DECLARE
credit_limit CONSTANT REAL := 5000.00; -- SQL data type
max_days_in_year CONSTANT INTEGER := 366; -- SQL data type
urban_legend CONSTANT BOOLEAN := FALSE; -- PL/SQL-only data type;
BEGIN
NULL;
END;
/
Declaring Boolean Variables
- Only the values TRUE, FALSE, and NULL can be assigned to a Boolean variable.
- The variables are connected by the logical operators AND, OR, and NOT.
- The variables always yield TRUE, FALSE, or NULL.
- Arithmetic, character, and date expressions can be used to return a Boolean value.
Composite Datatypes
- PL/SQL TABLES
- PL/SQL RECORDS
-- Initialization
DECLARE
hours_worked INTEGER := 40;
employee_count INTEGER := 0;
pi CONSTANT REAL := 3.14159;
radius REAL := 1;
area REAL := (pi * radius**2);
BEGIN
NULL;
END;
/
-- Default initializations
DECLARE
counter INTEGER; -- initial value is NULL by default
BEGIN
counter := counter + 1; -- NULL + 1 is still NULL
IF counter IS NULL THEN
DBMS_OUTPUT.PUT_LINE('counter is NULL.');
END IF;
END;
/
Results: --------- counter is NULL.
-- All variables will be NULL
DECLARE
null_string VARCHAR2(80) := TO_CHAR('');
address VARCHAR2(80);
zip_code VARCHAR2(80) := SUBSTR(address, 25, 0);
name VARCHAR2(80);
valid BOOLEAN := (name != '');
BEGIN
NULL;
END;
/
-- %TYPE type inheritance from a column or from other variable
-- in case of column it doesn't inherit constraint, only data type
DECLARE
v_name emp.ename%TYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE('name=' || v_name);
END;
/
-
Declare a variable according to:
- A database column definition
- Another previously declared variable
-
Prefix %TYPE with:
- The database table and column
- The previously declared variable name
DECLARE
name VARCHAR(25) NOT NULL := 'Smith';
surname name%TYPE := 'Jones'; -- inherits NOT NULL constraint too
BEGIN
DBMS_OUTPUT.PUT_LINE('name=' || name);
DBMS_OUTPUT.PUT_LINE('surname=' || surname);
END;
/
Results: --------- name=Smith surname=Jones
-- %ROWTYPE record type inheritance (from table, view, cursor, record)
CREATE TABLE employees_temp (
empid NUMBER(6) NOT NULL PRIMARY KEY,
deptid NUMBER(6) CONSTRAINT c_employees_temp_deptid CHECK (deptid BETWEEN 100 AND 200),
deptname VARCHAR2(30) DEFAULT 'Sales' );
DECLARE
emprec employees_temp%ROWTYPE;
BEGIN
emprec.empid := NULL; -- NOT Null constraint not inherited
emprec.deptid := 50; -- Check constraint not inherited
DBMS_OUTPUT.PUT_LINE ('emprec.deptname: ' || emprec.deptname); -- Initial value not inherited
END;
/
-- Error if identical names within a PL/SQL unit
DECLARE
id BOOLEAN;
id VARCHAR2(5); -- duplicate identifier
BEGIN
id := FALSE;
END;
/
PL/SQL units are the following:
- PL/SQL anonymous block
- FUNCTION
- LIBRARY
- PACKAGE
- PACKAGE BODY
- PROCEDURE
- TRIGGER
- TYPE
- TYPE BODY
-- qualified names (within the procedure it refers to local identifier)
-- 'echo' -> name of a label and a procedure
SET SERVEROUTPUT ON
<<echo>> -- name of a label (we can use labels to qualify PL/SQL program blocks)
DECLARE
x NUMBER := 5;
PROCEDURE echo IS -- name of a procedure
x NUMBER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('x = ' || x); -- output: x = 0
DBMS_OUTPUT.PUT_LINE('echo.x = ' || echo.x); -- output: echo.x = 0 (variable of the procedure)
END;
BEGIN
echo;
END;
/
Results: --------- x = 0 echo.x = 0
-- initialization of a logical variable
DECLARE
done BOOLEAN; -- Initial value is NULL by default
counter NUMBER := 0;
BEGIN
done := FALSE; -- Assign literal value
WHILE done != TRUE -- Compare to literal value
LOOP
counter := counter + 1;
done := (counter > 500); -- Assign value of BOOLEAN expression
END LOOP;
DBMS_OUTPUT.PUT_LINE(counter); -- output: 501
END;
/
Results: --------- 501
Operators in PL/SQL
Examples:
-
Increment the index for a loop.
- v_count := v_count + 1;
-
Set the value of a Boolean flag.
- v_equal := (v_n1 = v_n2);
-
Validate an employee number if it contains a value.
- v_valid := (v_empno IS NOT NULL);
-- SELECT data from a table into a variable
set serveroutput on
DECLARE
bonus NUMBER(7,2);
BEGIN
SELECT sal * 0.10 INTO bonus
FROM emp -- you need table EMP !!!
WHERE empno = 7900;
DBMS_OUTPUT.PUT_LINE('bonus = ' || TO_CHAR(bonus));
END;
/
Results: --------- bonus = 95
Datatype Conversion
- Convert data to comparable datatypes.
- Mixed datatypes can result in an error and affect performance.
-
Conversion functions:
-
TO_CHAR
- TO_DATE
- TO_NUMBER
Example:
DECLARE
v_date VARCHAR2(15);
BEGIN
SELECT TO_CHAR(hiredate, 'MON. DD, YYYY')
INTO v_date
FROM emp
WHERE empno = 7839;
END;
-- Two NULLs are not equal. Neither != is TRUE for them.
set serveroutput on
DECLARE
a NUMBER := NULL;
b NUMBER := NULL;
BEGIN
IF a = b THEN -- yields NULL, not TRUE
DBMS_OUTPUT.PUT_LINE('a = b'); -- not run
ELSIF a != b THEN -- yields NULL, not TRUE
DBMS_OUTPUT.PUT_LINE('a != b'); -- not run
ELSE
DBMS_OUTPUT.PUT_LINE('Can''t tell if two NULLs are equal');
END IF;
END;
/
Results: --------- Can't tell if two NULLs are equal
-- Concatenation operator ignores NULL.
set serveroutput on
BEGIN
DBMS_OUTPUT.PUT_LINE ('apple' || NULL || NULL || 'sauce'); -- output: applesauce
END;
/
Results: --------- applesauce
-- use brackets
DECLARE
a INTEGER := 2**2*3**2;
b INTEGER := (2**2)*(3**2);
BEGIN
DBMS_OUTPUT.PUT_LINE('a = ' || TO_CHAR(a)); -- output: a = 36
DBMS_OUTPUT.PUT_LINE('b = ' || TO_CHAR(b)); -- output: b = 36
END;
/
Results: --------- a = 36 b = 36
-- lazy evaluation
DECLARE
on_hand INTEGER := 0;
on_order INTEGER := 100;
BEGIN
IF (on_hand = 0) OR ((on_order / on_hand) < 5) THEN -- Will not cause ZERO_DIVIDE exception
DBMS_OUTPUT.PUT_LINE('On hand quantity is zero.');
END IF;
END;
/
Results: --------- On hand quantity is zero.
Subtypes
Unconstrained (gives only an alias for compatibility purpose)
Contrained (a real subset)
Predefined subtypes in STANDARD package, you can see them with the following query:
Examples from the package:
type BOOLEAN is (FALSE, TRUE);
subtype INTEGER is NUMBER(38,0);
subtype DECIMAL is NUMBER(38,0);
subtype BINARY_INTEGER is INTEGER range '-2147483647'..2147483647;
subtype NATURAL is BINARY_INTEGER range 0..2147483647;
subtype NATURALN is NATURAL not null;
type sys_refcursor is ref cursor;
-- user-defined subtypes (record, table, type inheritance + constraint)
DECLARE
SUBTYPE BirthDate IS DATE NOT NULL; -- Based on DATE type
SUBTYPE Counter IS NATURAL; -- Based on NATURAL subtype
SUBTYPE pinteger IS PLS_INTEGER RANGE -9..9;
TYPE NameList IS TABLE OF VARCHAR2(10);
SUBTYPE DutyRoster IS NameList; -- Based on TABLE type
TYPE TimeRec IS RECORD (minutes INTEGER, hours INTEGER);
SUBTYPE FinishTime IS TimeRec; -- Based on RECORD type
SUBTYPE ID_Num IS employees.employee_id%TYPE; -- Based on column type
BEGIN
NULL;
END;
/
-- implicit conversion CHAR -> NUMBER
set serveroutput on
DECLARE
V1 CHAR(10):='1234';
V2 NUMBER;
BEGIN
V2 := V1 + 1; -- CHAR -> NUMBER
DBMS_OUTPUT.PUT_LINE ('V2: ' || TO_CHAR(V2));
END;
/
Results: --------- V2: 1235
-- Take care of blank-padded semantics in case of CHAR data type !!!
set serveroutput on
DECLARE
last_name1 CHAR(5) := 'BELLO'; -- no trailing blanks
last_name2 CHAR(10) := 'BELLO '; -- trailing blanks
BEGIN
IF last_name1 = last_name2 THEN
DBMS_OUTPUT.PUT_LINE(last_name1 || '(length:'|| length(last_name1)||') equals ' ||
last_name2 || '(length:'|| length(last_name2)||')');
ELSE
DBMS_OUTPUT.PUT_LINE(last_name2 || ' does not equal ' || last_name1);
END IF;
END;
/
Results: --------- BELLO(length:5) equals BELLO (length:10)
-- Using TIMESTAMP data type
-- TIMESTAMP stores the fractional seconds
DECLARE
start_time VARCHAR2(50);
finish_time VARCHAR2(50);
elapsed_time NUMBER(30,10);
BEGIN
-- Get system time as seconds and fractional seconds past midnight:
SELECT TO_CHAR(SYSTIMESTAMP,'SSSSS.FF') INTO start_time FROM sys.DUAL;
-- Get system time again:
SELECT TO_CHAR(SYSTIMESTAMP,'SSSSS.FF') INTO finish_time FROM sys.DUAL;
elapsed_time := TO_NUMBER(finish_time, '99999.999999')
- TO_NUMBER(start_time, '99999.999999');
DBMS_OUTPUT.PUT_LINE ('Elapsed time: ' || TO_CHAR(elapsed_time,'0.999999'));
END;
/
Results: --------- Elapsed time: 0.000055
-- simple CASE expression (this is not a statement but an expression!)
set serveroutput on
DECLARE
grade CHAR(1) := 'B';
appraisal VARCHAR2(20);
BEGIN
appraisal :=
CASE grade
WHEN 'A' THEN 'Excellent'
WHEN 'B' THEN 'Very Good'
WHEN 'C' THEN 'Good'
WHEN 'D' THEN 'Fair'
WHEN 'F' THEN 'Poor'
ELSE 'No such grade'
END;
DBMS_OUTPUT.PUT_LINE ('Grade ' || grade || ' is ' || appraisal); -- Very Good
END;
/
Results: --------- Grade B is Very Good
-- This form of CASE tests equality so grade = NULL will not be TRUE !!!
-- use the other form, see in the next example below
set serveroutput on
DECLARE
grade CHAR(1); -- NULL by default
appraisal VARCHAR2(20);
BEGIN
appraisal :=
CASE grade
WHEN NULL THEN 'No grade assigned' -- grade = NULL
WHEN 'A' THEN 'Excellent'
WHEN 'B' THEN 'Very Good'
WHEN 'C' THEN 'Good'
WHEN 'D' THEN 'Fair'
WHEN 'F' THEN 'Poor'
ELSE 'No such grade'
END;
DBMS_OUTPUT.PUT_LINE ('Grade ' || grade || ' is: ' || appraisal); -- No such grade
END;
/
Results: --------- Grade is: No such grade
-- logical expressions after WHEN
-- First TRUE condition will be returned
set serveroutput on
DECLARE
grade CHAR(1); -- NULL by default
appraisal VARCHAR2(20);
BEGIN
appraisal :=
CASE
WHEN grade IS NULL THEN 'No grade assigned'
WHEN grade = 'A' THEN 'Excellent'
WHEN grade = 'B' THEN 'Very Good'
WHEN grade = 'C' THEN 'Good'
WHEN grade = 'D' THEN 'Fair'
WHEN grade = 'F' THEN 'Poor'
ELSE 'No such grade'
END;
DBMS_OUTPUT.PUT_LINE ('Grade ' || grade || ' is: ' || appraisal); -- No grade assigned
END;
/
Logic Tables
Build a simple Boolean condition with a comparison operator.

Any arithmetic expression containing a null value evaluates to NULL.
Retrieving Data in PL/SQL
Retrieve the order date and the ship date for the specified order.
DECLARE
v_orderdate ord.orderdate%TYPE;
v_shipdate ord.shipdate%TYPE;
BEGIN
SELECT orderdate, shipdate
INTO v_orderdate, v_shipdate
FROM ord
WHERE id = 620;
...
END;
Updating Data
Increase the salary of all employees in the emp table who are Analysts.
DECLARE
v_sal_increase emp.sal%TYPE := 2000;
BEGIN
UPDATE emp
SET sal = sal + v_sal_increase
WHERE job = 'ANALYST';
END;
Control statements
IF THEN
IF THEN ELSE
IF THEN ELSIF
IF condition_1 THEN
statements_1
ELSIF condition_2 THEN
statements_2
[ ELSIF condition_3 THEN
statements_3
]...
[ ELSE
else_statements
]
END IF;
Simple CASE statement
CASE_NOT_FOUND exception if none of them is true
CASE selector
WHEN selector_value_1 THEN statements_1
WHEN selector_value_2 THEN statements_2
...
WHEN selector_value_n THEN statements_n
[ ELSE else_statements ]
END CASE;
Searched CASE statement
CASE_NOT_FOUND exception if none of them is 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;
Basic LOOP
Exit from loop / starting a new iteration
WHILE LOOP
FOR LOOP
-- NULL statement
NULL;
-- GOTO statement
GOTO label
-- RETURN statement (return from a subprogram or from an anonymous block)
BEGIN
FOR i IN 1..3 loop
dbms_output.put_line(i);
IF i=2 THEN RETURN; END IF;
END loop;
END;
/
-- index of FOR loop is local within the loop
set serveroutput on
<<main>> -- Label block.
DECLARE
i NUMBER := 5;
BEGIN
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE ('local: ' || TO_CHAR(i) || ', global: ' || TO_CHAR(main.i));
END LOOP;
END main;
/
Results: --------- local: 1, global: 5 local: 2, global: 5 local: 3, global: 5
-- Loop indexes in nested loops
/*
- Nest loops to multiple levels.
- Use labels to distinguish between blocks and loops.
- Exit the outer loop with the EXIT statement referencing the label. */
set serveroutput on
BEGIN
<<outer_loop>>
FOR i IN 1..3 LOOP
<<inner_loop>>
FOR i IN 1..3 LOOP
IF outer_loop.i = 2 THEN
DBMS_OUTPUT.PUT_LINE('outer: ' || TO_CHAR(outer_loop.i) || ' inner: ' || TO_CHAR(inner_loop.i));
END IF;
END LOOP inner_loop;
END LOOP outer_loop;
END;
/
Results: --------- outer: 2 inner: 1 outer: 2 inner: 2 outer: 2 inner: 3
/*************** scope and visibility *************/
<<label1>>
DECLARE
a NUMBER;
BEGIN
a:=2;
<<label2>>
DECLARE
a number;
BEGIN
a:=4;
dbms_output.put_line(label1.a);
dbms_output.put_line(label2.a);
dbms_output.put_line(a);
END;
dbms_output.put_line(a);
END;
/
Results: --------- 2 4 4 2
/************** outer (bind) variables, it works only in SQL*PLUS environment, not in SqlDeveloper *************/
/* program gets a pointer, so it can modify the variable*/
Variable v number;
BEGIN
:v := mod(121,3);
END;
/
print v;
Using Bind Variables
To reference a bind variable in PL/SQL, you must prefix its name with a colon (:).
VARIABLE g_salary NUMBER
DECLARE
v_sal emp.sal%TYPE;
BEGIN
SELECT sal
INTO v_sal
FROM emp
WHERE empno = 7369;
:g_salary := v_sal;
END;
/************** printing on the screen *************/
set serveroutput on
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello World!');
END;
/
Use SET SERVEROUTPUT ON
A PL/SQL program actually doesn't print on the screen.
It prints into a buffer on the server side.
You can use GET_LINE to read from the buffer.
SET SERVEROUTPUT ON
DECLARE
v_status INTEGER := 0;
v_line VARCHAR2(100);
v_buff VARCHAR2(1000);
BEGIN
-- We print 3 lines into the buffer.
DBMS_OUTPUT.PUT_LINE('1-bubu'); DBMS_OUTPUT.PUT_LINE('2-bibi'); DBMS_OUTPUT.PUT_LINE('3-baba');
-- We empty the buffer.
WHILE v_status = 0 LOOP
DBMS_OUTPUT.GET_LINE (v_line, v_status);
v_buff := v_buff || v_line;
END LOOP;
-- We print the concatenated string into the buffer.
DBMS_OUTPUT.PUT_LINE(v_buff);
END;
/
Results: --------- 1-bubu2-bibi3-baba
Runtime environment reads the buffer only after the program terminated.
Without 'set serveroutput on' result remains in buffer.
set serveroutput off -- notice OFF !!!
BEGIN
DBMS_OUTPUT.PUT_LINE('first program');
DBMS_OUTPUT.PUT_LINE('second program');
END;
/
SET SERVEROUTPUT ON
DECLARE
v_status INTEGER := 0;
v_line VARCHAR2(100);
BEGIN
WHILE v_status = 0 LOOP
DBMS_OUTPUT.GET_LINE (v_line, v_status); -- reads buffer
END LOOP;
DBMS_OUTPUT.PUT_LINE('second program'); -- writes into buffer
END;
/
/****************** Input from the user ****************/
/* substitution happens before SQL statement is sent to Oracle Server */
ACCEPT v1 NUMBER FORMAT '99' PROMPT 'Give the department number:'
ACCEPT v2 DATE FORMAT 'yyyy.mm.dd' DEFAULT 1982.01.01 PROMPT 'Date (format: 2013.01.30)'
set serveroutput on
DECLARE
v_sum NUMBER;
BEGIN
SELECT sum(sal) INTO v_sum FROM emp WHERE deptno = &v1 AND hiredate < to_date('&v2', 'yyyy.mm.dd');
dbms_output.put_line(v_sum);
END;
/
/****************** records ****************/
DECLARE
TYPE rektip IS RECORD(m1 INTEGER, m2 VARCHAR2(10));
rec rektip;
BEGIN
rec.m1 := 1; rec.m2 := 'Bubu';
DBMS_OUTPUT. PUT_LINE(rec.m2);
END;
/****************** records, arrays ****************/
set serveroutput on
DECLARE
TYPE rek_type IS RECORD(f1 INTEGER DEFAULT 10, f2 dept%ROWTYPE); -- nested record (ROWTYPE)
rec rek_type;
TYPE tab_type IS TABLE OF INTEGER INDEX BY BINARY_INTEGER; -- associative (index by) array
TYPE rek_type2 IS RECORD(f1 INTEGER, f2 tab_type); -- nested array within a field
rec2 rek_type2;
rec_oszt dept%ROWTYPE; -- a record too
BEGIN
rec_oszt.dname := 'SALES';
dbms_output.put_line(rec.f1); -- default value of the field
SELECT * INTO rec.f2 FROM dept WHERE deptno = 10; -- rec (nested record)
dbms_output.put_line(rec.f2.loc);
rec2.f2(1) := 100; rec2.f2(2) := 200; rec2.f2(3) := 300; -- rec2 (array within a field)
FOR i IN rec2.f2.FIRST .. rec2.f2.LAST LOOP
dbms_output.put_line(rec2.f2(i));
END LOOP;
END;
/
Results: --------- 10 NEW YORK 100 200 300
PL/SQL Records
- Must contain one or more components of any scalar, RECORD, or PL/SQL TABLE datatype, called fields
- Are similar in structure to records in a 3GL
- Are not the same as rows in a database table
- Treat a collection of fields as a logical unit
- Are convenient for fetching a row of data from a table for processing
Using PL/SQL Table Methods
The following methods make PL/SQL tables easier to use:
- EXISTS
- COUNT
- FIRST and LAST
- PRIOR
- NEXT
- EXTEND
- TRIM
- DELETE
PL/SQL Tables
-
Are composed of two components:
- Primary key of datatype BINARY_INTEGER
- Column of scalar or record datatype
-
Increase dynamically because they are unconstrained
The %ROWTYPE Attribute
- Declare a variable according to a collection of columns in a database table or view.
- Prefix %ROWTYPE with the database table.
- Fields in the record take their names and datatypes from the columns of the table or view.