一、 实验目的

《数据库开发技术》课程之实验八

PL/SQL 的触发器与包

1. 掌握正确使用触发器的方法。

2. 掌握包的正确使用方法。

二、 实验内容和要求

1.    编写一个触发器,在 DEPT 表执行 INSERT 语句后被激发,此触发器将新部门的编号(deptno)、名称

(dname)及执行此操作的用户(USER)、当时的日期(SYSDATE)插入 N_DEPT 表{注:此表已建好,

表结构为 N_DEPT(DEPTNO NUMBER(4),DNAME VARCHAR2(10), UNAME VARCHAR2(20),

INDATE DATE)}。

Create table N_DEPT (DEPTNO NUMBER(4),DNAME VARCHAR2(10), UNAME VARCHAR2(20),

INDATE DATE);

CREATE OR REPLACE TRIGGER T_DEPT_INS

AFTER INSERT ON DEPT

FOR EACH ROW

BEGIN

INSERT INTO N_DEPT VALUES (:new.deptno, :new.dname, user, sysdate);

COMMIT;

END;

测试:

INSERT INTO DEPT VALUES(50, ‘DEVELOP’, ‘BEIJING’)

检查:

SELECT * FROM N_DEPT;

DEPTNO DNAME      UNAME                INDATE

———- ———- ——————– ————–

50 DEVELOP    SCOTT                04-6 月 -10

2.    创建触发器 CHECK_SAL,禁示对职务为 CLERK 的雇员的工资修改值超出 1000 至 2000 的范围,即

CLERK 职务员工的修改后工资值只能在 1000~2000 之间。要求测试该触发器。

步骤 1:创建和编译以下触发器:

CREATE OR REPLACE TRIGGER check_sal

BEFORE

UPDATE OF sal ON emp

FOR EACH ROW

WHEN (old.job=’CLERK’)

BEGIN

IF :new.job=’CLERK’ AND (:new.sal<1000 OR :new.sal>2000) THEN

RAISE_APPLICATION_ERROR(-20001, ‘工资修改值超出 1000 至 2000 的范围,   操作取消!’);

END IF;

END;

步骤 2:在 EMP 表中修改记录,对触发器进行测试:

UPDATE emp SET sal=800 WHERE empno=7876;

执行结果:

UPDATE emp SET sal=800 WHERE empno=7876

*

ERROR 位于第  1    行:

ORA-20001:                                                           工资修改值超出 1000 至 2000 的范围,  操作取消!

ORA-06512:       在”***.CHECK_SAL”, line 3

ORA-04088:                                          触发器  ‘***.CHECK_SAL’    执行过程中出错

3.    *编写一个管理雇员信息的包 emp_mgmt。包中有成员如下:

程序结构

类型

说明

Emp_count

init

tax_emp

Hire_emp

Fire_emp

公有变量   跟踪员工的总人数变化,插入和删除员工时要修改该变量的值

公有过程   初始化包,初始化员工人数和当前个人所得税率,建议有一个输入参数

p_tax,传入当前个人所得税率,暂定为工资的 8%。

公有函数   通过员工编号计算出员工应交个人所得税款

公有过程   通过员工编号插入员工

公有过程   通过员工编号删除员工

emp_tax_record 记录

用于游标 C_emp 的 RETURN(强类型游标)语句中

C_emp

游标

用于游标 FOR 循环中,会被过程 show_emp_tax 所使用

show_emp_tax

Exist_emp

公有过程   按工资升序输出所有雇员的应交所得税清单

私有函数   判断某个编号的员工是否存在,该函数会被 hire_emp 和 fire_emp 等过程调

sal_null

……

异常名

公有或私

有变量

工资为空值时的异常名

可加入你认为需要的各种变量,但在程序中要有变量用途的注释

步骤 1:创建包头和包体:

CREATE OR REPLACE PACKAGE emp_mgmt                     —     包头部分

IS

EMP_COUNT NUMBER(5);

TYPE emp_tax_record IS RECORD

— 员工总人数

(Empno NUMBER(4), Ename emp.ename%type, Salary NUMBER );                      —  记录定义

