跳到主要内容

包是一组相关过程、函数、变量、常量、类型、游标和异常等PL/SQL程序设计元素的组合。包具有面向对象设计的特点,是对这些PL/SQL程序设计元素的封装。包像一个容器或一个命名空间,可以将各种逻辑相关的过程、函数、变量、常量、类型、游标和异常结合在一起。为开发人员编写大型复杂应用程序提供良好的组织单元。当包被定义好后,应用程序可以通过包来访问各种不同的功能单元,而不用担心过多零散的子程序导致程序代码的松散。包具有简化应用设计、提高应用性能、实现信息隐藏、子程序重载等特点。对包中涉及的过程、函数、变量、常量、类型、游标和异常等PL/SQL程序设计元素如无特殊约束说明,其用法和现状保持一致。

包的创建

一个PL/SQL包由如下两部分组成:包头,或称为包的规范:主要是包的一些定义信息,不包含具体的代码实现部分,也可以说包头是PL/SQL程序和其他应用程序的接口,包含子程序、变量、常量、类型、游标和异常的声明。包体:是对包规范中声明的子程序和游标的实现部分,包体的内容对于外部应用程序来说是不可见的,包体就像是一个黑匣子一样,是对包规范的实现。注意:包头中子程序和游标是需要实现部分的,如果包头中没有声明子程序或游标,则包体就不是必须的。

创建包头语法图如下:

descript

END后的package_name可以省略。

Item_list_1如上图,可以包含多种元素,也可以一个元素都不包括,即包头可以为空,不过这样在实际使用中没有意义。

item_declaration::=

descript

创建包体语法如下:

descript

declare_section定义和声明私有常量和变量,不允许外部引用,仅允许包内引用。END后的package_name可以省略。

包在第一次被引用时,数据库会为当前SESSION实例化这个包,然后会执行initialize_section部分。

注意

包的子过程不使用包的变量时,不触发包的initialize_section部分。当引用包的过程、函数,且包体初始化部分执行失败时,已执行的包有状态成员(变量、常量、游标)的修改不会进行回滚,需要用户自己来维护数据一致性。

包头声明的变量、常量、游标等,包体只允许初始化一次。如重复初始化变量,返回报错 669: Variable(变量名) redeclared。

包头声明的游标名不允许与 PROCEDURE、FUNCTION 的例程名相同。如果重名,返回报错 6114: Previous use of '变量名' conflicts with this use。

包的删除

语法图如下:

DROP PACKAGE会将包头和包体一并删除,也可以通过DROP PACKAGE BODY关键字只删除包体,此时包头依然有效。

例如:

DROP PACKAGE BODY pkg_Count;

包的引用

包在首次引用时实例化并执行initialize_section部分,包为SESSION级,即多SESSION之间是不同的包实例。

包的引用可以采用点标记的方式,例如:

CALL pkg_Count.getCount();

下面我们通过几个例子示范包的常见用法,注意需要将SQLMODE设置为’ORACLE’,如果需要输出请设置SERVEROUTPUT为ON。

SET ENVIRONMENT SQLMODE 'ORACLE';
SET SERVEROUTPUT ON;

例子1:包中变量和常量的声明

CREATE PACKAGE pkg_7_3_1 AS
companyName CONSTANT VARCHAR(100) := 'GBASE';
productName VARCHAR(100);
END;

本例子中,只有包头,并不需要包体。注意,常量不能使用变量赋初始值。然后我们创建一个过程,对包中声明的常量进行引用,包中的变量可以赋值和引用。

CREATE PROCEDURE p_7_3_1 AS
BEGIN
DBMS_OUTPUT.PUT_LINE(pkg_7_3_1.companyName);
pkg_7_3_1.productName := 'GBase 8s';
DBMS_OUTPUT.PUT_LINE(pkg_7_3_1. productName);
END;

通过如下代码进行过程的调用。

CALL PROCEDURE p_7_3_1 ();

可以看到如下结果。

GBASE
GBase 8s

例子2:包中过程和函数的用法

CREATE PACKAGE pkg_7_3_2 AS
PROCEDURE helloWorld;
FUNCTION myPlus(param1 IN INT, param2 IN INT) RETURN INT;
END;

然后我们创建包体,用于实现包头中声明的过程和函数。

