You are on page 1of 52

Practice 1: Working with Procedure Builder

This practice gives you exposure to the Procedure Builders capability of


creating client-side and server-side procedures, accessing server-side
objects, and resolving basic compile errors.
Assumptions
You have Procedure Builder installed on your workstation.
You have access to a schema on an Oracle database.
You have the privileges needed to create procedures.
You have an EMP table.
Instructions
1. 1. Create a procedure called MY_LOOP in Procedure Builder that will print
the numbers 1 through to 10; each on its own line. Run the procedure in
the PL/SQL Interpreter.

Open Procedure Builder.


Click Program Units in the Object Navigator.
Click the + button on the side toolbar (or File > New >
Procedure on the top menu).
Then enter the following code in the Program Unit Editor.
PROCEDURE my_loop IS
BEGIN
FOR i IN 1..10 LOOP
TEXT_IO.PUT_LINE(i);
END LOOP;
END;

Compile the Procedure by clicking the Compile button.


Navigate to the PL/SQL Interpreter dialog window.
Enter my_loop;

2. Using Procedure Builder, add a new department to the DEPT table. Verify
the addition and then ROLLBACK;

Open Procedure Builder.


Connect to the database by either clicking File > Connect, or
double-clicking Database Objects in the Object Navigator.
Log in.
In the PL/SQL Interpreter, enter:

PL/SQL> INSERT INTO DEPT


+> (deptno, dname, loc)
+> VALUES
+> (50,'EDUCATION','MINNEAPOLIS');
Verify the change by typing
PL/SQL> SELECT * FROM dept;
DEPTNO DNAME
LOC
------ -------------- ------------10 ACCOUNTING
NEW YORK
20 RESEARCH
DALLAS
30 SALES
CHICAGO
40 OPERATIONS
BOSTON
50 EDUCATION
MINNEAPOLIS

Then enter ROLLBACK to undo the addition.

3. Implement (copy) the My_loop procedure on the server