CURSOR c_emp RETURN emp_tax_record;                                —    游标定义

Sal_null    EXCEPTION;                                                                —    异常名定义

PROCEDURE INIT(p_tax NUMBER);                                                                 —       初始化过程

FUNCTION tax_emp(p_Empno    emp.empno%type)                  —    函数:计算员工应交个人所得税款

RETURN number;

PROCEDURE    hire_emp (P_EMPNO NUMBER,    P_ENAME VARCHAR2,    P_JOB VARCHAR2,

P_SAL NUMBER);

PROCEDURE Fire_emp(P_EMPNO NUMBER);

—     插入员工

—         删除雇员

PROCEDURE show_emp_tax;                                            –按工资升序输出所有雇员的应交所得税清单

END emp_mgmt;

执行结果:

程序包已创建。

CREATE OR REPLACE PACKAGE BODY emp_mgmt                    —       包体部分

IS

CURSOR c_Emp RETURN emp_tax_record IS

SELECT empno, ename, sal FROM    emp ORDER BY sal;

V_tax NUMBER;                                                                                  —                                                                                                   存放当前个人所得税率

——————–私有函数:判断员工是否存在—————————-

FUNCTION EXIST_EMP(P_EMPNO IN emp.empno%type)

RETURN BOOLEAN –判断雇员是否存在的私有函数

IS

V_NUM int;

BEGIN

SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO= P_EMPNO;

IF V_NUM=1 THEN

RETURN TRUE;

ELSE

RETURN FALSE;

END IF;

END    EXIST_EMP;

——————————-         初始化过程  —————————-

PROCEDURE INIT(p_tax NUMBER)

IS

BEGIN

SELECT COUNT(*) INTO EMP_COUNT FROM EMP;

V_tax:= p_tax;

DBMS_OUTPUT.PUT_LINE(‘当前个人所得税率:’||V_tax||’%’);

DBMS_OUTPUT.PUT_LINE(‘当前雇员总人数’||EMP_COUNT);

DBMS_OUTPUT.PUT_LINE(‘初始化过程已经完成!’);

END INIT;

——————公有函数:通过员工编号计算出员工应交个人所得税款—————————-

FUNCTION tax_emp (p_Empno    emp.empno%type)

RETURN    NUMBER                                                            —    定义该函数返回的数据类型,只能定义

IS                                                                                                    —       数据类型,不能定义长度

v_Sal      NUMBER;

v_ReturnValue    NUMBER;

BEGIN

IF EXIST_EMP(P_EMPNO) THEN

SELECT sal      INTO v_Sal    FROM emp    WHERE empno=p_Empno;

IF V_SAL IS NULL THEN

RAISE sal_null;

ELSE

v_ReturnValue:=v_Sal* V_tax;

RETURN    v_ReturnValue;                                          —      返回语句,确定该函数返回的值

END IF;

END IF;

Exception

when sal_null then

raise_application_error(-20001, p_empno||’号员工工资为空,无所得税!’);

when others then

raise_application_error(-20002,’程序运行过程中出现错误号为 ‘||sqlcode||’错误信息为’||sqlerrm||’的错

误!’);

END tax_emp;

——————公有过程:通过员工编号插入员工—————————-

PROCEDURE    hire_emp (P_EMPNO NUMBER,    P_ENAME VARCHAR2,    P_JOB VARCHAR2,

P_SAL NUMBER)

IS

BEGIN

IF NOT EXIST_EMP(P_EMPNO) THEN

INSERT INTO emp (EMPNO,ENAME,JOB,SAL) VALUES (P_EMPNO, P_ENAME, P_JOB, P_SAL);

COMMIT;

EMP_COUNT:= EMP_COUNT + 1;

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’已插入!’);

DBMS_OUTPUT.PUT_LINE(‘当前雇员总人数’||EMP_COUNT);

ELSE

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’已存在,不能插入!’);

END IF;

EXCEPTION

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’插入失败!’);

END hire_emp;

——————公有过程:通过员工编号删除员工—————————-

PROCEDURE Fire_emp(P_EMPNO NUMBER)