CREATE PACKAGE BODY pkg_7_3_2 AS
PROCEDURE helloWorld AS
BEGIN
DBMS_OUTPUT.PUT_LINE('HelloWorld!');
END;
FUNCTION myPlus (param1 IN INT, param2 IN INT) RETURN INT AS
BEGIN
RETURN param1 + param2;
EXCEPTION
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('error');
END;
END;

我们创建一个过程,对包中声明的过程和函数进行引用。

CREATE PROCEDURE p_7_3_2 AS
BEGIN
pkg_7_3_2.helloWorld;
DBMS_OUTPUT.PUT_LINE(pkg_7_3_2.myPlus (2, 3));
END;

通过如下代码进行过程的调用。

CALL PROCEDURE p_7_3_2 ();

可以看到如下结果。

HelloWorld!
5

例子3:包的状态

CREATE PACKAGE pkg_7_3_3 AS
PROCEDURE getCount;
END;

CREATE PACKAGE BODY pkg_7_3_3 AS
FCount INTEGER;
PROCEDURE getCount AS
BEGIN
DBMS_OUTPUT.PUT_LINE('Current count is:');
DBMS_OUTPUT.PUT_LINE(FCount);
FCount:= FCount + 1;
END;
BEGIN
FCount := 1;
END;

在上面的例子中, pkg_7_3_3在包头中声明了一个名称为getCount的过程,然后包体中声明了一个类型为INTEGER的私有变量,并对包头中声明的getCount过程进行了代码实现,getCount过程实现输出私有变量FCount的值,并每次对Fcount进行+1操作。由于包是SESSION级的,因此我们可以看到在call pkg_7_3_3.getCount()多次引用时返回值会发生变化:第一次引用时输出的是1,第二次引用时输出的是2,第三次引用时输出的是3。特别注意的是此时如果另一个SESSION中引用pkg_7_3_3,输出的不是4而是1,即多SESSION之间是不同的包实例。

例子4:通过包中游标对数据进行遍历

为了演示这个例子,我们需要创建一张包含2个字段的表。

CREATE TABLE TPERSON(
FID VARCHAR(10) NOT NULL PRIMARY KEY,
FName VARCHAR(50)
)

并准备几条数据。

INSERT INTO TPERSON VALUES('1', 'first');
INSERT INTO TPERSON VALUES('2', 'second');
INSERT INTO TPERSON VALUES('3', 'third');

创建包头,包含一个返回类型是TPERSON表行记录类型的游标声明。

CREATE PACKAGE pkg_7_3_4 AS
CURSOR myc RETURN TPERSON%ROWTYPE;
END;

创建包体,可以看到包体中对游标进行了代码实现。这里需要补充说明两点:一是包的实现部分也可以写在包头中,从而不创建包体,建议的方式是将游标的代码写在包体里,将定义和实现进行分离;二是对游标的OPEN、FETCH、CLOSE等操作一般建议写在调用包游标的地方。

CREATE PACKAGE BODY pkg_7_3_4 AS
CURSOR myc RETURN TPERSON%ROWTYPE IS SELECT FID, FName FROM TPERSON;
END;

然后我们创建一个过程,在过程中,通过TYPE定义了一个记录类型TR,并声明了一个类型为TR的mt变量。在过程的实现部分,首先通过OPEN打开了游标,然后遍历取数据并赋值给mt,并通过内置包DBMS_OUTPUT的PUT方法和PUT_LINE方法进行输出。

CREATE PROCEDURE p_7_3_4 AS
TYPE TR IS RECORD(
id VARCHAR(10),
name VARCHAR(50)
);
mt TR;
BEGIN
OPEN pkg_7_3_4.myc;
FETCH pkg_7_3_4.myc INTO mt;
WHILE pkg_7_3_4.myc%FOUND LOOP
DBMS_OUTPUT.PUT(mt.id);
DBMS_OUTPUT.PUT(': ');
DBMS_OUTPUT.PUT_LINE(mt.name);
FETCH pkg_7_3_4.myc INTO mt;
END LOOP;
CLOSE pkg_7_3_4.myc;
END;

最后我们通过如下代码进行过程的调用

CALL PROCEDURE p_7_3_4 ();

可以看到如下结果

1: first
2: second
3: third