Assuming you still have Procedure Builder running and are still
logged on, Expand (click +) the Database Objects node in the
Object Navigator.
Find your schema and expand that node.
Notice the Stored Program Units node.
Click the MY_LOOP procedure in the Program Units node and
drag it to the Stored Program Units node.
Delete the client side MY_LOOP procedure.
Notice the * beside your remaining server-side MY_LOOP. It
did not recompile successfully on the server!
Open the Stored Program Unit editor (by double-clicking on the
icon. Notice the error.
Change TEXT_IO to DBMS_OUTPUT and recompile.

This completes the practice.

Practice 4: Creating Procedures


The practice gives you practical exposure to creating and dropping
procedures with and without parameters. S You may choose to complete this
lab in SQL*Plus or in Procedure Builder. The solutions are described in
SQL*Plus.
Assumptions
You have Procedure Builder installed on your workstations (if
you want to do the practice in Procedure Builder).
You have access to SQL*Plus (if you want to do the practice in
SQL*Plus).
You have access to a schema on an Oracle database.
You have the privileges needed to create procedures.
You have EMP and PRODUCT tables.
Instructions
2. 1. Create a Procedure that display Hello World on the screen. Execute
it and then delete the procedure.
Open SQL*Plus and log in to your practice schema.
SQL>
SQL>
2
3
4
5
6

SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE hello_world
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello World!');
END;
/

Procedure created.
SQL> EXEC hello_world;
Hello World!
PL/SQL procedure successfully completed.
SQL> DROP PROCEDURE hello_world;
Procedure dropped.
3. 2. Create a procedure called ADD_PROD to insert a new product into the
PRODUCT table. The procedure receives a product number and a product
description. If a product with that product number exists, display a
meaningful message on the screen and then generate a server error (hint:
Use RAISE_APPLICATION_ERROR to generate the server error).

Test your new procedure with the following statements.


SQL> EXEC ADD_PROD(1,'Picture Frames')
PL/SQL procedure successfully completed.
SQL> EXEC ADD_PROD(100860,'Picture Frames')
Product 100860 already exists!
BEGIN ADD_PROD(100860,'Picture Frames'); END;
*
ERROR at line 1:
ORA-20001: Invalid Product ID
ORA-06512: at "OLN.ADD_PROD", line 12
ORA-06512: at line 1
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

CREATE OR REPLACE PROCEDURE add_prod


(i_prodid IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE)
IS
BEGIN
INSERT INTO product (prodid, descrip)
VALUES
(i_prodid, i_descrip);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE
('Product ' || i_prodid ||
' already exists!');
RAISE_APPLICATION_ERROR
(-20001,'Invalid Product ID');
END add_prod;
/

Procedure created.
4. 3. Create a procedure called DEL_PROD to delete a product from the
PRODUCT table given a product IDif the product does not exist. Display a
message and then generate a server error.
Test your new procedure with the following statements.
SQL> EXEC del_prod(1);
PL/SQL procedure successfully completed.
SQL> EXEC del_prod(1);
1 does not exist!
BEGIN del_prod(1); END;

*
ERROR at line
ORA-20203: No
ORA-06512: at
ORA-06512: at

1:
products deleted.
"OLN.DEL_PROD", line 9
line 1

CREATE OR REPLACE PROCEDURE del_prod


(i_prodid IN product.prodid%TYPE)
IS
BEGIN
DELETE FROM product
WHERE prodid = i_prodid;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE
(i_prodid || ' does not exist!');
RAISE_APPLICATION_ERROR
(-20203,'No products deleted.');
END IF;
END DEL_PROD;
/
Procedure created.
5. 4. Create a procedure called QUERY_EMP to query the EMP table. The
employee number is passed in and the job title is returned. A salary
accumulator bind variable is passed in. The salary of the particular
employee is added to it and the new amount is passed out.
If the employee does not exist, display an error message and then
generate a server error.
Test your new procedure with the following statements.
SQL> VARIABLE job VARCHAR2(100)
SQL> VARIABLE sal NUMBER
SQL> EXEC :sal := 0
PL/SQL procedure successfully completed.
SQL> EXEC query_emp(7698,:sal,:job)
PL/SQL procedure successfully completed.
SQL> PRINT job
JOB
--------------------------------------------------MANAGER

SQL> PRINT sal


SAL
---------2850
SQL> EXEC query_emp(7369,:sal,:job)
PL/SQL procedure successfully completed.
SQL> PRINT job
JOB
--------------------------------------------------CLERK
SQL> PRINT sal
SAL
---------3650
SQL> EXEC query_emp(9999,:sal,:job)
Employee 9999 does not exist!
BEGIN query_emp(9999,:sal,:job); END;
*
ERROR at line 1:
ORA-20001: Bad employee number
ORA-06512: at "OLN.QUERY_EMP", line 18
ORA-06512: at line 1
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

CREATE OR REPLACE PROCEDURE query_emp


(i_empno IN
emp.empno%TYPE,
io_sal IN OUT emp.sal%TYPE,
o_job
OUT
emp.job%TYPE)
IS
v_sal
emp.sal%TYPE;
BEGIN
SELECT sal, job
INTO
V_sal, o_job
FROM
emp
WHERE
empno = i_empno;
io_sal := v_sal + io_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE
('Employee ' || i_empno ||
' does not exist!');

18
19
20
21

RAISE_APPLICATION_ERROR
(-20001,'Bad employee number');
END query_emp;
/

Procedure created.
This completes the practice.
Oracle Corporation, 2002

Practice 6: Creating Functions


In this practice, you create and drop server-side functions. You use these
functions as part of PL/SQL statements and SQL statements.
The solutions aredescribed in SQL*Plus.
Assumptions
You have Procedure Builder installed on your workstation (if
you want to do the practice in Procedure Builder).
You have access to SQL*Plus (if you want to do the practice in
SQL*Plus).
You have access to a schema on an Oracle database.
You have the privileges needed to create procedures and
functions.
You have EMP, DEPT, and PRODUCT tables.
Instructions
6. 1. Create a function called Q_PROD that, given a product ID, returns the
product description. If the product ID is invalid, the function will return
Invalid Product Number. Test your function with the following PL/SQL and
SQL statements.
SQL> VAR description VARCHAR2(100)
SQL> EXEC :description := q_prod(100860)
PL/SQL procedure successfully completed.
SQL> PRINT description
DESCRIPTION
------------------------------------------ACE TENNIS RACKET I

SQL> EXEC :description := q_prod(0)


PL/SQL procedure successfully completed.
SQL> PRINT description
DESCRIPTION
------------------------------------------Invalid Product Number
SQL>
2
3
4

SELECT ordid, q_prod(prodid) as descrip, qty


FROM
item
WHERE ordid = 610
4 /

ORDID
---------610
610
610
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

DESCRIP
QTY
------------------------------ ---------ACE TENNIS RACKET I
1
ACE TENNIS BALLS-3 PACK
3
ACE TENNIS NET
1

CREATE OR REPLACE FUNCTION q_prod


(v_prodid IN product.prodid%TYPE)
RETURN VARCHAR2
IS
v_descrip product.descrip%TYPE;
BEGIN
SELECT descrip
INTO
v_descrip
FROM
product
WHERE
prodid = v_prodid;
RETURN (v_descrip);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Invalid Product Number';
END q_prod;
/

Function created.
7. 2. Create a function called get_sal that returns the salary of an
employee given the employee number. If no employee number is given, it
will return the summation of the salaries from the EMP table. If an invalid
employee number was given, it will return NULL.
Test your function with the following statements.
SQL> VAR TOTAL_SAL NUMBER

SQL> EXECUTE :total_sal := get_sal(7839)


PL/SQL procedure successfully completed.
SQL> PRINT TOTAL_SAL
TOTAL_SAL
---------5000
SQL> EXECUTE :total_sal := get_sal;
PL/SQL procedure successfully completed.
SQL> PRINT TOTAL_SAL
TOTAL_SAL
---------29025
SQL> EXECUTE :total_sal := get_sal(9999)
PL/SQL procedure successfully completed.
SQL> PRINT TOTAL_SAL
TOTAL_SAL
---------SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

CREATE OR REPLACE FUNCTION get_sal


(i_empno
emp.empno%TYPE
DEFAULT NULL)
RETURN NUMBER
IS
v_total_sal
NUMBER;
BEGIN
IF i_empno IS NULL THEN
SELECT SUM(SAL)
INTO
v_total_sal
FROM
emp;
ELSE
SELECT sal
INTO
v_total_sal
FROM
emp
WHERE empno = i_empno;
END IF;
RETURN v_total_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;

21
22

END get_sal;
/

Function created.

This completes the practice.


Oracle Corporation, 2002

Practice 8: Create Packages


In this practice, you create and drop package specifications and bodies on the
server. You gain experience with PRIVATE and PUBLIC program units within
the package. The solutions are described in SQL*Plus.
Assumptions

You have Procedure Builder installed on your workstation (if you


wish to do the practice in Procedure Builder).
You have access to SQL*Plus (if you wish to do the practice in
SQL*Plus).
You have access to a schema on an Oracle database.
You have the privileges needed to create packages.
You understand procedures and functions.
You have EMP, DEPT, and PRODUCT tables.

Instructions
8. 1. You have created several stand-alone procedures and a function, and
you are now convinced that you would achieve better performance if you
put them into a package. Create a package (PROD_PACK) that has the
ADD_PROD, UPD_PROD, DEL_PROD procedures and the Q_PROD function.
Make the Q_PROD function rivate and, for now, declare that function first
within the body. (You get a compile error if you dont.)
Test your package with the following statements.
SQL> SET SERVEROUTPUT ON
SQL> EXEC prod_pack.add_prod(1,'Orange Slice');
PL/SQL procedure successfully completed.

SQL> EXEC prod_pack.upd_prod(1,'Pepsi One');


1 was Orange Slice
1 updated to Pepsi One
PL/SQL procedure successfully completed.
SQL> EXEC prod_pack.del_prod(1);
PL/SQL procedure successfully completed.
SQL> EXEC prod_pack.del_prod(1);
1 does not exist!
BEGIN prod_pack.del_prod(1); END;
*
ERROR at line
ORA-20203: No
ORA-06512: at
ORA-06512: at

1:
products deleted.
"OLN.PROD_PACK", line 67
line 1

The code for these units follows.


PROCEDURE add_prod
(i_prodid IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE)
IS
BEGIN
INSERT INTO product (prodid, descrip)
VALUES
(i_prodid, i_descrip);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE
('Product ' || i_prodid ||
' already exists!');
RAISE_APPLICATION_ERROR
(-20001,'Invalid Product ID');
END add_prod;
PROCEDURE upd_prod
(i_prodid
IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE
(i_prodid || ' was ' || q_prod(i_prodid));
UPDATE product
SET
descrip = i_descrip
WHERE prodid = i_prodid;
IF SQL%NOTFOUND THEN

DBMS_OUTPUT.PUT_LINE
('Product ' || i_prodid ||
' already exists!');
RAISE_APPLICATION_ERROR
(-20202,'No products updated.');
ELSE
DBMS_OUTPUT.PUT_LINE
(i_prodid || ' updated to ' || q_prod(i_prodid));
END IF;
END upd_prod;
PROCEDURE del_prod
(i_prodid IN product.prodid%TYPE)
IS
BEGIN
DELETE FROM product
WHERE prodid = i_prodid;
IF SQL%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE
(i_prodid || ' does not exist!');
RAISE_APPLICATION_ERROR
(-20203,'No products deleted.');
END IF;
END DEL_PROD;
FUNCTION q_prod
(v_prodid IN product.prodid%TYPE)
RETURN VARCHAR2
IS
v_descrip product.descrip%TYPE;
BEGIN
SELECT descrip
INTO
v_descrip
FROM
product
WHERE
prodid = v_prodid;
RETURN (v_descrip);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Invalid Product Number';
END q_prod;

SQL>
2
3
4
5
6

CREATE OR REPLACE PACKAGE prod_pack


IS
PROCEDURE add_prod
(i_prodid IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE);
PROCEDURE upd_prod

7
8
9
10
11
12

(i_prodid
IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE);
PROCEDURE del_prod
(i_prodid IN product.prodid%TYPE);
END prod_pack;
/

Package created.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

CREATE OR REPLACE PACKAGE BODY prod_pack


IS
-FUNCTION q_prod
(v_prodid IN product.prodid%TYPE)
RETURN VARCHAR2
IS
v_descrip product.descrip%TYPE;
BEGIN
SELECT descrip
INTO
v_descrip
FROM
product
WHERE
prodid = v_prodid;
RETURN (v_descrip);
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Invalid Product Number';
END q_prod;
-PROCEDURE add_prod
(i_prodid IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE)
IS
BEGIN
INSERT INTO product (prodid, descrip)
VALUES
(i_prodid, i_descrip);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE
('Product ' || i_prodid ||
' already exists!');
RAISE_APPLICATION_ERROR
(-20001,'Invalid Product ID');
END add_prod;
-PROCEDURE upd_prod
(i_prodid
IN product.prodid%TYPE,
i_descrip IN product.descrip%TYPE)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE

42
(i_prodid || ' was ' || q_prod(i_prodid));
43
UPDATE product
44
SET
descrip = i_descrip
45
WHERE prodid = i_prodid;
46
IF SQL%NOTFOUND THEN
47
DBMS_OUTPUT.PUT_LINE
48
('Product ' || i_prodid ||
49
' already exists!');
50
RAISE_APPLICATION_ERROR
51
(-20202,'No products updated.');
52
ELSE
53
DBMS_OUTPUT.PUT_LINE
54
(i_prodid || ' updated to ' ||
q_prod(i_prodid));
55
END IF;
56 END upd_prod;
57 -58 PROCEDURE del_prod
59
(i_prodid IN product.prodid%TYPE)
60 IS
61 BEGIN
62
DELETE FROM product
63
WHERE prodid = i_prodid;
64
IF SQL%NOTFOUND THEN
65
DBMS_OUTPUT.PUT_LINE
66
(i_prodid || ' does not exist!');
67
RAISE_APPLICATION_ERROR
68
(-20203,'No products deleted.');
69
END IF;
70 END DEL_PROD;
71 -72 END prod_pack;
73 /
Package body created.
Create a package called EMP_PACK. For now, the package contains one
public function called get_dept_sal. The function, given a department
number, will return the total salaries of the employees (EMP table) in that
department. Test your packaged function with the following statement.
SQL> VARIABLE DEPT_SAL NUMBER
SQL> EXECUTE :dept_sal := emp_pack.get_dept_sal(10)
PL/SQL procedure successfully completed.
SQL> PRINT dept_sal
DEPT_SAL

---------8750
SQL>
2
3
4
5
6
7

CREATE OR REPLACE PACKAGE emp_pack


IS
FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
END emp_pack;
/

Package created.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

CREATE OR REPLACE PACKAGE BODY emp_pack


IS
FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp
WHERE deptno = i_deptno;
RETURN v_dept_sal;
END get_dept_sal;
END emp_pack;
/

Package body created.


9. 2. You need to add two functions to your EMP_PACK package.
VALID_DEPTNO validates the department number. Given a department
number, if that number exists in the DEPT table, it returns True, otherwise,
it returns False.
CHK_MGR ensures that, given a manager number and an employee
number, both employees work for the same department and the manager
is actually a manager The SQL code you need follows. The function
returns True if valid and False if not.
For example, Jones (7566) is a manager and is in the same department as
Scott (7788)
SQL> SELECT 'TRUE' AS "?"
2 FROM
emp
3 WHERE empno = 7566 -- MANAGER

4
5
6
7
8

AND
AND

job
= 'MANAGER'
deptno = (SELECT deptno
FROM
emp
WHERE empno = 7788) -- EMPLOYEE

?
---TRUE
Test your package with the following block.
SQL> BEGIN
2
IF emp_pack.VALID_DEPTNO(10) THEN
3
DBMS_OUTPUT.PUT_LINE('10 IS GOOD!');
4
END IF;
5
IF emp_pack.VALID_DEPTNO(99) = FALSE THEN
6
DBMS_OUTPUT.PUT_LINE('99 IS NO GOOD!');
7
END IF;
8
IF emp_pack.chk_mgr(7566,7788) THEN
9
DBMS_OUTPUT.PUT_LINE('7566,7788 IS GOOD!');
10
END IF;
11
IF emp_pack.chk_mgr(7788,7566) = FALSE THEN
12
DBMS_OUTPUT.PUT_LINE('7788,7566 IS NO
GOOD!');
13
END IF;
14 END;
15 /
10 IS GOOD!
99 IS NO GOOD!
7566,7788 IS GOOD!
7788,7566 IS NO GOOD!
PL/SQL procedure successfully completed.
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15

CREATE OR REPLACE PACKAGE emp_pack


IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN;

16
17
18

-END emp_pack;
/

Package created.
CREATE OR REPLACE PACKAGE BODY emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp
WHERE deptno = i_deptno;
RETURN v_dept_sal;
END get_dept_sal;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'
INTO
v_result
FROM
dept
WHERE deptno = i_deptno;
-- IF YOU MADE IT THIS FAR, THE DEPTNO EXISTS!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- DEPTNO DOESN'T
EXIST!
RETURN FALSE;
WHEN OTHERS THEN
RETURN NULL;
END valid_deptno;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'

INTO
FROM
WHERE
AND
AND

v_result
emp
empno = i_mgr
job
= 'MANAGER'
deptno = (SELECT deptno
FROM
emp
WHERE empno = i_empno);
-- if you made it this far, the combo is good!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
WHEN OTHERS
THEN
RETURN NULL;
END chk_mgr;
END emp_pack;
/

10. 3. It is now time to finalize your package. You need one more procedure
added to your package that will insert new employees. The procedure
NEW_EMP:
a) a) Only requires an employee number and a name. The rest could be
defaults. These defaults are
Job
DEFAULT 'SALESMAN'
Mgr
DEFAULT 7839
Sal
DEFAULT 1000
Comm
DEFAULT 0
Deptno
DEFAULT 30
Hiredate can be assumed to be midnight, today. (Hint: This can be
built programmatically into your insert statement)
b) b) Only allows the insert to take place if the department number is
valid (VALID_DEPTNO).
c) c) After the insert, determines whether the Manager and Employee
number is a valid combination. If the combination is bad, rollback to a
point before the insert. (Hint: SAVEPOINT)
d) d) Currently, the order of your program units is important so ensure
that NEW_EMP is the last unit defined in your package body.
Test your newly improved package with the following statements.
SQL> EXEC emp_pack.new_emp(1,'GRANT')
PL/SQL procedure successfully completed.

