Programming in Oracle
with PL/SQL
Procedural Language Extension to SQL
1
PL/SQL
• Allows using general programming tools
with SQL, for example: loops,
conditions, functions, etc.
• This allows a lot more freedom than
general SQL, and is lighter-weight than
JDBC.
• We write PL/SQL code in a regular file,
for example PL.sql, and load it with @PL
in the sqlplus console.
2
PL/SQL Blocks
• PL/SQL code is built of Blocks, with a unique
structure.
• There are two types of blocks in PL/SQL:
1. Anonymous Blocks: have no name (like scripts)
• can be written and executed immediately in SQLPLUS
• can be used in a trigger
2. Named Blocks:
• Procedures
• Functions
3
:Anonymous Block Structure
DECLARE (optional)
/* Here you declare the variables you will use in this
block */
BEGIN (mandatory)
/* Here you define the executable statements (what
the block DOES!)*/
EXCEPTION (optional)
/* Here you define the actions that take place if an
exception is thrown during the run of this block */
END; (mandatory)
/ A correct completion of a block
will generate the following
Always put a new line with only :message
a / at the end of a block! (This
4 PL/SQL procedure successfully
tells Oracle to run the block)
completed
5
DECLARE
Syntax
identifier
identifier [CONSTANT]
[CONSTANT] datatype
datatype [NOT
[NOT NULL]
NULL]
[:=
[:= || DEFAULT
DEFAULT expr];
expr];
Notice that PL/SQL
Examples includes all SQL types,
…and more
Declare
Declare
birthday
birthday DATE;
DATE;
age
age NUMBER(2)
NUMBER(2) NOT
NOT NULL
NULL :=
:= 27;
27;
name
name VARCHAR2(13)
VARCHAR2(13) :=:= 'Levi';
'Levi';
magic
magic CONSTANT
CONSTANT NUMBER
NUMBER :=:= 77;
77;
valid
valid BOOLEAN
BOOLEAN NOT
NOT NULL
NULL :=
:= TRUE;
TRUE;
6
Declaring Variables with the
%TYPE Attribute
Examples Accessing column sname
in table Sailors
DECLARE
sname Sailors.sname%TYPE;
fav_boat VARCHAR2(30);
my_fav_boat fav_boat%TYPE := 'Pinta';
...
Accessing
another variable
7
Declaring Variables with the
%ROWTYPE Attribute
Declare a variable with the type of a
Accessing
.ROW of a table table
Reserves
reserves_record Reserves%ROWTYPE;
And how do we access the fields in
?reserves_record
reserves_record.sid:=9;
Reserves_record.bid:=877;
8
Creating a PL/SQL Record
A record is a type of variable which we can
define (like ‘struct’ in C or ‘object’ in Java)
DECLARE
DECLARE
TYPE
TYPE sailor_record_type
sailor_record_type ISIS RECORD
RECORD
(sname
(sname VARCHAR2(10),
VARCHAR2(10),
sid
sid VARCHAR2(9),
VARCHAR2(9),
age
age NUMBER(3),
NUMBER(3),
rating
rating NUMBER(3));
NUMBER(3));
sailor_record
sailor_record
sailor_record_type;
sailor_record_type;
...
...
BEGIN
BEGIN
Sailor
Sailor__record.sname:=‘peter’;
record.sname:=‘peter’;
9 Sailor
Sailor__record.age:=45;
record.age:=45;
Creating a Cursor
• We create a Cursor when we want to go over a
result of a query (like ResultSet in JDBC)
• Syntax Example:
sailorData is a
DECLARE variable that
cursor c is select * from sailors; can hold a
ROW from
sailorData sailors%ROWTYPE; the sailors
table
BEGIN
open c; Here the
first row of
fetch c into sailorData; sailors is
inserted into
10 sailorData
Example
RAD_VALS
DECLARE
DECLARE
Pi
Piconstant
constantNUMBER(8,7)
NUMBER(8,7):= :=3.1415926;
3.1415926;
radius area
areaNUMBER(14,2);
NUMBER(14,2);
cursor
cursorrad_cursor
rad_cursorisisselect
select**from
fromRAD_VALS;
RAD_VALS;
Rad_cursor rad_value
3 rad_valuerad_cursor%ROWTYPE;
rad_cursor%ROWTYPE;
f BEGIN
BEGIN
e 6 open
openrad_cursor;
rad_cursor;
fetch
fetchrad_cursor
rad_cursorinto
intorad_val;
rad_val;
t area:=pi*power(rad_val.radius,2);
8 area:=pi*power(rad_val.radius,2);
c insert
insertinto
intoAREAS
AREASvalues
values(rad_val.radius,
(rad_val.radius,area);
area);
h close
closerad_cursor;
rad_cursor;
END;
END;
Rad_val //
AREAS
Radius
11 Area
3 28.27
DECLARE
DECLARE DECLARE
DECLARE
… … ……
cursor
cursorrad_cursor
rad_cursorisisselect
select**from
from cursor
cursorrad_cursor
rad_cursorisisselect
select** from
from
;RAD_VALS
;RAD_VALS ;RAD_VALS
;RAD_VALS
;;rad_value
rad_valuerad_cursor%ROWTYPE ;;rad_value
rad_cursor%ROWTYPE rad_valueRAD_VALS.radius%TYPE
RAD_VALS.radius%TYPE
BEGIN
BEGIN BEGIN
BEGIN
;open
;openrad_cursor ;open
rad_cursor ;openrad_cursor
rad_cursor
;fetch
;fetchrad_cursor
rad_cursorinto
intorad_val
rad_val ;fetch
;area:=pi*power(rad_val.radius,2) ;fetchrad_cursor
rad_cursorinto
intorad_val
rad_val
;area:=pi*power(rad_val.radius,2) ;area:=pi*power(rad_val,2)
;area:=pi*power(rad_val,2)
,insert
,insertinto
intoAREAS
AREASvalues
values(rad_val.radius
(rad_val.radius ;insert
;);)area ;insertinto
intoAREAS
AREASvalues
values(rad_val,
(rad_val,area)
area)
area …
… …
…
DECLARE
DECLARE DECLARE
DECLARE
…… … …
;cursor
;cursorrad_cursor
rad_cursorisisselect
select**from
fromRAD_VALS
RAD_VALS cursor
cursorrad_cursor
rad_cursorisisselect
selectradius
radiusfrom
from
;rad_value RAD_VALS%ROWTYPE
;rad_value RAD_VALS%ROWTYPE ;RAD_VALS
;RAD_VALS
BEGIN ;;rad_value
BEGIN rad_valueRAD_VALS.radius%TYPE
RAD_VALS.radius%TYPE
;open
;openrad_cursor
rad_cursor BEGIN
BEGIN
;fetch
;fetchrad_cursor
rad_cursorinto
intorad_val ;open
rad_val ;openrad_cursor
rad_cursor
;area:=pi*power(rad_val.radius,2) ;fetch
;area:=pi*power(rad_val.radius,2) ;fetchrad_cursor
rad_cursorinto
intorad_val
rad_val
insert
insertinto
intoAREAS
AREASvalues
values(rad_val.radius,
(rad_val.radius, ;area:=pi*power(rad_val,2)
;area) ;area:=pi*power(rad_val,2)
;area) ;insert
… ;insertinto
intoAREAS
AREASvalues
values(rad_val,
(rad_val,area)
area)
… …
12 …
Explicit Cursor Attributes
Obtain status information about a
.cursor
Attribute Type Description
%ISOPEN Boolean Evaluates to TRUE if the cursor
is open.
%NOTFOUND Boolean Evaluates to TRUE if the most
recent fetch does not return a row.
%FOUND Boolean Evaluates to TRUE if the most
recent fetch returns a row;
complement of %NOTFOUND
%ROWCOUNT Number Evaluates to the total number of
13 rows returned so far.
SELECT Statements
DECLARE
v_sname VARCHAR2(10);
v_rating NUMBER(3);
BEGIN
SELECT sname, rating
INTO v_sname, v_rating
FROM Sailors
WHERE sid = '112';
END;
/
• INTO clause is required.
• Query must return exactly one row.
• Otherwise, a NO_DATA_FOUND or
TOO_MANY_ROWS exception is thrown
14
Conditional logic
:Condition :Nested conditions
If <cond> If <cond>
then <command> then
elsif <cond2> if <cond2>
then <command2> then
else <command1>
<command3> end if;
end if; else <command2>
end if;
15
IF-THEN-ELSIF Statements
. . .
IF rating > 7 THEN
v_message := 'You are great';
ELSIF rating >= 5 THEN
v_message := 'Not bad';
ELSE
v_message := 'Pretty bad';
END IF;
. . .
16
Suppose we have the following
:table
create table mylog( mylog
who varchar2(30), logon_num who
logon_num number
); 3 Peter
• Want to keep track of how 4 John
many times someone
2 Moshe
logged on to the DB
• When running, if user is
already in table, increment
logon_num. Otherwise,
17
insert user into table
Solution
DECLARE
cnt NUMBER;
BEGIN
select count(*)
into cnt
from mylog
where who = user;
if cnt > 0 then
update mylog
set logon_num = logon_num + 1
where who = user;
else
insert into mylog values(user, 1);
end if;
commit;
end;
18 /
SQL Cursor
SQL cursor is automatically created after
:each SQL query. It has 4 useful attributes
SQL%ROWCOUNT Number of rows affected by the
most recent SQL statement (an
integer value).
SQL%FOUND Boolean attribute that evaluates to
TRUE if the most recent SQL
statement affects one or more rows.
SQL%NOTFOUND Boolean attribute that evaluates to
TRUE if the most recent SQL
statement does not affect any rows.
SQL%ISOPEN Always evaluates to FALSE because
PL/SQL closes implicit cursors
19 immediately after they are executed.
Solution (2)
BEGIN
update mylog
set logon_num = logon_num + 1
where who = user;
if SQL%ROWCOUNT = 0 then
insert into mylog values(user, 1);
end if;
commit;
END;
/
20
Loops: Simple Loop
create table number_table(
num NUMBER(10)
);
DECLARE
i number_table.num%TYPE := 1;
BEGIN
LOOP
INSERT INTO number_table
VALUES(i);
i := i + 1;
EXIT WHEN i > 10;
END LOOP;
END;
21
Loops: Simple Cursor Loop
create table doubles(
num NUMBER(10)
);
DECLARE
cursor c is select * from number_table;
cVal c%ROWTYPE;
BEGIN
open c;
LOOP
fetch c into cVal;ل11لىا11طر ا11لاول س11 ارسا,I;`hcval
EXIT WHEN c%NOTFOUND;
insert into doubles values(cVal.num*2);
END LOOP;
22
END;
Loops: FOR Loop
DECLARE
i number_table.num%TYPE;
BEGIN
FOR i IN 1..10 LOOP
INSERT INTO doubles VALUES(i);
END LOOP;
END;
Notice that i is incremented
automatically
23
Loops: For Cursor Loops
DECLARE
cursor c is select * from number_table;
BEGIN
for num_row in c loop
insert into doubles
values(num_row.num*2);
end loop;
END;
/
Notice that a lot is being done implicitly:
declaration of num_row, open cursor,
24 fetch cursor, the exit condition
Loops: WHILE Loop
DECLARE
TEN number:=10;
i number_table.num%TYPE:=1;
BEGIN
WHILE i <= TEN LOOP
INSERT INTO doubles
VALUES(i);
i := i + 1;
END LOOP;
END;
25
Printing Output
• You need to use a function in the
DBMS_OUTPUT package in order to print to the
output
• If you want to see the output on the screen, you
must type the following (before starting):
set serveroutput on format wrapped size 1000000
• Then print using
– dbms_output. put_line(your_string);
– dbms_output.put(your_string);
26
Input and output example
set serveroutput on format wrap size 1000000
ACCEPT high PROMPT 'Enter a number: '
DECLARE
i number_table.num%TYPE:=1;
BEGIN
dbms_output.put_line('Look Friend, I can print from PL/SQL!!!');
WHILE i <= &high LOOP
dbms_output.put_line(‘i=‘||i);
INSERT INTO number_table
VALUES(i);
i := i + 1;
END LOOP;
END;
27
Reminder- structure of a block
DECLARE (optional)
/* Here you declare the variables you will use in this
block */
BEGIN (mandatory)
/* Here you define the executable statements (what
the block DOES!)*/
EXCEPTION (optional)
/* Here you define the actions that take place if an
exception is thrown during the run of this block */
END; (mandatory)
/
28
Trapping Exceptions
• Here we define the actions that should
happen when an exception is thrown.
• Example Exceptions:
– NO_DATA_FOUND
– TOO_MANY_ROWS
– ZERO_DIVIDE
• When handling an exception, consider
performing a rollback
29
DECLARE
num_row number_table%ROWTYPE;
BEGIN
select *
into num_row
from number_table;
dbms_output.put_line(1/num_row.num);
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('No data!');
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('Too many!');
WHEN OTHERS THEN
dbms_output.put_line(‘Error’);
30 end;
User-Defined Exception
DECLARE
e_number1 EXCEPTION;
cnt NUMBER;
BEGIN
select count(*)
into cnt
from number_table;
IF cnt = 1 THEN RAISE e_number1;
ELSE dbms_output.put_line(cnt);
END IF;
EXCEPTION
WHEN e_number1 THEN
dbms_output.put_line('Count = ‘||
31 cnt);//print count=1
end;
Functions and Procedures
• Up until now, our code was in an
anonymous block
• It was run immediatelyف ورا
• It is useful to put code in a function or
procedure so it can be called several
times
• Once we create a procedure or function
in a Database, it will remain until
deleted (like a table).
32
Creating Procedures
CREATE
CREATE [OR
[OR REPLACE]
REPLACE] PROCEDURE
PROCEDURE
procedure_name
procedure_name
[(parameter1
[(parameter1 [mode1]
[mode1] datatype1,
datatype1,
parameter2
parameter2 [mode2]
[mode2] datatype2,
datatype2,
.. .. .)]
.)]
IS|AS
IS|AS
PL/SQL
PL/SQL Block;
Block;
• Modes:
– IN: procedure must be called with a value for the parameter.
Value cannot be changed
– OUT: procedure must be called with a variable for the
parameter. Changes to the parameter are seen by the user
(i.e., call by reference)
– IN OUT: value can be sent, and changes to the parameter
are seen by the user
33
• Default Mode is: IN
?Example- what does this do
Table mylog create
create or
or replace
replace procedure
procedure
num_logged
num_logged
_logon (person
who (person IN
IN mylog.who%TYPE,
mylog.who%TYPE,
num num
num OUT
OUT mylog.logon_num%TYPE)
mylog.logon_num%TYPE)
IS
IS
BEGIN
3 Pete BEGIN
select
select logon_num
logon_num
into
into num
num
from
from mylog
4 John where
mylog
where who
who == person;
person;
END;
END;
//
2 Joe
34
Calling the Procedure
declare
declare
howmany
howmany mylog.logon_num%TYPE;
mylog.logon_num%TYPE;
begin
begin
num_logged(
num_logged(''ORA71398',howmany);
ORA71398',howmany);
dbms_output.put_line(howmany);
dbms_output.put_line(howmany);
end;
end;
//
35
Errors in a Procedure
• When creating the procedure, if there are
errors in its definition, they will not be shown
• To see the errors of a procedure called
myProcedure, type
SHOW ERRORS PROCEDURE myProcedure
in the SQLPLUS prompt
• For functions, type
SHOW ERRORS FUNCTION myFunction
36
Creating a Function
• Almost exactly like creating a
procedure, but you supply a return type
CREATE
CREATE [OR
[OR REPLACE]
REPLACE] FUNCTION
FUNCTION
function_name
function_name
[(parameter1
[(parameter1 [mode1]
[mode1] datatype1,
datatype1,
parameter2
parameter2 [mode2]
[mode2] datatype2,
datatype2,
.. .. .)]
.)]
RETURN
RETURN datatype
datatype
IS|AS
IS|AS
PL/SQL
PL/SQL Block;
Block;
37
A Function
create
create or
or replace
replace function
function
rating_message(rating
rating_message(rating IN IN NUMBER)
NUMBER)
return
return VARCHAR2
VARCHAR2
AS
AS NOTE THAT YOU
BEGIN DON'T SPECIFY THE
BEGIN SIZE
IF
IF rating
rating >> 77 THEN
THEN
return
return 'You
'You are
are great';
great';
ELSIF
ELSIF rating
rating >=>= 55 THEN
THEN
return
return 'Not
'Not bad';
bad';
ELSE
ELSE
return
return 'Pretty
'Pretty bad';
bad';
END
END IF;
IF;
END;
END;
//
38
Calling the function
declare
declare
paulRate
paulRate number
number :=9;
:=9;
Begin
Begin
dbms_output.put_line(rating_Message(paulRate))
dbms_output.put_line(rating_Message(paulRate))
;;
end;
end;
//
39
:Creating a function
create or replace function squareFunc(num in number)
return number
is
BEGIN
return num*num;
End;
/
:Using the function
BEGIN
dbms_output.put_line(squareFunc(6));
END;
/
40
Packages
• Functions, Procedures, Variables can be put
together in a package
• In a package, you can allow some of the
members to be "public" and some to be
"private"
• There are also many predefined Oracle
packages
• Won't discuss packages in this course
41
Triggers
• Triggers are special procedures which
we want activated when someone has
performed some action on the DB.
• For example, we might define a trigger
that is executed when someone
attempts to insert a row into a table,
and the trigger checks that the
inserted data is valid
42
CREATE-TRIGGER
PURPOSE: To create and enable a database trigger.
A database trigger is a stored PL/SQL block that is
associated with a table. Oracle automatically executes
a trigger when a specified SQL statement is issued
.against the table
CREATE [OR REPLACE] TRIGGER [schema.]trigger
{BEFORE | AFTER} {DELETE | INSERT | UPDATE [OF
column [, column] ...]} [OR {DELETE | INSERT | UPDATE
[OF column [, column] ...]}] ... ON [schema.]table
[ [REFERENCING { OLD [AS] old [NEW [AS] new] | NEW
[AS] new [OLD [AS] old] } ] FOR EACH ROW [WHEN
(condition)] ]
43
create trigger general_comments_modified before
insert or update on general_comments for each row
;begin :new.modified_date := sysdate; end
/
44
CREATE TABLE T4 (a number, b
CHAR(10));
CREATE TABLE T5 (c CHAR(10), d
number );
CREATE TRIGGER trig1 AFTER INSERT ON T4
FOR EACH ROW WHEN (NEW.a <= 10)
BEGIN
;INSERT INTO T5 VALUES(:NEW.b, :NEW.a)
END trig1;
45
select trigger_name from user_triggers;
select trigger_type, table_name,
triggering_event from user_triggers where
trigger_name = <‘trigger_name’>;
drop trigger <trigger_name>;
46
-- create a main table and its shadow table
CREATE TABLE parts (pnum NUMBER(4), pname
VARCHAR2(15));
CREATE TABLE parts_log (pnum NUMBER(4), pname
VARCHAR2(15));
CREATE TRIGGER parts_trig BEFORE INSERT ON parts
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN INSERT INTO parts_log VALUES(:new.pnum,
:new.pname);
COMMIT;
47 END;
CREATE TABLE emp_audit ( empno NUMBER(6),
up_date DATE, new_sal NUMBER(8,2), old_sal
NUMBER(8,2),user1 varchar2(50));
CREATE OR REPLACE TRIGGER audit_sal AFTER
UPDATE OF sal ON emp FOR EACH ROW BEGIN
INSERT INTO emp_audit VALUES( :old.empno,
SYSDATE, :new.sal, :old.sal,user );
END;
/
48
ALTER TRIGGER trigger name ENABLE
ALTER TRIGGER trigger name DISABLE
ALTER TRIGGER trigger name RENAME
TO new_name
49
CREATE TABLE empauditlog ( audit_date DATE, audit_user
;VARCHAR2(20), audit_desc VARCHAR2(20) )
CREATE OR REPLACE TRIGGER emp_audit_trig AFTER INSERT
OR UPDATE OR DELETE ON empauditlog
DECLARE
;v_action VARCHAR2(20)
BEGIN
;'IF INSERTING THEN v_action := 'Added employee(s)
;'ELSIF UPDATING THEN v_action := 'Updated employee(s)
;'ELSIF DELETING THEN v_action := 'Deleted employee(s)
;END IF
50
;dbms_output.put_line(v_action );END
create table queries_audit ( query_id integer not null,
audit_time date not null, query_sql varchar(4000))
create or replace trigger queries_audit_sql
before update on queries_audit for each row
when (old.query_sql is not null and
(new.query_sql is null or old.query_sql <>
new.query_sql))
begin insert into queries_audit (query_id,
audit_time, query_sql) values (:old.query_id,
;sysdate, :old.query_sql); end
51
DECLARE
CURSOR dept_cur IS
SELECT deptno
FROM dept
;ORDER BY deptno
Employee cursor all employees for a dept number --
CURSOR emp_cur (v_dept_no DEPT.DEPTNO%TYPE) IS
SELECT ename
FROM emp
;WHERE deptno = v_dept_no
BEGIN
FOR dept_rec IN dept_cur LOOP
;dbms_output.put_line('Employees in Department '||TO_CHAR(dept_rec.deptno))
FOR emp_rec in emp_cur(dept_rec.deptno) LOOP
;dbms_output.put_line('...Employee is '||emp_rec.ename)
;END LOOP
;END LOOP
;END
/
52