IS

BEGIN

IF EXIST_EMP(P_EMPNO) THEN

DELETE FROM EMP WHERE EMPNO=P_EMPNO;

COMMIT;

EMP_COUNT:= EMP_COUNT – 1;

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’已删除!’);

DBMS_OUTPUT.PUT_LINE(‘当前雇员总人数’||EMP_COUNT);

ELSE

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’不存在,不能删除!’);

END IF;

EXCEPTION

WHEN OTHERS THEN

DBMS_OUTPUT.PUT_LINE(‘雇员’||P_EMPNO||’删除失败!’);

END Fire_emp;

——————公有过程:按工资升序输出所有雇员的应交所得税清单—————————-

PROCEDURE show_emp_tax

IS

BEGIN

FOR emp_tax_record    IN    c_Emp    LOOP    –输出雇员的所得税

DBMS_OUTPUT.PUT_LINE(emp_tax_record.empno||’, ‘|| emp_tax_record.ename||”’ tax is ‘

||TO_CHAR(tax_emp (emp_tax_record.Empno))||’.’);

END    LOOP;

DBMS_OUTPUT.PUT_LINE(‘当前雇员总人数:’||EMP_COUNT);

END show_emp_tax;

END emp_mgmt;

执行结果:

程序包主体已创建。

步骤 2:初始化包:

SET SERVEROUTPUT ON

EXECUTE emp_mgmt.INIT(8);

执行结果:

当前个人所得税率:8%

当前雇员总人数 16

初始化过程已经完成!16

步骤 3:按工资升序输出所有雇员的应交所得税清单:

EXECUTE emp_mgmt.INIT(8);

EXECUTE emp_mgmt.show_emp_tax;

执行结果:

7369, SMITH’ tax is 7744.

7900, JAMES’ tax is 9196.

7876, ADAMS’ tax is 10648.

7521, WARD’ tax is 12100.

7654, MARTIN’ tax is 12100.

7934, MILLER’ tax is 12584.

7844, TURNER’ tax is 14520.

7499, ALLEN’ tax is 15488.

7951, OLIVEN’ tax is 20328.

7782, CLARK’ tax is 23716.

7948, MIKE’ tax is 26136.

7698, BLAKE’ tax is 27588.

7566, JONES’ tax is 28798.

7788, SCOTT’ tax is 29040.

7902, FORD’ tax is 29040.

7839, KING’ tax is 48400.

当前雇员总人数: 16

PL/SQL 过程已成功完成。

步骤 4:对员工表插入一条新记录:

EXECUTE emp_mgmt.hire_emp (8001,’小王’,’CLERK’,1000);

执行结果:

雇员 8001 已插入!

当前雇员总人数 17

PL/SQL 过程已成功完成。

步骤 5:通过全局变量 EMP_COUNT 查看雇员人数:

BEGIN

DBMS_OUTPUT.PUT_LINE(‘当前雇员总人数:’||emp_mgmt.EMP_COUNT);

END;

显示结果为:

当前雇员总人数:17

PL/SQL 过程已成功完成。

步骤 6:删除员工表新插入的记录

EXECUTE emp_mgmt.fire_emp(8001);

执行结果:

雇员 8001 删除失败!                        —   因为存在禁止删除的触发器,要执行 drop trigger emp_test;

PL/SQL 过程已成功完成。

删除触发器后,再次执行:

EXECUTE emp_mgmt.fire_emp(8001);

执行结果:

雇员 8001 已删除!

当前雇员总人数 16

PL/SQL 过程已成功完成。

步骤 7:通过员工编号计算出员工应交个人所得税款

DECLARE

V_empno emp.empno%type;

BEGIN

V_empno:=7951;

DBMS_OUTPUT.PUT_LINE(V_empno ||’     应交个人所得税款为:’|| emp_mgmt. tax_emp(V_empno));

END;

执行结果:

7951 应交个人所得税款为:20328

PL/SQL 过程已成功完成。

三、 实验报告

记录实验内容 1 和 2。记录实验过程中遇到的困难和解决问题的方法。