SQL> EXEC emp_pack.new_emp(2,'ROBERT',i_deptno =>


99)
BEGIN emp_pack.new_emp(2,'ROBERT',i_deptno => 99);
END;
*
ERROR at line 1:
ORA-20205: Invalid department number. Try again.
ORA-06512: at "OLN.EMP_PACK", line 76
ORA-06512: at line 1
SQL> EXEC emp_pack.new_emp(3,'SPENCER',i_deptno =>
10)
BEGIN emp_pack.new_emp(3,'SPENCER',i_deptno => 10);
END;
*
ERROR at line 1:
ORA-20206: Invalid Manager for this employee!
ORA-06512: at "OLN.EMP_PACK", line 83
ORA-06512: at line 1
SQL> SELECT empno, ename, job FROM emp where empno =
1;
EMPNO
ENAME
JOB
---------- ---------- --------1 GRANT
SALESMAN
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

CREATE OR REPLACE PACKAGE emp_pack


IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN;
-PROCEDURE new_emp
(i_empno
emp.empno%TYPE,

19
i_ename
20
i_job
'SALESMAN',
21
i_mgr
22
i_sal
23
i_comm
24
i_deptno
25
26 END emp_pack;
27 /

emp.ename%TYPE,
emp.job%TYPE

DEFAULT

emp.mgr%TYPE
emp.sal%TYPE
emp.comm%TYPE
emp.deptno%TYPE

DEFAULT
DEFAULT
DEFAULT
DEFAULT

7698,
1000,
0,
30);

