http://blog.csdn.net/robinson_0612/article/details/6084475
包,是一个逻辑集合,是由PL/SQL类型以及PL/SQL子程序的集合。PL/SQL类型包括table类型,record类型。PL/SQL项则包括游标,游标
变量。PL/SQL子程序包括过程,函数等。可以说包可谓是包罗万象。是所有PL/SQL相关资源的汇总。
包的使用可以简化应用程序设计,实现信息掩藏,子程序重载等功能。
包的优点
1.模块化:将函数,子程序全部融合在一起,使得成为一个有机的整体,封装了相关的结构。
2.易于维护:整合了子程序,更易于维护。
3.简化应用程序设计:包的声明与包体内容相分离。
4.隐藏信息:私有对象不可访问,所有的包体内代码可以实现隐藏。
5.节省I/O:一次编译,多次使用。
一、包的组成与创建语法
包头:用于定义包的公共组件,如函数头,过程头,游标等以及常量,变量等。包头中定义的公共组件可以在包内引用,也可以被其
它子程序引用。
包体:用于定义包头中定义过的过程和函数。可以单独定义私有组件,包括变量,常量,过程和函数等。私有组件只能在包内使用,而
不能被其它子程序所调用。
一言以蔽之,包头定义包的声明及描述部分,而包体则定义了对应包的具体执行部分。
创建包的语法:
CREATE [OR REPLACE] PACKAGE package_name --定义包头
{AS|IS}
public_variable_declarations |
public_type_declarations |
public_exception_declarations |
public_cursor_declarations |
function_declarations |
procedure_specifications
END [package_name]
CREATE [OR REPLACE] PACKAGE BODY package_name --定义包体,包体中的package_name应当与包头中的package_name相同
{AS|IS}
private_variable_declarations |
private_type_declarations |
private_exception_declarations |
private_cursor_declarations |
function_declarations |
procedure_specifications
END [package_name]
二、创建包
下面演示包的创建,基于用户scott创建,存储过程,函数等依赖于其下的对象
1.创建包头
CREATE OR REPLACE PACKAGE emp_package IS --创建包头,包的名字为emp_package
g_deptno NUMBER(3) := 30; --定义一个公共变量g_deptno
PROCEDURE add_employee(eno NUMBER, name VARCHAR2, salary NUMBER, dno NUMBER DEFAULT g_deptno); --声明过程
PROCEDURE fire_employee(eno NUMBER); --声明过程
FUNCTION get_sal(eno NUMBER) RETURN NUMBER; --声明函数
END emp_package;
/
2.创建包体
CREATE OR REPLACE PACKAGE BODY emp_package IS --创建包体,注意,包体中包的名字必须与包头的名字相一致
FUNCTION validate_deptno(v_deptno NUMBER) RETURN BOOLEAN --创建一个私有函数,注,此私有函数不能该包外子程序调用
IS
v_temp INT;
BEGIN
SELECT 1 INTO v_temp FROM dept WHERE deptno = v_deptno;
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
PROCEDURE add_employee --创建添加雇员的过程
(eno NUMBER, name VARCHAR2, salary NUMBER, dno NUMBER DEFAULT g_deptno) IS
BEGIN
IF validate_deptno(dno) THEN --该过程调用了包内的一个函数validate_deptno来验证dno的有效性
INSERT INTO emp(empno, ename, sal, deptno) VALUES(eno, name, salary, dno);
ELSE
RAISE_APPLICATION_ERROR(-20000, '不存在该部门');
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
RAISE_APPLICATION_ERROR(-20011, '该雇员已存在');
END;
PROCEDURE fire_employee(eno NUMBER) IS --创建解除雇员的过程
BEGIN
DELETE FROM emp WHERE empno = eno;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20012, '该雇员不存在');
END IF;
END;
FUNCTION get_sal(eno NUMBER) RETURN NUMBER IS --创建函数get_sal返回雇员的薪水
v_sal emp.sal%TYPE;
BEGIN
SELECT sal INTO v_sal FROM emp WHERE empno = eno;
RETURN v_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20012, '该雇员不存在');
END;
END emp_package;
/
3.创建仅包含包头的包(仅包含包头的包也可以被调用,具体参照后面的包的调用)
CREATE OR REPLACE PACKAGE global_int
IS
g_positive CONSTANT NUMBER:=10;
g_negative CONSTANT NUMBER:=-10;
END global_int;
三、包的调用
对于包的私有对象只能在包内调用。如上面的例子中对包内私有函数validate_deptno进行了直接调用
对于包的公共对象,既可以在包内调用,也可以由其他应用程序调用。使用其他应用程序调用时的方法:包名.包对象
1.调用包的公共变量
scott@ORCL> exec emp_package.g_deptno:=10;
2.调用包的公共过程
scott@ORCL> exec emp_package.add_employee(2222,'Robinson',3000); --此调用未指定部门号,则使用缺省值,但前面执行了
--exec emp_package.g_deptno:=10;故部门号变为
scott@ORCL> exec emp_package.add_employee(3333,'Jackson',4000,20);
scott@ORCL> select * from emp where empno in(2222,3333);
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
2222 Robinson 3000 10
3333 Jackson 4000 20
3.调用包的公共函数
scott@ORCL> var sal number
scott@ORCL> exec :sal:=emp_package.get_sal(7788);
PL/SQL procedure successfully completed.
scott@ORCL> print sal
SAL
----------
310
4.以不同用户身份调用包.需要使用schema名字来调用,即:用户名.包名.包对象名
scott@ORCL> conn lion/lion --注意帐户需要具有执行所调用包的权限
lion@ORCL> exec scott.emp_package.fire_employee(2222);
5.调用远程数据库包的公共对象。调用方法:包名.包对象名@数据库链接名
sys@ASMDB> create database link orcl
2 connect to lion identified by lion
3 using 'orcl';
Database link created.
sys@ASMDB> exec scott.emp_package.add_employee@orcl(4444,'Richard',4000);
BEGIN scott.emp_package.add_employee@orcl(4444,'Richard',4000); END;
*
ERROR at line 1:
ORA-06550: line 1, column 7: --注意远程调用时,对于缺省的参数不适用,需要明确指定参数
PLS-00424: RPC defaults cannot include Package State
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
sys@ASMDB> exec scott.emp_package.add_employee@orcl(4444,'Richard',4000,20); --下面的调用被成功执行
PL/SQL procedure successfully completed.
6.无包体包的调用(使用前面创建的包global_int)
scott@ORCL> BEGIN
2 DBMS_OUTPUT.PUT_LINE('Result is : '||2*global_int.g_positive); --使用包DBMS_OUTPUT来调用
3 END;
4 /
Result is : 20
PL/SQL procedure successfully completed.
scott@ORCL> CREATE OR REPLACE FUNCTION f_negative(m number) --将包嵌入到函数之中
2 RETURN NUMBER
3 IS
4 BEGIN
5 RETURN(m*global_int.g_negative);
6 END f_negative;
7 /
Function created.
scott@ORCL> EXEC DBMS_OUTPUT.PUT_LINE(f_negative(2));
-20
PL/SQL procedure successfully completed.
四、包的管理
1.查看包
scott@ORCL> select line,text from user_source --查看包头
2 where name='EMP_PACKAGE' and type='PACKAGE';
LINE TEXT
---------- ------------------------------------------------------------
1 PACKAGE emp_package IS
2 g_deptno NUMBER(3) := 30;
3 PROCEDURE add_employee(eno NUMBER, name VARCHAR2, salary N
UMBER, dno NUMBER DEFAULT g_deptno);
4 PROCEDURE fire_employee(eno NUMBER);
5 FUNCTION get_sal(eno NUMBER) RETURN NUMBER;
6 END emp_package;
scott@ORCL> select line,text from user_source --查看包体
2 where name='EMP_PACKAGE' and type='PACKAGE BODY';
LINE TEXT
---------- --------------------------------------------------------------------------------
1 PACKAGE BODY emp_package IS
2 FUNCTION validate_deptno(v_deptno NUMBER) RETURN BOOLEAN IS
3 v_temp INT;
4 BEGIN
5 SELECT 1 INTO v_temp FROM dept WHERE deptno = v_deptno;
6 RETURN TRUE;
......................
2.查看包的参数
scott@ORCL> desc emp_package;
PROCEDURE ADD_EMPLOYEE
Argument Name Type In/Out Default?
------------------------------ ----------------------- ------ --------
ENO NUMBER IN
NAME VARCHAR2 IN
SALARY NUMBER IN
DNO NUMBER IN DEFAULT
PROCEDURE FIRE_EMPLOYEE
Argument Name Type In/Out Default?
------------------------------ ----------------------- ------ --------
ENO NUMBER IN
FUNCTION GET_SAL RETURNS NUMBER
Argument Name Type In/Out Default?
------------------------------ ----------------------- ------ --------
ENO NUMBER IN
3.包的删除
DROP PACKAGE package_name --同时删除包体和包头
scott@ORCL> DROP PACKAGE global_int;
删除包体,保留包头
DROP PACKAGE BODY package_name --删除包体
scott@ORCL> drop package body emp_package;
Package body dropped.
4.包的编译
重新编译包规范和包体:alter package...compile
重新编译包规范:alter package...compile specification
重新编译包体:alter package...compile body
五、总结
创键包体之前应该先创建包头
包头应当仅仅包含那些希望作为公共对象的部分
包头的声明应包含尽可能少的结构信息
任意包头的变更,需要重新编译该包内的子程序
在包头内定义的任意公共对象可以被任意内部或外部子程序调用
包体内的私有对象仅仅能被该包体内的子程序调用
包的重载功能类似于C++中函数的重载功能,即拥有多个同名的子程序,每个同名子程序使用不同的参数。用户可以传递不同的参数来调
用同名但参数不同的子程序,此即为包的重载功能。简言之,不管传递什么样的参数,所完成的任务是相同的。假定需要查询部门所在的位置
,输入参数部门编号或部门名称都会返回同样的结果。对外部程序而言,似乎是调用的同一个子程序,但其始质调用了不同的子程序,执行了
不同的代码。
有关包的创建与管理请参考:PL/SQL --> 包的创建与管理
一、使用重载特性建立包头
在包中,具有重载特性的子程序必须使用不同的输入参数。同名函数返回值数据类型必须完全相同。
以下情况不能实现重载
a.如果两个子程序的参数仅在名称和类型上不同, 这两个程序不能重载。
PROCEDURE overloadproc(o_parameter IN NUMBER);
PROCEDURE overloadproc(o_parameter OUT NUMBER);
IN ,OUT 为参数类型,NUMBER 为数据类型. 两个过程仅在类型上不同时不能重载。
b.函数使用不同的返回类型时不能进行重载
FUNCTION overloadfunc(f_parameter NUMBER) RETURN DATE;
FUNCTION overloadfunc(f_parameter VARCHAR2) RETURN NUMBER;
c.重载子程序的参数的类族必须不同,如由于NUMBER 和INTEGER 属性同一类族,所以不能实现重载。
PROCEDURE overloadproc(o_parameter NUMBER);
PROCEDURE overloadproc(o_parameter INTEGER);
--下面使用重载特性建立包头,包含了重载函数get_sal,以及重载过程fire_employee
CREATE OR REPLACE PACKAGE overload IS
FUNCTION get_sal(eno NUMBER) RETURN NUMBER;
FUNCTION get_sal(name VARCHAR2) RETURN NUMBER;
PROCEDURE fire_employee(eno NUMBER);
PROCEDURE fire_employee(name VARCHAR2);
END;
二、创建重载特性的包体
对于包中具有重载特性的函数或过程,需要依次对其创建不同的包体,即使用不同的执行代码。
对前面创建的包头,我们对其创建如下包体
通过调用get_sal函数来返回雇员的薪水,可以使用雇员编号或雇员名字作为参数
通过调用fire_employee来解雇雇员,可以使用雇员编号或雇员名字作为参数
CREATE OR REPLACE PACKAGE BODY overload IS
FUNCTION get_sal(eno NUMBER) RETURN NUMBER IS
v_sal emp.sal%TYPE;
BEGIN
SELECT sal INTO v_sal FROM emp WHERE empno = eno;
RETURN v_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20020, 'The Employee is not exist !');
END;
FUNCTION get_sal(name VARCHAR2) RETURN NUMBER IS
v_sal emp.sal%TYPE;
BEGIN
SELECT sal INTO v_sal FROM emp WHERE upper(ename) = upper(name);
RETURN v_sal;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20020, 'The Employee is not exist !');
END;
PROCEDURE fire_employee(eno NUMBER) IS
BEGIN
DELETE FROM emp WHERE empno = eno;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20020, 'The Employee is not exist !');
END IF;
END;
PROCEDURE fire_employee(name VARCHAR2) IS
BEGIN
DELETE FROM emp WHERE UPPER(ename) = UPPER(name);
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20020, 'The Employee is not exist !');
END IF;
END;
END;
三、重载子程序的调用
在对使用了重载特性的子程序进行调用时,PL/SQL会自动根据所提供的参数寻找同名且参数相符的子程序来执行其代码
scott@ORCL> var sal_1 number;
scott@ORCL> var sal_2 number;
scott@ORCL> exec :sal_1:=overload.get_sal('king');
scott@ORCL> exec :sal_2:=overload.get_sal(7788);
scott@ORCL> print sal_1 sal_2;
SAL_1
----------
5800
SAL_2
----------
3900
四、包的初始化
包的初始化,也称之为包的构造过程。即当包被首次使用时,会自动执行其构造过程,并且该构造过程在同一会话内仅仅被执行一次。
对于包的初始化,其通常的办法是包体的末尾增加一段匿名SQL代码。如下
CREATE OR REPLACE PACKAGE BODY package_name
IS
PROCEDURE procedure_name
····
FUNCTION function_name
····
BEGIN
Initialization_code;-- 要运行的初始化代码
END
--下面首先声明包头
CREATE OR REPLACE PACKAGE emp_package IS
minsal NUMBER(6, 2); --定义公共变量minsal,用于存放雇员最低薪水
maxsal NUMBER(6, 2); --定义公共变量maxsal,用于存放雇员最高薪水
PROCEDURE add_employee(eno NUMBER, name VARCHAR2, salary NUMBER, dno NUMBER);
PROCEDURE upd_sal(eno NUMBER, salary NUMBER); --对upd_sal过程实现重载
PROCEDURE upd_sal(name VARCHAR2, salary NUMBER);
END;
--下面定义包体
CREATE OR REPLACE PACKAGE BODY emp_package IS
PROCEDURE add_employee(eno NUMBER, name VARCHAR2, salary NUMBER, dno NUMBER) IS
BEGIN
IF salary BETWEEN minsal AND maxsal THEN
INSERT INTO emp (empno, ename, sal, deptno) VALUES(eno, name, salary, dno);
ELSE
RAISE_APPLICATION_ERROR(-20001, 'The salary is over specified range.');
END IF;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
RAISE_APPLICATION_ERROR(-20002, 'The employee is exists.');
END;
PROCEDURE upd_sal(eno NUMBER, salary NUMBER) IS
BEGIN
IF salary BETWEEN minsal AND maxsal THEN
UPDATE emp SET sal = salary WHERE empno = eno;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20003, 'The employee is not exists.');
END IF;
ELSE
RAISE_APPLICATION_ERROR(-20001, 'The salary is over specified range.');
END IF;
END;
PROCEDURE upd_sal(name VARCHAR2, salary NUMBER) IS
BEGIN
IF salary BETWEEN minsal AND maxsal THEN
UPDATE emp SET sal = salary WHERE UPPER(ename) = UPPER(name);
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20004, 'The employee is not exists.');
END IF;
ELSE
RAISE_APPLICATION_ERROR(-20001, 'The salary is over specified range.');
END IF;
END;
BEGIN
SELECT min(sal), max(sal) INTO minsal, maxsal FROM emp; --初始化公共变量minsal, maxsal
END;
--调用
scott@ORCL> exec emp_package.add_employee(1234,'Henry',3500,20);
scott@ORCL> exec emp_package.upd_sal('Henry',3500);
scott@ORCL> exec emp_package.upd_sal('Henry',100); --当范围超出最高和最小薪水则返回错误信息,且更新失败
BEGIN emp_package.upd_sal('Henry',100); END;
*
ERROR at line 1:
ORA-20001: The salary is over specified range.
ORA-06512: at "SCOTT.EMP_PACKAGE", line 34
ORA-06512: at line 1
五、前置声明
前置声明指的是在包体内,假定过程A调用了过程B,而B在A之后定义,这样的话,将会收到错误信息。对此,我们可以不改变过程A,B的
书写顺序及其代码,而将B事先声明,此之为前置声明。如下面的例子:
--未使用前置声明时的代码
CREATE OR REPLACE PACKAGE BODY forward_pack IS
PROCEDURE award_bonus(...)
IS
BEGIN
cal_rating(...); --在此例中过程cal_rating在过程award_bonus之后定义,这样即为非法调用
END;
PROCEDURE cal_rating(...)
IS
BEGIN
...
END;
END forward_pack;
--使用前置声明后的代码
CREATE OR REPLACE PACKAGE BODY forward_pack IS
PROCEDURE cal_rating(...); --在此处增加一行用于声明过程cal_rating,仅仅列出过程名及参数信息
PROCEDURE award_bonus(...)
IS
BEGIN
cal_rating(...);
END;
PROCEDURE cal_rating(...)
IS
BEGIN
...
END;
END forward_pack;
六、函数纯度级别
Oracle函数可以在SQL语句中调用,也可以作为表达式的一部分,基于函数的一些特殊性,在包中使用SQL语句调用公共函数时,同样也存
在一些限制,其限制主要如下:
公用函数不能包含DML语句
公用函数不能读写远程包变量
对此可以使用纯度级别来现在公用函数的某些操作
定义语法
PRAGMA RESTRICT_REFERENCES(function_name,WNDS[,WNPS][,RNDS][RNPS]);
WNDS:限制函数不能修改数据库(即执行DML操作)
WNPS:限制函数不能修改包变量,即不能给包变量赋值
RNDS:限制函数不能读取数据库数据(即禁止SELECT操作)
RNPS:限制函数不能读取包变量,即不能将包变量赋值给其它变量
--下面的代码创建使用纯度即被的包头
CREATE OR REPLACE PACKAGE purity IS
minsal NUMBER(6, 2); --定义公共变量minsal
maxsal NUMBER(6, 2); --定义公共变量maxsal
FUNCTION max_sal RETURN NUMBER; --定义公共函数
FUNCTION min_sal RETURN NUMBER;
PRAGMA RESTRICT_REFERENCES(max_sal, WNPS); --指定函数所使用的纯度级别
PRAGMA RESTRICT_REFERENCES(min_sal, WNPS);
END;
--下面的代码创建使用纯度级别的包体
CREATE OR REPLACE PACKAGE BODY purity IS
FUNCTION max_sal RETURN NUMBER IS
BEGIN
SELECT max(sal) INTO maxsal FROM emp;
RETURN maxsal;
END;
FUNCTION min_sal RETURN NUMBER IS
BEGIN
SELECT min(sal) INTO minsal FROM emp;
RETURN minsal;
END;
END;
--创建包体后,收到了如下的错误信息,因为两个公共函数指定了纯度级别为WNPS,而且函数内的代码对变量进行了赋值
scott@ORCL> show errors package body purity;
Errors for PACKAGE BODY PURITY:
LINE/COL ERROR
-------- -----------------------------------------------------------------
2/1 PLS-00452: Subprogram 'MAX_SAL' violates its associated pragma
8/1 PLS-00452: Subprogram 'MIN_SAL' violates its associated pragma
--下面使用初始化包的方法来为变量赋值
CREATE OR REPLACE PACKAGE BODY purity IS
FUNCTION max_sal RETURN NUMBER IS
BEGIN
RETURN maxsal; --函数可以读取包初始化后变量的值
END;
FUNCTION min_sal RETURN NUMBER IS
BEGIN
RETURN minsal; --函数可以读取包初始化后变量的值
END;
BEGIN
SELECT min(sal), max(sal) INTO minsal, maxsal FROM emp; --对公共变量进行初始化
END;
--下面调用限定的公用函数
scott@ORCL> var minsal number;
scott@ORCL> var maxsal number;
scott@ORCL> exec :minsal:=purity.minsal;
scott@ORCL> exec :maxsal:=purity.maxsal;
scott@ORCL> print minsal maxsal;
MINSAL
----------
800
MAXSAL
----------
5800
七、包内游标一致性状态
可以在包内定义一个公共游标,该包内的所有子程序调用该游标来实现相应的功能。如何确保子程序调用游标采取顺序一致性性调用,
而不会出现获得重复的游标记录,下面给出的例子中说明了包内游标一致性状态的使用。
--创建包头,并且定义了一个公共游标,两个公共过程
CREATE OR REPLACE PACKAGE pack_cur
IS
CURSOR cur IS
SELECT empno,ename FROM emp ORDER BY empno;
PROCEDURE return1_3rows;
PROCEDURE return4_6rows;
END pack_cur;
/
--创建包体
CREATE OR REPLACE PACKAGE BODY pack_cur
IS
v_empno emp.empno%TYPE; --定义用于存储游标结果的变量
v_ename emp.ename%TYPE; --定义用于存储游标结果的变量
PROCEDURE return1_3rows IS
BEGIN
OPEN cur; --在第一个过程中打开游标
DBMS_OUTPUT.PUT_LINE('Empno Ename');
LOOP
FETCH cur INTO v_empno,v_ename;
DBMS_OUTPUT.PUT_LINE(v_empno||' '||v_ename);
EXIT WHEN cur%ROWCOUNT >= 3; --指定游标退出的条件
END LOOP;
END return1_3rows;
PROCEDURE return4_6rows IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Empno Ename');
LOOP
FETCH cur INTO v_empno,v_ename; --因为在第一个过程中游标已打开,在此可以直接从游标提取数据
DBMS_OUTPUT.PUT_LINE(v_empno||' '||v_ename);
EXIT WHEN cur%ROWCOUNT >= 6; --指定游标退出的条件
END LOOP;
CLOSE cur; --关闭游标
END return4_6rows;
END;
/
--调用示例及其结果
scott@ORCL> set serveroutput on;
scott@ORCL> exec pack_cur.return1_3rows;
Empno Ename
1234 Henry
3333 Jackson
4444 Richard
scott@ORCL> exec pack_cur.return4_6rows;
Empno Ename
7369 SMITH
7499 ALLEN
7521 WARD
八、在包内使用自定义类型
--创建包头
CREATE OR REPLACE PACKAGE cust_type IS
TYPE emp_tb_type IS TABLE OF emp%ROWTYPE --定义一个PL/SQL索引表
INDEX BY BINARY_INTEGER;
PROCEDURE read_emp_table(p_emp_table OUT emp_tb_type); --定义一个过程
END cust_type;
/
--创建包体
CREATE OR REPLACE PACKAGE BODY cust_type IS
PROCEDURE read_emp_table(p_emp_table OUT emp_tb_type) IS --定义了输出参数的类型为emp_tb_type
i BINARY_INTEGER:=0;
BEGIN
FOR emp_record IN (SELECT * FROM emp) --提取记录使用FOR循环
LOOP
p_emp_table(i):=emp_record; --将提取的记录存放到PL/SQL索引表
i:= i + 1;
END LOOP;
END read_emp_table;
END cust_type;
/
--下面使用匿名的PL/SQL块来过程来调用包
DECLARE
v_emp_table cust_type.emp_tb_type;
BEGIN
cust_type.read_emp_table(v_emp_table);
DBMS_OUTPUT.PUT_LINE('An example: '||v_emp_table(3).ename);
END;
An example: WARD