内容纲要
一. Package的概念
在一个大型项目中,可能有很多模块,而每个模块又有自己的过程、函数等。而这些过程、函数默认是放在一起的(如在PL/SQL中,过程默认都是放在一起 的,即Procedures中),这些非常不方便查询和维护,甚至会发生误删除的事件。
PL/SQL为了满足程序模块化的需要,引入了包的构造。通过使用包就可以分类管理过程和函数等。
- 包是一种数据库对象,相当于一个容器。将逻辑上相关的过程、函数、变量、常量和游标组合成一个更大的单位。用户可以从其他 PL/SQL 块中对其进行引用
- 包类似于C++和JAVA语言中的类,其中变量相当于类中的成员变量,过程和函数相当于类方法。把相关的模块归类成为包,可使开发人员利用面向对象的方法进行开发,具有面向对象程序设计语言的特点,
- PL/SQL的包具有信息隐蔽性(information hiding),仅在算法和数据结构设计有关层可见。可将过程说明和过程体组成一个程序单位。也可将过程说明与它的过程体分开。也可在包中定义过程,而该过程在包说明中没有定义过程说明,这样定义的过程仅在包内使用。
- 在PL/SQL程序设计中,使用包不仅可以使程序设计模块化,对外隐藏包内所使用的信息(通过使用私用变量),而写可以提高程序的执行效率。
优点:
- 把函数和存储过程进行分门别类,不同package的存储过程可以重名,可以有效避免命名冲突和加强模块化管理
- 被组合在一起的相关对象的集合,当包中任何函数或者存储过程被调用,包就会被加载入内存,包中的任何函数或存储过程的子程序访问速度将大大加快。
一个完整的Package包括包头(specification)和包体(body)两个部分
二. Package的创建
- 只有当包头编辑成功后才能编辑包体.其中的函数名与过程名须和包头中的函数过程一样.
- 包说明和包体必须有相同的名字
- 包的开始没有begin语句,与存储过程和函数不同。
- 在包的说明部分定义函数和过程的名称和参数,具体实现在包体中定义。
- 在包内声明常量、变量、类型定义、异常、及游标时不使用declare。
- 包内的过程和函数的定义不要create or replace语句。
- 包声明和包体两者分离。
1. 语法
1.1 创建包头
CREATE [OR REPLACE] PACKAGE package_name
{IS | AS}
[公有数据类型定义]
[公有游标声明]
[公有变量、常量声明]
[公有子程序声明]
END;
A. 包头中直接定义的变量,均为全局变量,可以在包外使用。
B. 包头中仅声明过程,函数,RECORD,Collections等的类型
1.2 创建包体
CREATE [OR REPLACE] PACKAGE BODY package_name
{IS | AS}
[私有数据类型定义]
[私有变量、常量声明]
[私有子程序声明和定义]
[公有子程序定义]
BEGIN
执行部分(初始化部分)
END;
A. 包体中直接定义的变量均为局部变量,只能在包内使用
B. 包体中具体定义过程,函数,RECORD类型的变量等
1.3 包的调用
Package-name.type-name
Package-name.procedure-name
Package-name.function-name
...
Package-name 是程序包名称,
type-name是包内声明的类型名称,
procedure-name是包内声明的过程名称,
function-name 是包内声明的函数名称
2. 实例
2.1 创建包头
-- 创建一个名为test的包头。
CREATE OR REPLACE PACKAGE test
IS
-- 定义一个名为transid的全局变量,类型为string。
transid STRING
-- 定义一个名为l_row的全局变量,类型为int。
l_row int := 1
-- 声明一个名为test_procedure的过程,形参名为test_name,类型为字符串。
-- 声明一个名为aa_type的NESTED TABLE类型,用来存放整数型的数据。
TYPE aa_type IS TABLE OF INTEGER
END;
/
2.2 创建包体
-- 声明一个名为test的包体,此处必须与包头名一致。
CREATE OR REPLACE PACKAGE body test
IS
-- 具体定义过程test_procedure的内容。
PROCEDURE test_procedure(test_name string) IS
BEGIN
dbms_output.put_line(test_name ||'是个好同学!')
END
END;
/
2.3 包的调用
DECLARE
-- 声明一个包内NESTED TABLE类型aa_type的变量v_aa_type。
v_aa_type test.aa_type:=test.aa_type()
BEGIN
-- 给变量v_aa_type分配空间。
v_aa_type.extend(3)
-- 依次给变量v_aa_type赋值。
v_aa_type(1) := '1001'
v_aa_type(2) := '2001'
v_aa_type(3):= '3001'
-- 依次打印出变量v_aa_type里的值。
FOR i IN 1..v_aa_type.count()
LOOP
dbms_output.put_line(v_aa_type(i))
END LOOP;
test.test_procedure('张三')
END;
/
三. Package的调用
Inceptor中,在创建Packages的时候可以仅创建包头不用再创建包体;但是创建包体的时候,一定要有一个相同名字的包头的存在。
1. 仅创建包头
1.1 创建一个名为aa_pkg的包头,在包内定义一个名为aa_type的表类型
-- 创建一个名为aa_pkg的包头。
CREATE OR REPLACE PACKAGE aa_pkg IS
-- 创建包内类型NESTED TABLE类型,名为aa_type。
TYPE aa_type IS TABLE OF INTEGER
END;
/
1.2 创建一个名为print_aa的过程,依次打印出表类型变量中的值
CREATE OR REPLACE PROCEDURE print_aa (aa aa_pkg.aa_type) IS
-- 定义一个整数类型的变量i。
i INT
BEGIN
-- 使用集合元素方法FIRST()和NEXT(i),依次打印出aa内的值。
i := aa.FIRST()
WHILE i IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE (i ||' '||aa(i))
i := aa.NEXT(i)
END LOOP;
END;
/
1.3 创建一个aa_pkg.aa_type类型的变量为aa_var
-- 给变量aa_var分配大小为3的空间。
-- 依次给变量aa_var赋值。
-- 利用过程print_aa打印出变量的值。
!set plsqlUseSlash true
DECLARE
-- 创建一个类型为aa_pkg包内类型aa_type的变量aa_var,并初始化。
aa_var aa_pkg.aa_type:=aa_pkg.aa_type()
BEGIN
aa_var.extend(3)
aa_var(1) := '1001'
aa_var(2) := '2001'
aa_var(3):= '3001'
print_aa(aa_var)
END;
/
2. 创建包头和包体
2.1 创建一个名为testpackage的包头
-- 创建一个名为testpackage的包头。
CREATE OR REPLACE PACKAGE testpackage
IS
-- 声明一个名为record_type的记录类型,分量名分别为trans_time,price,分量类型分别为string,double。
TYPE record_type IS RECORD(trans_time STRING, price DOUBLE)
-- 声明一个名为cur的动态游标。
TYPE cur IS REF CURSOR
-- 声明一个名为testprocedure()的过程,且不带参数。
PROCEDURE testprocedure()
END;
/
2.2 创建一个名为testpackage的包体
-- 创建一个名为testpackage的包体,此处和包头名相同。
CREATE OR REPLACE PACKAGE BODY testpackage
IS
-- 定义一个类型为record_type的变量v_record_type。
v_record_type record_type
-- 定义一个游标类型为cur的游标变量sqlcur。
sqlcur cur
-- 具体定义过程testprocedure()的内容。
PROCEDURE testprocedure() IS
BEGIN
-- 打开游标sqlcur,去查询表transactions中交易号为943197522的交易时间和价格。
OPEN sqlcur FOR
SELECT trans_time,price FROM transactions WHERE trans_id=943197522;
LOOP
-- 将游标查询到的结果放进变量v_record_type里。
FETCH sqlcur INTO v_record_type
-- 如果游标没有查询到结果,就退出。
EXIT WHEN sqlcur%notfound
-- 输出变量v_record_type的分量trans_time和分量price的值。
dbms_output.put_line(v_record_type.trans_time||' '||v_record_type.price)
END LOOP;
-- 关闭游标。
CLOSE sqlcur
END;
END;
/
2.3 调用包内函数
BEGIN
-- 调用包内过程testpackage.testprocedure(),此处不可以直接调用testprocedure()。
testpackage.testprocedure()
END;
/
四.Inceptor预定义包
1.dbms_output
Inceptor中,包dbms_output是系统预定义的,包内只有一个名为put_line的过程,且put_line过程也是系统预定义的。
在实际的使用中,可以直接调用过程put_line打印结果,也可以调用包内过程put_line来打印结果。
BEGIN
FOR test IN 1..5 LOOP
dbms_output.put_line('test:'||test)
END LOOP;
END;
/
2.owa_util
在撰写ETL调度工具时,在往日志表里面写调度日志时,需要写入当前执行的用户名称、执行作业的名称等信息,所以就想到了OWA_UTIL.WHO_CALLED_ME这个过程。
Parameter | Description |
---|---|
owner | The owner of the program unit.–程序单元的所有者。 |
name | The name of the program unit. This is the name of the package, if the calling program unit is wrapped in a package, or the name of the procedure or function if the calling program unit is a standalone procedure or function. If the calling program unit is part of an anonymous block, this is NULL.–程序单元的名称。这是包的名称(如果调用程序单元包装在包中),或者是过程或函数的名称(如果调用程序单元是独立的过程或函数)。如果调用程序单元是匿名块的一部分,则该值为NULL。 |
lineno | The line number within the program unit where the call was made.–调用所在程序单元中的行号。 |
caller_t | The type of program unit that made the call. The possibilities are: package body, anonymous block, procedure, and function. Procedure and function are only for standalone procedures and functions.–发出调用的程序单元的类型。可能性有:包体、匿名块、过程和函数。过程和函数仅用于独立的过程和函数。这一点很重要,如果过程在包里面,返回的是包名。 |
-- 创建一个名为outer_proc()的过程。
CREATE OR REPLACE PROCEDURE outer_proc()
IS
-- 分别声明四个变量。
v_owner STRING
v_name STRING
v_lineno INT
v_type STRING
BEGIN
DBMS_OUTPUT.PUT_LINE('Outer outer proc')
-- 调用包内过程owa_util.who_called_me,其中v_owner, v_name, v_lineno,
v_type分别用来储存相对应形参所获得的值。
owa_util.who_called_me(v_owner, v_name, v_lineno, v_type)
-- 依次输出四个变量的值。
DBMS_OUTPUT.PUT_LINE('Owner: ' || v_owner)
DBMS_OUTPUT.PUT_LINE('Name: ' || v_name)
DBMS_OUTPUT.PUT_LINE('Lineno: ' || v_lineno)
DBMS_OUTPUT.PUT_LINE('Type: ' || v_type)
END;
/
BEGIN
outer_proc()
END;
/
通过结果来看,own_util包形同虚设