DM 支持 DMSQL 程序包来扩展数据库功能,用户可以通过包来创建应用程序或者使用包来管理过程和函数。
11.1 创建包
包的创建包括包规范和包主体的创建。
11.1.1 创建包规范
包规范中包含了有关包中的内容信息,但是它不包含任何过程的代码。定义一个包规范的详细语法如下。
语法格式
CREATE [OR REPLACE] PACKAGE [IF NOT EXISTS] [<模式名>.]<包名> [WITH ENCRYPTION] [<调用者权限>] AS|IS <包内声明列表> END [包名]
<调用者权限>::=AUTHID DEFINER |
AUTHID CURRENT_USER
<包内声明列表>::= <包内声明>;{<包内声明>;}
<包内声明>::= <变量列表定义>|<游标定义>|<异常定义>|<过程定义>|<函数定义>|<类型声明>
<变量列表定义>::= <变量定义>{<变量定义>}
<变量定义>::= <变量名><变量类型>[<赋值标识><表达式>];
<变量类型>::=<DMSQL程序类型> |
[<模式名>.]<表名>.<列名>%TYPE |
[<模式名>.]<表名>%ROWTYPE> |
<记录类型>
<赋值标识>::=DEFAULT |
ASSIGN |
:=
<记录类型>::= RECORD(<变量名> <DMSQL程序类型>;{<变量名> <DMSQL程序类型>;})
<游标定义>::= CURSOR <游标名> [FOR <查询语句>]
<异常定义>::= <异常名> EXCEPTION [FOR <错误号> [,<错误描述>]]
<过程定义>::= PROCEDURE <过程名> <参数列表>
<函数定义>::= FUNCTION <函数名><参数列表> RETURN <返回值数据类型>[RESULT_CACHE] [DETERMINISTIC] [PIPELINED]
<类型声明>::= TYPE <类型名称> IS <数据类型>
图例
创建包规范
使用说明
- 创建包的名称不能与系统创建的模式名称相同;
- 包部件可以以任意顺序出现,其中的对象必须在引用之前被声明;
- 过程和函数的声明都是前向声明,包规范中不包括任何实现代码;
- < 赋值标识 > 中 DEFAULT、ASSIGN 和:=均用于为变量赋值,三种赋值标识功能和用法完全一样。
权限
- 使用该语句的用户必须是 DBA 或该包对象的拥有者且具有 CREATE PACKAGE 数据库权限的用户;
- < 调用者权限 > 中用关键字 AUTHID DEFINER 或 AUTHID CURRENT_USER 指定包的调用者权限。DEFINER 为采用包定义者权限;CURRENT_USER 为当前用户权限。缺省为 DEFINER。
11.1.2 创建包主体
包主体中包含了在包规范中的前向子程序声明相应的代码。它的创建语法如下。
语法格式
CREATE [OR REPLACE] PACKAGE BODY [<模式名>.]<包名> [WITH ENCRYPTION] AS|IS <包体部分> END [包名]
<包体部分> ::= <包体声明列表> [<初始化代码>]
<包体声明列表> ::=<包体声明>;[{<包体声明>;}]
<包体声明>::=<变量定义>|<游标定义>|<异常定义>|<过程定义>|<函数定义>|<类型声明> |<存储过程实现>|<函数实现>
<变量定义> ::= <变量名列表> <数据类型> [<默认值定义>]
<游标定义> ::= CURSOR <游标名> [FOR <查询语句>]
<异常定义> ::= <异常名> EXCEPTION [FOR <错误号> [, <错误描述>]]
<过程定义> ::= PROCEDURE <过程名> <参数列表>
<函数定义> ::= FUNCTION <函数名> <参数列表> RETURN <返回值数据类型>
<类型声明> ::= TYPE <类型名称> IS <数据类型>
<存储过程实现> ::= PROCEDURE <过程名> <参数列表> AS|IS BEGIN <实现体> END [<过程名>];
<函数实现> ::= FUNCTION <函数名><参数列表> RETURN <返回值数据类型>[DETERMINISTIC] [PIPELINED]<AS|IS> BEGIN <实现体> END [<函数名>];
<初始化代码> ::= [[<说明部分>]BEGIN<执行部分>[<异常处理部分>]]
<说明部分> ::=[DECLARE]<说明定义>{<说明定义>}
<说明定义>::=<变量列表说明>|<异常变量说明>|<子游标定义>|<子过程定义>|<子函数定义>;
<变量列表说明>::= <变量初始化>{<变量初始化>}
<记录类型>::= RECORD(<变量名> <DMSQL程序类型>;{<变量名> <DMSQL程序类型>;})
<异常变量说明>::=<异常变量名> EXCEPTION [FOR <错误号> [,<错误描述>]]
<子游标定义>::=cursor <游标名> [FOR<查询表达式>|<连接表>]
<子过程定义>::=PROCEDURE<过程名>[(<参数列>)]<IS|AS><模块体>
<子函数定义>::=FUNCTION<函数名>[(<参数列>)]RETURN<返回数据类型><IS|AS><模块体>
<执行部分>::=<SQL过程语句序列>{< SQL过程语句序列>}
<SQL过程语句序列>::=[<标号说明>]<SQL过程语句>;
<标号说明>::=<<<标号名>>>
<SQL过程语句>::=<SQL语句>|<SQL控制语句>
<异常处理部分>::=EXCEPTION<异常处理语句>{<异常处理语句>}
<异常处理语句>::= WHEN <异常名> THEN <执行部分>;
图例
创建包主体
使用说明
- 包规范中定义的对象对于包主体而言都是可见的,不需要声明就可以直接引用。这些对象包括变量、游标、异常定义和类型定义;
- 包主体中不能使用未在包规范中声明的对象;
- 包主体中的过程、函数定义必须和包规范中的前向声明完全相同。包括过程的名字、参数定义列表的参数名和数据类型定义;
- 若包规范中的过程、函数声明中的参数包含默认值,则允许包主体中的过程、函数定义中的参数忽略该默认值,若未忽略则默认值必须与包规范中保持一致;若包规范中的过程、函数声明中的参数不包含默认值,则包主体中的过程、函数定义中的参数也不能包含默认值;
- 包中可以有重名的过程和函数,只要它们的参数定义列表不相同。系统会根据用户的调用情况进行重载(Overload);
- 用户在第一次访问包(如调用包内过程、函数,访问包内变量)时,系统会自动将包对象实例化。每个会话根据数据字典内的信息在本地复制包内变量的副本。如果用户定义了 package 的初始化代码,还必须执行这些代码(类似于一个没有参数的构造函数执行);
- 对于一个会话,包头中声明的对象都是可见的,只要指定包名,用户就可以访问这些对象。可以将包头内的变量理解为一个 session 内的全局变量;
- 关于包内过程、函数的调用:DM 支持按位置调用和按名调用参数两种模式。除了需要在过程、函数名前加入包名作为前缀,调用包内的过程、函数的方法和普通的过程、函数并无区别;
- 包体内声明的变量、类型、方法以及实现的未在包头内声明的方法被称作本地变量、方法。本地变量、方法只能在包体内使用,用户无法直接使用;
- 在包体声明列表中,本地变量必须在所有的方法实现之前进行声明;本地方法必须在使用之前进行声明或实现;
- 如果创建包时,在函数定义中使用 DETERMINISTIC 指定该函数为确定性函数,在函数实现中可以省略指定该函数的确定性;如果函数定义中没有指定该函数为确定性函数,则函数实现时不能指定该函数为确定性函数。
- 结果集缓存 RESULT_CACHE,只是语法支持,没有实际意义。
权限
使用该语句的用户必须是 DBA 或该包对象的拥有者且具有 CREATE PACKAGE 数据库权限的用户。
11.2 重编译包
重新对包进行编译,如果重新编译失败,则将包置为禁止状态。
重编功能主要用于检验包的正确性。
语法格式
ALTER PACKAGE [<模式名>.]<包名> COMPILE [CASCADE] [DEBUG];
参数
1.< 模式名 > 指明被重编译的包所属的模式;
2.< 包名 > 指明被重编译的包的名字;
3.[CASCADE] 当指定 CASCADE 后,将级联重编译所有直接或间接引用该包的对象,需要考量影响范围,建议谨慎使用;
4.[DEBUG] 可忽略。
图例
重编译包
权限
执行该操作的用户必须是包的创建者,或者具有 DBA 权限。
11.3 删除包
和创建方式类似,包对象的删除分为包规范的删除和包主体的删除。
11.3.1 删除包规范
从数据库中删除一个包对象。
语法格式
DROP PACKAGE [IF EXISTS] [<模式名>.]<包名>;
参数
1.< 模式名 > 指明被删除的包所属的模式,缺省为当前模式;
2.< 包名 > 指明被删除的包的名字。
图例
删除包规范
使用说明
1.删除不存在的包规范会报错。若指定 IF EXISTS 关键字,删除不存在的包规范,不会报错;
2.如果被删除的包不属于当前模式,必须在语句中指明模式名;
3.如果一个包规范被删除,那么对应的包主体被自动删除。
权限
执行该操作的用户必须是 DBA,或者是该包的拥有者且具有 DROP PACKAGE 权限。
11.3.2 删除包主体
从数据库中删除一个包的主体对象。
语法格式
DROP PACKAGE BODY [IF EXISTS] [<模式名>.]<包名>;
参数
1.< 模式名 > 指明被删除的包所属的模式,缺省为当前模式;
2.< 包名 > 指明被删除的包的名字。
图例
删除包主体
使用说明
- 删除不存在的包主体会报错。若指定 IF EXISTS 关键字,删除不存在的包主体,不会报错;
- 如果被删除的包不属于当前模式,必须在语句中指明模式名。
权限
执行该操作的用户必须是 DBA,或者是该包的拥有者且具有 DROP PACKAGE 权限。
11.4 应用实例
以下是一个包规范的使用实例:
创建表并插入数据,这些数据将在之后的例子中用到。
CREATE TABLE Person(Id INT IDENTITY, Name VARCHAR(100), City VARCHAR(100));
INSERT INTO Person(Name, City) VALUES('TOM','武汉');
INSERT INTO Person(Name, City) VALUES('JACK','北京');
INSERT INTO Person(Name, City) VALUES('MARY','上海');
COMMIT;
表中数据如表 11.3.1 所示。
ID | NAME | CITY |
---|---|---|
1 | TOM | 武汉 |
2 | JACK | 北京 |
3 | MARY | 上海 |
创建包规范:
CREATE OR REPLACE PACKAGE PersonPackage AS
E_NoPerson EXCEPTION;
PersonCount INT;
Pcur CURSOR;
PROCEDURE AddPerson(Pname VARCHAR(100), Pcity varchar(100));
PROCEDURE RemovePerson(Pname VARCHAR(100), Pcity varchar(100));
PROCEDURE RemovePerson(Pid INT);
FUNCTION GetPersonCount RETURN INT;
PROCEDURE PersonList;
END PersonPackage;
这个包规范的部件中包括 1 个变量定义,1 个异常定义,1 个游标定义,4 个过程定义和 1 个函数定义。
以下是一个包主体的实例,它对应于前面的包规范定义,包括 4 个子过程和 1 个子函数的代码实现。在包主体的末尾,是这个包对象的初始化代码。当一个会话第一次引用包时,变量 PersonCount 被初始化为 Person 表中的记录数。
创建包主体:
CREATE OR REPLACE PACKAGE BODY PersonPackage AS
PROCEDURE AddPerson(Pname VARCHAR(100), Pcity varchar(100) )AS
BEGIN
INSERT INTO Person(Name, City) VALUES(Pname, Pcity);
PersonCount = PersonCount + SQL%ROWCOUNT;
END AddPerson;
PROCEDURE RemovePerson(Pname VARCHAR(100), Pcity varchar(100)) AS
BEGIN
DELETE FROM Person WHERE NAME LIKE Pname AND City like Pcity;
IF SQL%ROWCOUNT=0 THEN
RAISE E_NoPerson;
END IF;
PersonCount = PersonCount - SQL%ROWCOUNT;
END RemovePerson;
PROCEDURE RemovePerson(Pid INT) AS
BEGIN
DELETE FROM Person WHERE Id = Pid;
IF SQL%ROWCOUNT=0 THEN
RAISE E_NoPerson;
END IF;
PersonCount = PersonCount - SQL%ROWCOUNT;
END RemovePerson;
FUNCTION GetPersonCount RETURN INT AS
BEGIN
RETURN PersonCount;
END GetPersonCount;
PROCEDURE PersonList AS
DECLARE
V_id INT;
V_name VARCHAR(100);
V_city VARCHAR(100);
BEGIN
IF PersonCount = 0 THEN
RAISE E_NoPerson;
END IF;
OPEN Pcur FOR SELECT Id, Name, City FROM Person;
LOOP
FETCH Pcur INTO V_id,V_name,V_city;
EXIT WHEN Pcur%NOTFOUND;
PRINT ('No.' || (cast (V_id as varchar(100))) || ' ' || V_name || '来自' || V_city );
END LOOP;
CLOSE Pcur;
END PersonList;
BEGIN
SELECT COUNT(*) INTO PersonCount FROM Person;
END PersonPackage;
重新编译包:
ALTER PACKAGE PersonPackage COMPILE;
调用包中的 AddPerson 过程,往数据表中增加一条记录:
CALL PersonPackage. AddPerson ('BLACK', '南京') ;
当前记录变化如表 11.3.2 所示。
ID | NAME | CITY |
---|---|---|
1 | TOM | 武汉 |
2 | JACK | 北京 |
3 | MARY | 上海 |
4 | BLACK | 南京 |
调用包中的 RemovePerson 过程,删除第二条记录:
CALL PersonPackage. RemovePerson ('JACK', '北京') ;
或者
CALL PersonPackage. RemovePerson (2) ;
在此例中,以上两种写法可以得到相同的结果,系统对同名过程根据实际参数进行了重载。如果过程执行结果没有删除任何一条表中的记录,那么会抛出一个包内预定义的异常:E_NoPerson。
此时表中的数据如表 11.3.3 所示。
ID | NAME | CITY |
---|---|---|
1 | TOM | 武汉 |
3 | MARY | 上海 |
4 | BLACK | 南京 |
引用包中的变量。
SELECT PersonPackage. PersonCount;
或者
SELECT PersonPackage. GetPersonCount;
以上两句语句的作用是等价的。前一句是直接引用了包内变量,后一句是通过调用包内的子函数来得到想要的结果。
调用包中的过程 PersonList 查看表中的所有记录:
CALL PersonPackage.PersonList;
可以得到以下输出:
No.1 TOM来自武汉
No.3 MARY来自上海
No.4 BLACK来自南京