Package created.
SQL>
SQL> CREATE OR REPLACE PACKAGE BODY emp_pack
2 IS
3 -4
FUNCTION get_dept_sal
5
(i_deptno
emp.deptno%TYPE)
6
RETURN NUMBER
7
IS
8
v_dept_sal
emp.sal%TYPE;
9
BEGIN
10
SELECT SUM(sal)
11
INTO
v_dept_sal
12
FROM
emp
13
WHERE deptno = i_deptno;
14
RETURN v_dept_sal;
15
END get_dept_sal;
16 -17
FUNCTION valid_deptno
18
(i_deptno
emp.deptno%TYPE)
19
RETURN BOOLEAN
20
IS
21
v_result
VARCHAR2(1);
22
BEGIN
23
SELECT 'x'
24
INTO
v_result
25
FROM
dept
26
WHERE deptno = i_deptno;
27
-- IF YOU MADE IT THIS FAR, THE DEPTNO
EXISTS!
28
RETURN TRUE;
29
EXCEPTION
30
WHEN NO_DATA_FOUND THEN -- DEPTNO DOESN'T
EXIST!
31
RETURN FALSE;
32
WHEN OTHERS THEN
33
RETURN NULL;
34
END valid_deptno;

35 -36
FUNCTION chk_mgr
37
(i_mgr
emp.mgr%TYPE,
38
i_empno
emp.empno%TYPE)
39
RETURN BOOLEAN
40
IS
41
v_result
VARCHAR2(1);
42
BEGIN
43
SELECT 'x'
44
INTO
v_result
45
FROM
emp
46
WHERE empno = i_mgr
47
AND
job
= 'MANAGER'
48
AND
deptno = (SELECT deptno
49
FROM
emp
50
WHERE empno = i_empno);
51
-- if you made it this far, the combo is
good!
52
RETURN TRUE;
53
EXCEPTION
54
WHEN NO_DATA_FOUND THEN
55
RETURN FALSE;
56
WHEN OTHERS
THEN
57
RETURN NULL;
58
END chk_mgr;
59
60
PROCEDURE new_emp
61
(i_empno
emp.empno%TYPE,
62
i_ename
emp.ename%TYPE,
63
i_job
emp.job%TYPE
DEFAULT
'SALESMAN',
64
i_mgr
emp.mgr%TYPE
DEFAULT 7698,
65
i_sal
emp.sal%TYPE
DEFAULT 1000,
66
i_comm
emp.comm%TYPE
DEFAULT 0,
67
i_deptno emp.deptno%TYPE DEFAULT 30)
68
IS
69
BEGIN
70
IF valid_deptno(i_deptno) THEN
71
SAVEPOINT before_insert;
72
INSERT INTO emp
73
VALUES (i_empno, i_ename, i_job, i_mgr,
74
TRUNC (SYSDATE), i_sal, i_comm,
i_deptno);
75
ELSE
76
RAISE_APPLICATION_ERROR (-20205,
77
'Invalid department number. Try
again.');
78
END IF;
79
IF chk_mgr(I_mgr,I_empno) = TRUE THEN
80
NULL;

81
82
83
84
85
86
87
88
89
90

ELSE
ROLLBACK TO SAVEPOINT before_insert;
RAISE_APPLICATION_ERROR (-20206,
'Invalid Manager for this employee!');
END IF;
END new_emp;
END emp_pack;
/

Package body created.


This completes the practice.

Oracle Corporation, 2002

Practice 9: More Package Concepts


In this practice, you modify the EMP_PACK package that you created in the
previous practice; implementing the topics covered in this module. The
solutions are described in SQL*Plus.
Assumptions

You have Procedure Builder installed on your workstation (if


they want to do the practice in Procedure Builder).
You have access to SQL*Plus (if you want to do the practice in
SQL*Plus).
You have access to a schema on an Oracle database.
You have the privileges needed to create packages.
You understand procedures and functions.
You have EMP and DEPT tables.
You have an EMP_PACK package from the last practice
(although the code is available in this practice).

Instructions
11. 1. It is now time to enhance the EMP_PACK package that you created in
the previous practice. (If you have lost it, the code is given at the end of
this question). You decided that you would like the function
get_dept_sal to return the total salary of the whole table if it is not
passed a department number. Although you could use defaults for this,
you decide you would like to overload the function.

Test the package with the following statements.


SQL> VAR SAL NUMBER
SQL> EXEC :SAL := emp_pack.get_dept_sal(10)
PL/SQL procedure successfully completed.
SQL> PRINT SAL
SAL
---------8750
SQL> EXEC :SAL := emp_pack.get_dept_sal
PL/SQL procedure successfully completed.
SQL> PRINT SAL
SAL
---------30025
Here is the original specification and body of EMP_PACK.
CREATE OR REPLACE PACKAGE emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN;
-PROCEDURE new_emp
(i_empno
emp.empno%TYPE,
i_ename
emp.ename%TYPE,
i_job
emp.job%TYPE
DEFAULT
i_mgr
emp.mgr%TYPE
DEFAULT
i_sal
emp.sal%TYPE
DEFAULT
i_comm
emp.comm%TYPE
DEFAULT
i_deptno emp.deptno%TYPE DEFAULT
END emp_pack;

'SALESMAN',
7698,
1000,
0,
30);

/
CREATE OR REPLACE PACKAGE BODY emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp
WHERE deptno = i_deptno;
RETURN v_dept_sal;
END get_dept_sal;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'
INTO
v_result
FROM
dept
WHERE deptno = i_deptno;
-- IF YOU MADE IT THIS FAR, THE DEPTNO EXISTS!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- DEPTNO DOESN'T EXIST!
RETURN FALSE;
WHEN OTHERS THEN
RETURN NULL;
END valid_deptno;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'
INTO
v_result
FROM
emp
WHERE empno = i_mgr
AND
job
= 'MANAGER'
AND
deptno = (SELECT deptno
FROM
emp

WHERE empno = i_empno);


-- if you made it this far, the combo is good!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
WHEN OTHERS
THEN
RETURN NULL;
END chk_mgr;
PROCEDURE new_emp
(i_empno
emp.empno%TYPE,
i_ename
emp.ename%TYPE,
i_job
emp.job%TYPE
DEFAULT 'SALESMAN',
i_mgr
emp.mgr%TYPE
DEFAULT 7698,
i_sal
emp.sal%TYPE
DEFAULT 1000,
i_comm
emp.comm%TYPE
DEFAULT 0,
i_deptno emp.deptno%TYPE DEFAULT 30)
IS
BEGIN
IF valid_deptno(i_deptno) THEN
SAVEPOINT before_insert;
INSERT INTO emp
VALUES (i_empno, i_ename, i_job, i_mgr,
TRUNC (SYSDATE), i_sal, i_comm, i_deptno);
ELSE
RAISE_APPLICATION_ERROR (-20205,
'Invalid department number. Try again.');
END IF;
IF chk_mgr(I_mgr,I_empno) = TRUE THEN
NULL;
ELSE
ROLLBACK TO SAVEPOINT before_insert;
RAISE_APPLICATION_ERROR (-20206,
Invalid Manager for this employee!);
END IF;
END new_emp;
END emp_pack;
/
CREATE OR REPLACE PACKAGE emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION get_dept_sal
RETURN NUMBER;

-Rest of package is unchanged


CREATE OR REPLACE PACKAGE BODY emp_pack
IS
FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp
WHERE deptno = i_deptno;
RETURN v_dept_sal;
END get_dept_sal;
-FUNCTION get_dept_sal
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp;
RETURN v_dept_sal;
END get_dept_sal;
Rest of package body is unchanged
12. 2. You decide that, as VALID_DEPTNO and CHK_MGR both return
BOOLEAN and would not be used outside the package anyway, you want to
make these members private. However, you are aware that, once private,
the order in which your members appear within the body is very significant.
You decide that you will use forward declarations so that you can place the
private members anywhere within the package.
Make VALID_DEPTNO and CHK_MGR private and add the forward
declarations to your package body.
Enter the following code to ensure everything is still working correctly.
SQL> EXEC emp_pack.new_emp(2,'SPENCER')
PL/SQL procedure successfully completed.
SQL> EXEC emp_pack.new_emp(2,'SPENCER', i_deptno => 99)
BEGIN emp_pack.new_emp(2,'SPENCER', i_deptno => 99);
END;

*
ERROR at line 1:
ORA-20205: Invalid department number. Try again.
ORA-06512: at "OLN.EMP_PACK", line 97
ORA-06512: at line 1
SQL> EXEC emp_pack.new_emp(3,'ROBERT', i_deptno => 20)
BEGIN emp_pack.new_emp(3,'ROBERT', i_deptno => 20);
END;
*
ERROR at line 1:
ORA-20206: Invalid Manager for this employee!
ORA-06512: at "OLN.EMP_PACK", line 104
ORA-06512: at line 1
CREATE OR REPLACE PACKAGE emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION get_dept_sal
RETURN NUMBER;
-PROCEDURE new_emp
(i_empno
emp.empno%TYPE,
i_ename
emp.ename%TYPE,
i_job
emp.job%TYPE
i_mgr
emp.mgr%TYPE
i_sal
emp.sal%TYPE
i_comm
emp.comm%TYPE
i_deptno emp.deptno%TYPE

DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT

END emp_pack;
/
CREATE OR REPLACE PACKAGE BODY emp_pack
IS
--FORWARD DECLARATIONS-------------FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,
i_empno
emp.empno%TYPE)
RETURN BOOLEAN;

'SALESMAN',
7698,
1000,
0,
30);

--FORWARD DECLARATIONS END----------Rest of package is unchanged

13. 3. You want to add a public function to your EMP_PACK package to


implement the following business rule. A department number and job
combination is valid if that combination currently exists in the EMP table.
The function CHECK_DEPT_JOB receives a department number and job
and, if that combination exists, returns TRUE, otherwise it returns FALSE.
This processing promises to be rather heavy and, in most cases,
repetitive, so you decide to add a one-time only procedure to your
package to load a PL/SQL Index-by table and have this function access
that instead.
Test your function with the following block.
SQL> BEGIN
2
IF emp_pack.check_dept_job('CLERK',20) THEN
3
DBMS_OUTPUT.PUT_LINE('VALID');
4
END IF;
5
IF emp_pack.check_dept_job('CLERK',40) = FALSE
THEN
6
DBMS_OUTPUT.PUT_LINE('INVALID');
7
END IF;
8 END;
9 /
VALID
INVALID
PL/SQL procedure successfully completed.
(Hint: You will need to add four things to your package.
An explicit cursor that would list out the department number
and job combinations.
A PL/SQL Index-by table that will store the ROWS returned by
the cursor.
A one-time only procedure that will load the PL/SQL Index-by
table.
The function CHECK_DEPT_JOB that will go through the
PL/SQL Index-by table looking for matches.)
(Hint 2: In the solutions, the cursor and the PL/SQL table are private.
When you are trying this on your own, make them public and then, when
everything works, make them private)

Here is the final state of the package after all these questions.
CREATE OR REPLACE PACKAGE emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
-FUNCTION get_dept_sal
RETURN NUMBER;
-PROCEDURE new_emp
(i_empno
emp.empno%TYPE,
i_ename
emp.ename%TYPE,
i_job
emp.job%TYPE
i_mgr
emp.mgr%TYPE
i_sal
emp.sal%TYPE
i_comm
emp.comm%TYPE
i_deptno emp.deptno%TYPE

DEFAULT
DEFAULT
DEFAULT
DEFAULT
DEFAULT

'SALESMAN',
7698,
1000,
0,
30);

FUNCTION check_dept_job
(i_job
emp.job%TYPE,
i_deptno emp.deptno%TYPE)
RETURN BOOLEAN;
END emp_pack;
/
CREATE OR REPLACE PACKAGE BODY emp_pack
IS
-- CURSOR and INDEX-BY table used in One-Time only Proc
CURSOR dept_job_c IS
SELECT DISTINCT deptno, job
FROM
emp;
TYPE dept_job_table_type IS TABLE OF dept_job_c%ROWTYPE
INDEX BY BINARY_INTEGER;
dept_job_table
dept_job_table_type;
counter
PLS_INTEGER := 0;
--FORWARD DECLARATIONS-------------FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN;
-FUNCTION chk_mgr
(i_mgr
emp.mgr%TYPE,

i_empno
emp.empno%TYPE)
RETURN BOOLEAN;
--FORWARD DECLARATIONS END---------FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp
WHERE deptno = i_deptno;
RETURN v_dept_sal;
END get_dept_sal;
-FUNCTION get_dept_sal
RETURN NUMBER
IS
v_dept_sal
emp.sal%TYPE;
BEGIN
SELECT SUM(sal)
INTO
v_dept_sal
FROM
emp;
RETURN v_dept_sal;
END get_dept_sal;
-FUNCTION valid_deptno
(i_deptno
emp.deptno%TYPE)
RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'
INTO
v_result
FROM
dept
WHERE deptno = i_deptno;
-- IF YOU MADE IT THIS FAR, THE DEPTNO EXISTS!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN -- DEPTNO DOESN'T EXIST!
RETURN FALSE;
WHEN OTHERS THEN
RETURN NULL;
END valid_deptno;
-FUNCTION chk_mgr
(i_mgr
i_empno

emp.mgr%TYPE,
emp.empno%TYPE)

RETURN BOOLEAN
IS
v_result
VARCHAR2(1);
BEGIN
SELECT 'x'
INTO
v_result
FROM
emp
WHERE empno = i_mgr
AND
job
= 'MANAGER'
AND
deptno = (SELECT deptno
FROM
emp
WHERE empno = i_empno);
-- if you made it this far, the combo is good!
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
WHEN OTHERS
THEN
RETURN NULL;
END chk_mgr;
PROCEDURE new_emp
(i_empno
emp.empno%TYPE,
i_ename
emp.ename%TYPE,
i_job
emp.job%TYPE
DEFAULT 'SALESMAN',
i_mgr
emp.mgr%TYPE
DEFAULT 7698,
i_sal
emp.sal%TYPE
DEFAULT 1000,
i_comm
emp.comm%TYPE
DEFAULT 0,
i_deptno emp.deptno%TYPE DEFAULT 30)
IS
BEGIN
IF valid_deptno(i_deptno) THEN
SAVEPOINT before_insert;
INSERT INTO emp
VALUES (i_empno, i_ename, i_job, i_mgr,
TRUNC (SYSDATE), i_sal, i_comm, i_deptno);
ELSE
RAISE_APPLICATION_ERROR (-20205,
'Invalid department number. Try again.');
END IF;
IF chk_mgr(I_mgr,I_empno) = TRUE THEN
NULL;
ELSE
ROLLBACK TO SAVEPOINT before_insert;
RAISE_APPLICATION_ERROR (-20206,
'Invalid Manager for this employee!');
END IF;
END new_emp;
FUNCTION check_dept_job

(i_job
emp.job%TYPE,
i_deptno emp.deptno%TYPE)
RETURN BOOLEAN
IS
BEGIN
FOR i IN dept_job_table.FIRST .. dept_job_table.LAST LOOP
IF dept_job_table(i).deptno = i_deptno AND
dept_job_table(i).job
= i_job
THEN
RETURN TRUE;
END IF;
END LOOP;
RETURN FALSE; -- MADE IT THIS FAR ... NEVER GOT A MATCH!
END check_dept_job;
--- Start of one time only procedure to load table.
BEGIN
FOR dept_job_c_rec IN dept_job_c LOOP
counter := counter + 1;
dept_job_table(counter) := dept_job_c_rec;
END LOOP;
END emp_pack;
/
14. 4. Finally, you have decided that you would like to ensure the purity of the
get_dept_sal function inside your emp_pack. You want to ensure that,
at compile time, the compiler checks to ensure that this function does not
access a database or a packaged variable.
(Hint: PRAGMA )
CREATE OR REPLACE PACKAGE emp_pack
IS
-FUNCTION get_dept_sal
(i_deptno
emp.deptno%TYPE)
RETURN NUMBER;
PRAGMA
RESTRICT_REFERENCES(get_dept_sal,WNDS,WNPS,RNPS);
-FUNCTION get_dept_sal
RETURN NUMBER;
PRAGMA
RESTRICT_REFERENCES(get_dept_sal,WNDS,WNPS,RNPS);
-Rest of package specification unchanged

This completes the practice.

Oracle Corporation, 2002

Practice 1: Oracle Supplied Packages


In this practice you create user-defined procedure to drop a table, use the EXECUTE
IMMEDIATE statement, and use the DBMS_JOB package. After completing these practices,
you should be able to describe the use and application of some Oracle Server supplied
packages like DBMS_OUTPUT, DBMS_JOB and DBMS_DDL.

Instructions
Creating a Procedure
1) Create a procedure DROP_TABLE that drops the table specified in the input parameter. Use
the procedures and functions from the supplied DBMS_SQL package.
Test the DROP_TABLE procedure by creating a new table called EMP_DUP as a copy of the
EMP table, then executing the DROP_TABLE procedure to drop the EMP_DUP table.
SQL> CREATE OR REPLACE PROCEDURE drop_table
2
3

(v_table_name IN VARCHAR2)
IS

dyn_cur number;

dyn_err varchar2(255);

BEGIN

dyn_cur := DBMS_SQL.OPEN_CURSOR;

DBMS_SQL.PARSE(dyn_cur,'drop table '||

v_table_name,DBMS_SQL.NATIVE);

10

DBMS_SQL.CLOSE_CURSOR(dyn_cur);

11
12

EXCEPTION
WHEN OTHERS THEN dyn_err := sqlerrm;

13

DBMS_SQL.CLOSE_CURSOR(dyn_cur);

14

RAISE_APPLICATION_ERROR(-20600,dyn_err);

15

END drop_table;

16

Procedure created.
SQL> CREATE TABLE emp_dup
2

AS SELECT * FROM emp;

Table created.
SQL> EXECUTE drop_table('emp_dup')
PL/SQL procedure successfully completed.
SQL> SELECT * FROM emp_dup;
*
ERROR at line 1:
ORA-00942: table or view does not exist

Using the EXECUTE IMMEDIATE statement.


2) Create another procedure DROP_TABLE2 that drops the table specified in the input
parameter. Use the EXECUTE IMMEDIATE statement. Test the DROP_TABLE procedure by
creating a new table called EMP_DUP as a copy of the EMP table, then executing the
DROP_TABLE procedure to drop the EMP_DUP table.
SQL> CREATE OR REPLACE PROCEDURE DROP_TABLE2
2

(v_table_name

IS

BEGIN

5
6

IN

VARCHAR2)

EXECUTE IMMEDIATE 'DROP TABLE '||v_table_name;


END;

7/
Procedure created.
SQL> CREATE TABLE emp_dup
2

AS SELECT * FROM emp;

Table created.
SQL> EXECUTE drop_table2('emp_dup')
PL/SQL procedure successfully completed.
SQL> SELECT * FROM emp_dup;
*
ERROR at line 1:

ORA-00942: table or view does not exist

Using the package DBMS_JOB


Recreate the EMP_DUP table and schedule DROP_TABLE2 to run in five minutes time using
DBMS_JOB.
Note: a five minute interval is defined using the following code. SYSDATE +
(5.0/24.0)/60.0
(What is the increment of SYSDATE?)
Confirm the job has been scheduled using USER_JOBS.
SQL> CREATE TABLE emp_dup
2

AS SELECT * FROM emp;

Table created.
SQL> VARIABLE JOBNO NUMBER
SQL> EXECUTE DBMS_JOB.SUBMIT(:JOBNO, 'DROP_TABLE2(''EMP_DUP'');', SYSDATE + (5.0/24.0)/60.0)
PL/SQL procedure successfully completed.
SQL> PRINT JOBNO
JOBNO
-------------1

SQL> SELECT JOB, NEXT_DATE, NEXT_SEC, WHAT


2

FROM USER_JOBS

JOB

NEXT_DATE

NEXT_SEC

---

----------

-------- ---------------------

30-SEP-2002

05:32:46

WHAT

DROP_TABLE2(EMP_DUP);

Oracle Corporation, 2002

Practice 4: Database Triggers


In this practice you will learn how to create a trigger which involkes a stored procedure.
Instructions
Create a stored procedure which is invoked by a trigger.
1) The business hours for our store are 8:00 a.m. to 10:00 p.m. Sunday through Friday, and
8:00 a.m. to 12:00 midnight on Saturday. To ensure that the ord table can only be modified
during these hours, create a stored procedure that is called by a trigger for the ord table.
a) Create a stored procedure called TIME_CHECK which checks the current
time against the business hour times. If the current time is not within the
business hours, use the RAISE_APPLICATION_ERROR procedure to give an
appropriate error message.
SQL> CREATE OR REPLACE PROCEDURE time_check
2

IS

BEGIN

IF

AND '22:00' AND TO_CHAR (SYSDATE, 'DY') IN ('SUN','MON',

'TUE','WED', 'THU', 'FRI') )

OR (TO_CHAR (SYSDATE,'HH24:MI') NOT BETWEEN '08:00' AND

'24:00'AND TO_CHAR (SYSDATE, 'DY') = 'SAT' )

THEN

10

RAISE_APPLICATION_ERROR (-20205,'You may only make

11

(TO_CHAR (SYSDATE, 'HH24:MI') NOT BETWEEN '08:00'

changes during normal

12

office hours.');

13

END IF;

14 END time_check;
15 /
Procedure created.

b) Create a trigger called secure_ord on the ord table. Fire the trigger
before data is inserted, updated, and deleted. Call your TIME_CHECK
procedure from this trigger.
SQL> CREATE OR REPLACE TRIGGER secure_ord
2

BEFORE INSERT OR UPDATE OR DELETE ON ord

BEGIN

time_check;

END secure_ord;

6 /
Trigger created.

c) Test your trigger.


SQL> insert into ord
2

values (999, sysdate, 'A', 100,sysdate +7,1000);

insert into ord values (999,sysdate,'A',100, sysdate + 7, 1000);


*
ERROR at line 1:
ORA-20205: You may only make changes during normal office hours.
ORA-06512: ORAxx.TIME_CHECK, line 18
ORA-06512: ORAxx.SECURE_ORD, line 1
ORA-04088: error during execution of trigger ORAxx. SECURE_ORD
Note: ORAxx is your username.
Oracle Corporation, 2002

Practice 7: Implementation of Database Triggers


In these practices, you will learn how to enforce data integrity within the server, protect data
integrity with a trigger, enforce referential integrity within the server and compute derived
data within the server.

Instructions
Data integrity and referential integrity:
1. Create a trigger to ensure that once an order has been shipped, it can never be
changed.
a) Create a trigger ITEM_ORDER_SHIPPED. The trigger should be fired
before data is inserted, updated or deleted in a row in the ITEM table. When
a row is changed in the ITEM table, check to see if that row exists in the ORD
table. If the SHIPDATE column has a value in the ORD table, fail the trigger.
Add exception handling and give appropriate message if the trigger fails.
SQL> CREATE OR REPLACE TRIGGER item_order_shipped
2

BEFORE INSERT OR UPDATE OR DELETE ON item

FOR EACH ROW

DECLARE

v_ordid

number;

v_shipdate

date;

BEGIN

SELECT ordid, shipdate

INTO v_ordid, v_shipdate

10

FROM ord

11

WHERE ordid = NVL(:NEW.ordid, :OLD.ordid);

12

IF v_shipdate IS NOT NULL THEN

13

RAISE_APPLICATION_ERROR(-20500,

14

'Corresponding order has already been shipped in

15

the ORD table, no changes allowed.');

16

END IF;

17

EXCEPTION

18

WHEN NO_DATA_FOUND THEN

19

RAISE_APPLICATION_ERROR(-20600,'Order does not

20

exist');

END;

21 /
Trigger created.

b) Create a trigger called ORD_ORDER_SHIPPED. Fire the trigger before


every row that is changed due to updates and deletes in the ORD table. Use
the WHEN clause to fire the trigger when the SHIPDATE column has a value.
Raise an appropriate exception with an appropriate message informing the
user that the order was already shipped.
SQL> CREATE OR REPLACE TRIGGER ord_order_shipped
2

BEFORE UPDATE OR DELETE ON ord

FOR EACH ROW

WHEN (OLD.shipdate IS NOT NULL)

BEGIN

RAISE_APPLICATION_ERROR(-20550,' Corresponding

order has already been shipped,no changes

allowed.');

9
10

END;
/

Trigger created.

Test the trigger. You can use the following data before and after creating the
triggers.
SQL> UPDATE item
2

SET qty = 20

WHERE ordid = 620

AND prodid = 100860;

UPDATE item
*
ERROR at line 1:
ORA-20500: Corresponding order has already been shipped,no changes
allowed.
ORA-06512: at : ORAxx.ITEM_ORDER_SHIPPED, line 19
ORA-04088: error during execution of trigger
ORAxx.ITEM_ORDER_SHIPPED

Note: ORAxx is your username.


SQL> UPDATE ord
2

SET shipdate = sysdate

WHERE ordid = 620;

UPDATE ord
*
ERROR at line 1:
ORA-20550: Corresponding order has already been shipped,no changes
allowed.
ORA-06512: at : ORAxx.ORD_ORDER_SHIPPED, line 2
ORA-04088: error during execution of trigger
ORAxx.ORD_ORDER_SHIPPED
Drop these triggers before proceeding to the next exercise.
SQL> DROP TRIGGER item_order_shipped;
Trigger dropped.
SQL> DROP TRIGGER item_order_shipped;
Trigger dropped.

Compute derived data:


2) Create a trigger that updates the TOTAL column in the ORD table after DML on the item
table.
a) The trigger is written on the ITEM table. After a row is added to the ITEM
table, the amount from the ITEMTOT in the ITEM table is added to the
amount in the TOTAL column of the ORD table. After a row is updated in the
ITEM table, the adjusted amount in the ITEMTOT column in the ITEM table is
added/subtracted to/from the amount in the TOTAL column of the ORD table. If
a row is deleted from the ITEM table, the amount in the ITEMTOT column in
the ITEM table is subtracted from the amount in the TOTAL column of the
ORD table. Write a procedure to perform the necessary DML on the ORD
table. Call the procedure with appropriate parameters from the trigger.
SQL> CREATE OR REPLACE TRIGGER ord_total
2

BEFORE INSERT OR UPDATE OF itemtot OR DELETE ON item

FOR EACH ROW

BEGIN

IF INSERTING THEN

upd_ord(:NEW.ordid, :NEW.itemtot);

ELSIF UPDATING THEN

upd_ord(:NEW.ordid, :NEW.itemtot - :OLD.itemtot);

ELSE - means DELETING

10

upd_ord(:OLD.ordid, -:OLD.itemtot);

11
12

END IF;
END;

13

Trigger created.

SQL> CREATE OR REPLACE PROCEDURE upd_ord


2
3

(p_ordid IN NUMBER, p_itemtot IN NUMBER)


IS

BEGIN

UPDATE ord

SET total = NVL(total, 0) + NVL(p_itemtot, 0)

WHERE ordid = p_ordid;

END;

Procedure created.

b) Test the trigger. You can use the following data before and after creating
the triggers.
SQL> SELECT *
2

FROM item

WHERE ordid = 621;

ORDID

ITEMID

PRODID ACTUALPRICE

QTY

ITEMTOT

------- ------- -------- ----------- ------ --------621

100861

45

10

450

621

100870

2.8

100

280

SQL> SELECT *
2

FROM ord

WHERE ordid = 621;


ORDID ORDERDATE

-------- ----------

CUSTID SHIPDATE

TOTAL

- --------- ------------ -------

621 15-MAR-1987 A

100 1-JAN-1987

730

SQL> INSERT INTO item


2

VALUES(621, 3, 100871, 5.6, 100, 560);

1 row created.

SQL> SELECT *
2

FROM item

WHERE ordid = 621;

ORDID

ITEMID

PRODID ACTUALPRICE

QTY

ITEMTOT

------- ------- -------- ----------- ------ --------621

100861

45

10

450

621

100870

2.8

100

280

621

100871

5.6

100

560

SQL> SELECT *
2

FROM ord

WHERE ordid = 621;


ORDID ORDERDATE

-------- ----------

CUSTID SHIPDATE

TOTAL

- --------- ------------ -------

621 15-MAR-1987 A

100 1-JAN-1987

1290

Note: Drop the trigger before proceeding to the next exercise.


SQL> DROP TRIGGER ord_total;
Trigger dropped.

Oracle Corporation, 2002

Practice 9: Managing Stored Objects Using the Data


Dictionary or Procedure Builder
In this practice, you will learn how to manage stored objects using the data dictionary
tables and views.
Instructions

1. In a previous practice (Topic 4), you created a procedure which checks


the current time against the business hour times. The business hours
for our store have changed. The new business hours are 8:00 a.m. to
10:00 p.m. Sunday through Thursday, and 8:00 a.m. to 12:00 midnight
on Friday and Saturday. To ensure that the ord table can only be
modified during these hours, you created a trigger on the ord table. The
trigger is fired before data is inserted, updated, and deleted.
a) You cannot find the original script that created
the trigger or the procedure. List the body of the
secure_ord trigger in order to see the name of
the procedure. Copy the body of the procedure
and create a new script in order to enforce the new
business hours.
SQL> SELECT trigger_body
2

FROM user_triggers

WHERE trigger_name = 'SECURE_ORD';


TRIGGER_BODY
----------------------------------------------------BEGIN
time_check;
END secure_ord;
SQL> SELECT text
2

FROM user_source

WHERE name = 'TIME_CHECK'


4

ORDER BY line;

TEXT
-----------------------------------------------------------------PROCEDURE time_check

IS
BEGIN
IF (TO_CHAR (SYSDATE, 'HH24:MI') NOT BETWEEN '08:00' AND
'22:00'
'DY') IN ('SUN', 'MON', 'TUE',
'WED', 'THU', 'FRI'))
OR (TO_CHAR (SYSDATE, 'HH24:MI') NOT BETWEEN '08:00' AND
'24:00'
AND TO_CHAR (SYSDATE, 'DY') = 'SAT' )
THEN
RAISE_APPLICATION_ERROR (-20205,
'You may only make changes during normal office hours.');
END IF;
END time_check;
15 rows selected
SQL> CREATE OR REPLACE PROCEDURE time_check
2

IS

BEGIN
4

IF (TO_CHAR(SYSDATE,'HH24:MI') NOT BETWEEN '08:00' AND

'22:00'

AND TO_CHAR(SYSDATE,'DY') IN ('SUN','MON','TUE',

'WED', 'THU'))

OR (TO_CHAR(SYSDATE,'HH24:MI') NOT BETWEEN '08:00' AND

'24:00'

10

AND TO_CHAR(SYSDATE, 'DY') IN ('FRI', 'SAT'))

THEN

11

RAISE_APPLICATION_ERROR(-20205, 'You may only make

12

changes during normal office hours.');

13

END IF;

14 END time_check;
15 /
Procedure created.

Note: when you work with Procedure Builder you need not to query the data
dictionary for the text of the procedure and the body of the trigger. The trigger
is stored by the table, the procedure youll find in the section Stored Program
Units. You could simply copy the text of the procedure via the menu.
After you have connected to the Procedure Builder with the
username/password and connectstring provided to you, select Database
Objects, select your username, then Stored Program Units. Activate the
program TIME_CHECK. You now could copy the text of the procedure by
using the menu; Program, choose Stored Program Unit Editor. Copy this, go
to SQL*Plus, add the words Create or Replace before the text and save it
as a file. In Procedure Builder you cant save text files.

b) Test your trigger.

Note: In order for your trigger to fail, you may need to change
the time to be outside the range of your current time while
performing this exercise.
SQL> insert into ord
2

values (999, sysdate, 'A', 100,sysdate + 7, 1000);

insert into ord


*
error at line 1:
ORA-20205: You may only make changes during normal office hours.
ORA-06512: at ORAxx.TIME_CHECK, line 9
ORA-06512: at ORAxx.SECURE_ORD, line 1
ORA-04088: error during execution of trigger ORAxx.SECURE_ORD

Note: ORAxx is your username.

Oracle Corporation, 2002

Practice 11: Managing Dependencies


In practice, you will compile your stored program units, track dependencies between stored
program units and check their status.
Instructions
1. In another practice (Practice 9), you recompiled a procedure to enforce business hour time
for our store. The recompilation makes the dependent trigger INVALID.
a) Recompile the procedure TIME_CHECK.
SQL> ALTER PROCEDURE time_check COMPILE;
Procedure altered.
b) Find all dependent objects and their types on this procedure.
SQL> SELECT name, type
2 FROM user_dependencies
3 WHERE referenced_name = 'TIME_CHECK';
NAME

TYPE

---------------------

--------------------

SECURE_ORD

TRIGGER

c) Check to see if these dependent objects are INVALID.


SQL> SELECT object_name, object_type, status
2 FROM user_objects;
OBJECT_NAME
OBJECT_TYPE

STATUS

---------------

-----------------

--------

BONUS

TABLE

VALID

CUSTID

SEQUENCE

VALID

27 rows selected.
d) Create a procedure to dynamically recompile all dependent objects on this procedure. Use
the procedure name as an input parameter. Make use of the ALTER_COMPILE procedure in
the DBMS_DDL package. Execute the procedure you just created.
SQL> CREATE OR REPLACE PROCEDURE compile_obj

(p_obj_name IN VARCHAR2)

IS

CURSOR obj_cur IS

SELECT name, type

FROM user_dependencies

WHERE referenced_name = UPPER(p_obj_name);

BEGIN

FOR obj_rec in obj_cur LOOP

10

DBMS_DDL.ALTER_COMPILE(obj_rec.type, user, obj_rec.name);

11

END LOOP;

12

END;

13

Procedure created.
SQL> EXECUTE compile_obj('TIME_CHECK')
PL/SQL procedure successfully completed.
e) Check to see if these dependent objects are INVALID.
SQL> SELECT object_name, object_type, status
2 FROM user_objects;
OBJECT_NAME

OBJECT_TYPE

STATUS

---------------

-----------------

--------

BONUS

TABLE

VALID

COMPILE_OBJ

PROCEDURE

VALID

CUSTID

SEQUENCE

VALID

28 rows selected.

Oracle Corporation, 2002