目录
11.1 优势和利益... 1
11.2 过程... 1
11.2.1 语法... 2
11.2.2 建立或者替换... 2
11.2.3 执行存储过程... 3
11.2.4 安全... 3
试验:访问过程... 3
11.2.5 参数... 5
11.2.6 局域声明... 12
试验:AUTHID DEFINER. 13
工作原理... 14
试验:AUTHID CURRENT_USER. 14
试验:使用自动事务处理进行日志记录... 15
11.3 函数... 17
11.3.1 语法... 17
11.3.2 返回值... 17
试验:ITE函数... 18
11.3.3 确定性... 19
11.3.4 常见错误... 19
11.4 程序包... 20
11.4.1 语法... 20
11.4.2 规范... 20
11.4.3 主体... 21
试验:私有过程和函数... 22
11.4.4 程序包变量和其它声明... 25
试验:程序包变量... 25
实例化程序块... 27
11.4.5 重载... 28
试验:重载SWAP() 29
11.4.6 依赖性... 31
试验:依赖性实例... 31
11.4.7 程序包的优势... 33
试验:改变程序包... 33
11.5 数据词典... 34
11.5.1 列出所有的用户存储过程... 34
11.5.2 从数据库中检索用户代码... 34
11.6 包装实用工具... 35
11.7 小结... 36
开发者能够命名他们的PL/SQL程序块,为它们确定参数,将它们存储在数据库中,并且从任何数据库客户或者实用工具中引用或者运行它们,例如 SQL*Plus、Pro*C,甚至是JDBC。
这此听PL/SQL程序称为存储过程和函数。它们的集合称为程序包。在本章中,我们将要解释使用过程、函数和程序包的三大优势、这三种相似结构之间的区别。
Oracle 9i产品帮助文档:
http://docs.oracle.com/cd/B10501_01/index.htm
可根据自己需要进行查询,包含了众多的文档。
Sample Schemas的目录:
http://docs.oracle.com/cd/B10501_01/server.920/a96539/toc.htm
Sample Schemas的文档(示例模式的表及介绍):
http://docs.oracle.com/cd/B10501_01/server.920/a96539.pdf
11.1 优势和利益
我们来开始分析使用存储过程、函数和程序包代替匿名PL/SQL程序块的优势:
●可扩展性。使用过程和函数可以让开发者增加功能。编写用户自己的全程可以让用户灵活地扩展数据库的核心能力。
●模块化。任何优秀的开发者都知道编写模块化代码的重要性。用户应该通过编写小型、可管理的过程在用户应用中定义单独的处理模块,它们可以组织在一起形成更复杂的应用。
●可重用性。由于这些全程命名后并且保存在数据库中,所以任何应用都能够执行它们(只有它们更具有合适的权限完成这项工作)。这种重用代码的能力在开发过程中非常关键。
●可维护性。继续使用上一部分中的示例,考虑当存储日志信息的表发生改变之后,会出现什么情况。如果用户需要改动的只有一个地方,那么就会使用户代码更容易维护。
●抽象和数据隐藏。我们假定用户正在调用不是由用户编写的名为GIVE_EMPLOYEE RAISE()的过程,用户可以向它提供一些信息,让它来工作,换句话说,用户可以用它来员工加薪。它对于用户来讲是个黑箱。它怎样实现并不重要,用户所需要知道的就是它能够完成的工作。所有处理过程的实现和复杂性都对调用者进行了隐藏。
●安全性。就如作为安全机制使用的视图一样,过程也具有相同的功效。用户可以设为用户应用,使得访问数据库的唯一方式就是通过用户提供的过程和函数。这不仅可以让数据更加安全,而且还可以确保它的正确性。
11.2 过程
存储过程(stored procedure)从根本上讲就是命名PL/SQL程序块,它可以被赋予参数、存储在数据库中,然后由另一个应用或者PL/SQL例程激活(或者调用)。建立最简单的存储过程:
SQL> create procedure my_proc as 2 begin 3 null; 4 end my_proc; 5 / 过程已创建。
注意:
可以注意到,当建立存储过程的时候,用户需要在过程的最后一行放入一个/,来告诉SQL*Plus执行用户以前输入或者载入的代码行。
11.2.1 语法
现在我们来讨论建立存储过程的语法:
1 [CREATE [OR REPLACE]] 2 PROCEDURE procedure_name[(parameter[,parameter]…)] 3 [AUTHID (DEFINER | CURRENT_USER)] {IS | AS} 4 [PRAGMA AUTONOMOUS_TRANSACTION;] 5 [local declarations] 6 BEGIN 7 executable statements 8 [EXCEPTION 9 exception handlers] 10 END [name];
这个语法的大部分代码都匿名PL/SQL程序块非常相似。在BEGIN-EXCEPTION-END程序块之间只有少量的差别。
11.2.2 建立或者替换
我们来编辑最初的示例,完成第一个计算机程序要完成的所有工作,打印“Hello World”。
SQL> create procedure my_proc as 2 begin 3 dbms_output.put_line('Hello World'); 4 end my_proc; 5 / create procedure my_proc as * ERROR 位于第 1 行: ORA-00955: 名称已由现有对象使用
如果我们要使用正确的语法,我们就要对过程进行修改,如下所示:
SQL> create or replace procedure my_proc as 2 begin 3 dbms_output.put_line('Hello World'); 4 end my_proc; 5 / 过程已创建。
11.2.3 执行存储过程
既然已经拥有了存储过程,接下来我们就要查看它的实际运行情况,确保它完成了我们希望它完成的工作。为了执行用户的过程,用户可以从PL/SQL匿名程序块中对其进行调用:
SQL> set serverout on SQL> begin 2 my_proc; 3 end; 4 / Hello World PL/SQL 过程已成功完成
另外,还有一种快捷方式可以使调用过程更容易。用户可以在SQL*Plus中使用EXECUTE命令,或者科室为EXEC:
SQL> execute my_proc; Hello World PL/SQL 过程已成功完成
调用EXECUTE <PROCEDURE_NAME>与在匿名PL/SQL程序块中调用过程安全相同。它是一个SQL*Plus的便捷函数,而不是PL/SQL命令。
11.2.4 安全
由于存储过程存储在数据库中,所以它是数据库对象。就如同其他对象一样,可以通过特权控制对它的访问。表和视图具有SELECT、INSERT、UPDATE和DELETE这样的特权,而过程具有EXECUTE特权。将过程上的EXECUTE特权赋予用户或者角色,可以让这些实体有能力运行它:将它赋予角色PUBLIC可以让所有用户都可以使用这个过程。
试验:访问过程
(1) 我们首先要建立三个数据库用户CHRIS、SEAN和MARK。用户需要使用具有DBA角色的账号进行连接来完成这项工作。
SQL> conn system/zyf; 已连接。 SQL> create user chris identified by chris; 用户已创建 SQL> grant connect,resource to chris; 授权成功。 SQL> create user sean identified by sean; 用户已创建 SQL> grant connect,resource to sean; 授权成功。 SQL> create user mark identified by mark; 用户已创建 SQL> grant connect,resource to mark; 授权成功。
(2) 现在,我们要作为用户MARK建立一个过程。这是非常简单的过程。它不会执行任何工作,但是可以帮助我们展示访问特权:
SQL> conn mark/mark; 已连接。 SQL> create procedure marks_procedure as 2 begin 3 null; 4 end; 5 / 过程已创建。
(3) 现在,我们尝试作为用户CHRIS执行这个过程:
SQL> conn chris/chris; 已连接。 SQL> execute mark.marks_procedure BEGIN mark.marks_procedure; END; * ERROR 位于第 1 行: ORA-06550: 第 1 行, 第 7 列: PLS-00201: 必须说明标识符 'MARK.MARKS_PROCEDURE' ORA-06550: 第 1 行, 第 7 列: PL/SQL: Statement ignored
CHRIS不能访问和执行MARK的过程。就如同其它对象一样,默认情况下,最初只有所有者可以访问它们的过程。为了允许其它用户访问他们的过程,这些所有者必须明确向其它用户赋予访问权。
(4) 我们返回去,为CHRIS赋予过程上的EXECUTE特权。
SQL> conn mark/mark; 已连接。 SQL> grant execute on marks_procedure to chris; 授权成功。 SQL> conn chris/chris; 已连接。 SQL> execute mark.marks_procedure PL/SQL 过程已成功完成。
当CHRIS被赋予了EXECUTE特权之后,他就能够执行MARK的过程。
(5) 现在,我们来作为用户SEAN尝试执行这个过程。
SQL> conn sean/sean; 已连接。 SQL> execute mark.marks_procedure BEGIN mark.marks_procedure; END; * ERROR 位于第 1 行: ORA-06550: 第 1 行, 第 7 列: PLS-00201: 必须说明标识符 'MARK.MARKS_PROCEDURE' ORA-06550: 第 1 行, 第 7 列: PL/SQL: Statement ignored
(6) 现在,我们来作为用户MARK进行连接,并且GRANT EXECUTE给PUBLIC,接下来,当我们完成这些操作之后,看看SEAN是否能够执行MARK的过程:
SQL> conn mark/mark; 已连接。 SQL> grant execute on marks_procedure to public; 授权成功。 SQL> conn sean/sean 已连接。 SQL> execute mark.marks_procedure PL/SQL 过程已成功完成。
注意:
可以注意到,为一个过程将EXECUTE赋予PUBLIC可以让所有的数据库用户都能够使用它。尽管没有向SEAN赋予直接的访问权,人还可以通过PUBLIC角色继承EXECUTE特权。
11.2.5 参数
过程可以进行参数化处理。这意味着,过程的调用者传递一个值 即可使用它。参数可以是任何合法的PL/SQL类型,它可以采用三种模式:IN、OUT和IN OUT。
●IN 参数要通过调用者传入,只能够由过程读取。这就是说,它们是只读值,不能由过程所改变。这是参数行为中最常用的模式,是没有给出参数模式指示器时的默认模式。
●OUT 参数能够由过程写入。这些参数适用于过程需要向调用者返回多条信息的时候。必须向OUT参数传递返回值。
●IN OUT 参数就如其名称所暗示的那样,同时具有IN参数和OUT参数的特性。过程能够读取和写入它们的值。
- INT参数
我们来作为用户CHRIS进行连接,并且建立简单的过程INSERT_INTO_T()。就如过程名称所暗示的那样,这个过程将要接受一个参数P_PARM,并将它的值插入到表T中:
SQL> conn chris/chris; 已连接。 SQL> create table t( 2 n number 3 ) 4 / 表已创建。 SQL> create or replace 2 procedure insert_into_t(p_parm in number) is 3 begin 4 insert into t values(p_parm); 5 end insert_into_t; 6 / 过程已创建。
我们来试着执行新过程,看看会发生什么变化:
SQL> select * from t; 未选定行 SQL> exec insert_into_t(p_parm=>100); PL/SQL 过程已成功完成。 SQL> run 1* select * from t N ---------- 100
- 参数传递
我们可以采用三种方式传递参数
●使用名称表示法
●使用位置表示法
●使用混合表示法
名称表示法
在以上的示例中,我们明确命名了各个参数。这称为名称参数。它会采用如下形式:
PROCEDURE_NAME(PARAM_NAME =>VALUE[,PARM_NAME=>VALUE])
形成过程要调用命名各个正规参数、后面跟随由等于号(=)和大于号(>)构成=>,最后使用表达式来结束。以上的示例中,用户应该看到调用:
SQL> exec insert_into_t(p_parm1=>200,p_parm=>201);
看看当我们使用打乱次序的参数调用过程的时候会出现什么情况:
SQL> create or replace procedure three_parms( 2 p_p1 number, 3 p_p2 number, 4 p_p3 number 5 ) as begin 6 dbms_output.put_line('p_p1='||p_p1); 7 dbms_output.put_line('p_p2='||p_p2); 8 dbms_output.put_line('p_p3='||p_p3); 9 end three_parms; 10 / 过程已创建。 SQL> set serverout on SQL> exec three_parms(p_p1=>12,p_p3=>3,p_p2=>68); p_p1=12 p_p2=68 p_p3=3 PL/SQL 过程已成功完成。
位置表示法
命名符号可能会很长。考虑一个具有10个或者更多参数的过程,调用例程要输入很多的内容。大多数开发者可以使用称为位置表示法参数传递的更短的符号。采用这种方式时,用户可以基于参数在过程中定义的次序对它们进行传递。如:
SQL> exec three_parms(12,3,68) p_p1=12 p_p2=3 p_p3=68 PL/SQL 过程已成功完成。
混合表示法
用户可以将名称表示法参数传递和位置表示法参数传递相混合。这称为混合表示法参数传递。以上的过程也可以采用这种方式调用:
SQL> exec three_parms(12,3,p_p3=>68); p_p1=12 p_p2=3 p_p3=68 PL/SQL 过程已成功完成。
默认值
到目前为止,物价普过程定义的所有参数都提供了参数值,但是如果过程有默认值,我们不是必须要这样做。过程的作者可以为任何IN参数定义默认值。
注意:
OUT和IN OUT参数不能有默认值。
我们来分析一个示例:
SQL> create or replace procedure default_values( 2 p_parm1 varchar2, 3 p_parm2 varchar2 default 'Chris', 4 p_parm3 varchar2 default 'Sean') as 5 begin 6 dbms_output.put_line(p_parm1); 7 dbms_output.put_line(p_parm2); 8 dbms_output.put_line(p_parm3); 9 end default_values; 10 / 过程已创建。 SQL> set serverout on SQL> exec default_values('Tom',p_parm3=>'Joel'); Tom Chris Joel PL/SQL 过程已成功完成。
增加参数
作为一个小的实用规则,如果您要向已有的存储过程增加参数,那么就应该将其增加为最后的参数。通过采用这种方法,只要用户增加的参数具有默认值,那么当前使用位置表示法调用用户过程的所有例程就仍然可以工作。如果用户所有增加的参数没有作为过程定义的最后的参数,那么用户就会导致调用用户过程的所有例程抛出错误,或者得到意想不到的结果。
常见错误
当调用过程的时候,用户可能会在用户参数传递中犯一些错误。最常见的错误有:
●提供了没有在过程中定义的名称参数
●没有提供足够的参数
●提供过多的参数
●提供了具有非法数据类型的参数
- OUT参数
到目前为止,我们的示例过程只会获取参数值,并且对它们进行处理。我们还没有看到任何机制可以从例程向调用者返回值。OUT参数可以让我们完成这项工作。
SQL> create or replace procedure emp_lookup( 2 p_empno in number, 3 o_ename out emp.ename%type, 4 o_sal out emp.sal%type) as 5 begin 6 select ename,sal into o_ename,o_sa● 7 from emp 8 where empno=p_empno; 9 exception 10 when NO_DATA_FOUND then 11 o_ename:='NULL'; 12 o_sal:=-1; 13 end emp_lookup; 14 / 过程已创建。
这里是我们在SQL*Plus中使用VARIABLE命令调用我们的EMP_LOOKUP过程的示例。首先我们要定义2个变量:
SQL> variable name varchar2(10); SQL> variable sal number; SQL> exec emp_lookup('7782',:name,:sal); PL/SQL 过程已成功完成。 SQL> print name NAME -------------------------------- CLARK SQL> print sa● SAL ---------- 2450 SQL> print name,sal NAME -------------------------------- CLARK
或者我们也可以凸现这些返回值,从DUAL中选取它们:
SQL> select :name,:sal from dual; :NAME :SAL -------------------------------- ---------- CLARK 2450
从匿名PL/SQL程序块中调用相同的过程将会如下所示:
SQL> set serverout on SQL> declare 2 l_ename emp.ename%type; 3 l_sal emp.sal%type; 4 begin 5 emp_lookup(7782,l_ename,l_sal); 6 dbms_output.put_line('Ename='||l_ename); 7 dbms_output.put_line('Sal='||l_sal); 8 end; 9 / Ename=CLARK Sal=2450 PL/SQL 过程已成功完成。
- IN OUT参数
就如名称所暗示的那样,IN OUT参数能够用于传入参数值,并且从存储过程返回值。它们适用于用户需要向过程传递可以由过程本身修改的INPUT变量的情况。
用户需要这种功能的过程的典型示例就是交互例程:
SQL> create or replace procedure swap( 2 p_parm1 in out number, 3 p_parm2 in out number) as 4 l_temp number; 5 begin 6 l_temp:=p_parm1; 7 p_parm1:=p_parm2; 8 p_parm2:=l_temp; 9 end swap; 10 / 过程已创建。
执行交互:
SQL> set serverout on SQL> declare 2 l_num1 number:=100; 3 l_num2 number:=101; 4 begin 5 swap(l_num1,l_num2); 6 dbms_output.put_line('l_num1='||l_num1); 7 dbms_output.put_line('l_num2='||l_num2); 8 end; 9 / l_num1=101 l_num2=100 PL/SQL 过程已成功完成。
- NOCOPY
当用户传递作为参数的大型数据结构时,使用NOCOPY提示通常会很有用。
考虑一个同时具有IN参数和IN OUT参数的过程。IN参数会通过引用传递(passed by reference)。这意味着它会传递指向变量实际内存位置的只读指针。
要注意,无论参数中的实际值多大,指针都只会传递少量的数据。这是因为所需传递的只是指向位置的指针。
与此相对,OUT参数要通过值传递(passed by value)。这意味着它的值要复制到参数中。
NOCOPY可以让用户提示编译器,就像IN参数一样,通过引用传递OUT和IN OUT参数。然后在这种情况下,引用要可更新,不能够像IN参数一样是只读的。这样就可以节约需要传递给过程,以及从过程回递的数据数量。但是NOCOPY只是一个提令,而不是指令,所以编译器可能会忽略它。即使如此,它通常也会成功,为通过OUT和IN OUT参数传递大量数据的PL/SQL程序带来益处。
SQL> set serverout on SQL> DECLARE 2 l_1 NUMBER := 10; 3 l_2 NUMBER := 20; 4 l_3 NUMBER := 30; 5 PROCEDURE test_out(p1 IN NUMBER, 6 x1 IN OUT NUMBER, 7 x2 IN OUT NOCOPY NUMBER) IS 8 BEGIN 9 x1 := p1; 10 dbms_output.put_line('inside test_out, x1=' || x1); 11 x2 := p1; 12 dbms_output.put_line('inside test_out, x2=' || x2); 13 raise_application_error(-20005, 'test NOCOPY'); 14 END; 15 BEGIN 16 dbms_output.put_line('before, l_1=' || l_1 || ', l_2=' || l_2 || 17 ', l_3=' || l_3); 18 BEGIN 19 --the OUT parameter has no value at all until the program terminates successfully, 20 --unless you have requested use of the NOCOPY hint 21 test_out(l_1, l_2, l_3); 22 EXCEPTION 23 WHEN OTHERS THEN 24 dbms_output.put_line('SQLCODE => ' || SQLCODE || ', SQLERRM => ' || 25 SQLERRM); 26 END; 27 dbms_output.put_line('after, l_1=' || l_1 || ', l_2=' || l_2 || ', l_3=' || l_3); 28 END; 29 / before, l_1=10, l_2=20, l_3=30 inside test_out, x1=10 inside test_out, x2=10 SQLCODE => -20005, SQLERRM => ORA-20005: test NOCOPY after, l_1=10, l_2=20, l_3=10 PL/SQL 过程已成功完成。
- 参数次序
定义参数的次序没有限制。具有或者不具有默认值的IN、OUT和IN OUT参数都可以采用开发者认为合适的次序进行混合。一般的惯例是将那些没有默认值的必要参数放在列表的开始,其后跟随OUT参数,然后是IN OUT,最后是具有默认值的IN参数。采用这种方式定义参数可以让调用者在执行用户过程的时候,更有可能使用位置表示法。考虑如下示例:
procedure get_balance(p_date in date defaultsysdate, p_value out number, p_name in varchar2 default user, p_account_number in number)
唯一必须的参数是P_VALUE和P_ACCOUNT_NUMBER。由于它们是第2个和第4个参数,所以为了调用这个过程,即使用户想要使用默认址,用户也需要使用名称表示法,或者传递所有4个参数。用户定义这个过程的理想方式应该如下所示:
procedure get_balance(p_account_number in number, p_value out number, p_date in date defaultsysdate, p_name in varchar2 default user)
11.2.6 局域声明
就如同匿名PL/SQL程序块一样,过程可以定义局域变量。这些定义紧随可选的参数列表之后。在匿名PL/SQL程序块中,它使用DECLARE保留字开始。在过程声明中,由于我们使用了CREATE OR REPLACE语法,所以不必再使用这个保留字。
如果用户注意了我们最后的示例SWAP(),用户就应该看到我们使用了一个局域变量L_TEMP。
create or replace procedure swap( p_parm1 in out number, p_parm2 in out number)as l_temp number; begin end;
- AUTHID
过程的AUTHID指令可以告诉Oracle,这个过程使用谁的权限运行。默认情况下,存储过程会作为调用者的过程运行,但是具有设计者的特权。过程的CURRENT_SCHEMA将会是它的设计者,也就是说,过程内引用的所有对象都会解析为过程设计者的对象。这称为使用设计者权利运行,理解它很重要。这是使用过程安全策略的基础。用户可以通过增加AUTHID指令,明确定义过程使用设计者的权限:
create or replace procedure foo AUTHID DEFINER as begin null; end foo;
因为这是默认行为,所以它并不是必须的。考虑我们的用户SCOTT。他拥有一个表,取消了所有人在这个表上的特权,任何普通用户(除了他自己)都不可能访问这个表。现在,SCOTT要编写一个过程,向表中插入所提供的值,以及是谁插入了这个数据行。他将这个过程的EXECUTE特权赋予了PUBLIC。尽管除了SCOTT以外,没有用户具有直接向表中进行插入的特权。但是由于过程使用SCOTT的特权运行,所以所有使用SCOTT过程的用户都可以插入数据行。在这个过程中引用的所有对象都会认为是SCOTT正在访问它们。
我们来使用数据库账号scott和hr,实现以上的场景,即看能否通过hr访问scott的存储过程。
试验:AUTHID DEFINER
(1) 我们首先作为用户SCOTT连接数据库,建立NUMBERS表。
SQL> conn scott/tiger 已连接。 SQL> create table numbers( 2 n number, 3 username varchar2(30) 4 ) 5 / 表已创建。
(2) 现在,建立存储过程INSERT_NUMBERS,它会向NUMBERS表中进行插入操作:
SQL> create or replace 2 procedure insert_numbers(p_num number)AUTHID DEFINER as 3 begin 4 insert into numbers values(p_num, user); 5 end; 6 / 过程已创建。
(3) 我们要将过程上的EXECUTE特权赋予PUBLIC。
SQL> grant execute on insert_numbers to public; 授权成功。
(4) 我们作为用户HR进行连接,尝试向NUMBERS表中进行一次插入。
SQL> conn hr/hr; 已连接。 SQL> insert into scott.numbsers values(12345,'SEAN'); insert into scott.numbsers values(12345,'SEAN') * ERROR 位于第 1 行: ORA-00942: 表或视图不存在
(5) 我们现在作为HR执行INSERT_NUMBERS过程,并且查看表的内容(只有SCOTT能够访问):
SQL> exec scott.insert_numbers(12345); PL/SQL 过程已成功完成。
工作原理
这对于安全非常重要,它可以让过程的作者和应用的开发者更加有力地控制数据的访问。如果所有对基本表的访问都受到限制,访问表的唯一方式就是通过存储过程,那么,开发者能够确保数据的一致性。如果出现了错误,他们也可以知道问题出现在所开发的API中。
另外,用户还可以定义使用调用者特权运行的过程。这称为使用调用者权利(invoker rights)。可以使用具有CURRENT_USER的AUTHID定义用户过程来实现:
create or replace procedure foo AUTHID DEFINER as begin null; end;
这种定义具有与设计者权利相对的效果。它会采用调用者的特权,而不是设计者的特权。当用户想要在运行时,而不是在编译时进行特权检查的时候,就可以使用这个选项。在使用AUTHID CURRENT_USER过程时,如果用户没有过程所访问对象上的特权时,也可以成功编译调用。这可能会让开发者编写最终不能进行访问的代码。如果用户要执行这个过程,而没有过程所访问对象上的特权,就会出现运行错误。
我们要将我们最后的示例改为AUTHID CURRENT_USER,并且查看效果。
试验:AUTHID CURRENT_USER
我们将要使用与上个例子完全相同的过程,只是要改变AUTHID子句。
(1) 我们首先会作为用SCOTT重新建立INSERT_NUMBERS过程。
SQL> conn scott/tiger; 已连接。 SQL> create or replace 2 procedure insert_numbers(p_num number)AUTHID CURRENT_USER as 3 begin 4 insert into numbers values(p_num, user); 5 end; 6 / 过程已创建。
注意,唯一作出修改的代码行是第2行,其中修改后过程改为AUTHID CURRENT_USER。
(2) 现在,我们来作为用户HR执行这个过程
SQL> grant execute on insert_numbers to hr; 授权成功。 SQL> conn hr/hr; 已连接。 SQL> exec scott.insert_numbers(12345); BEGIN scott.insert_numbers(12345); END; * ERROR 位于第 1 行: ORA-00942: 表或视图不存在 ORA-06512: 在"SCOTT.INSERT_NUMBERS", line 3 ORA-06512: 在line 1
工作原理
执行过程SCOTT.INSERT_NUMBERS()将会产生与上一次完全不同的结果。由于过程定义为AUTHID CURRENT_USER,所以在执行的时候,过程中的对象都会解析为CURRENT_USER的对象。在这个例子中,当HR执行过程,应用表NUMBSERS的时候,它就会在它的CURRENT_SCHEMA中寻找NUMBERS表。由于没有这样的对象,所以出现ORA-00942: 表或视图不存在。
- PRAGMA AUTONOMOUS_TRANSACTION
在第12章中,我们将要讨论事务处理,以及COMMIT或者ROLLBACK的含义,和它们对以前所做工作的影响。从较高的角度来看,当使用了COMMIT的时候,所有在用户事务处理中执行的工作都会“保存”在数据库中,与此相对,当使用了ROLLBACK的时候,用户事务处理中的所有工作都会反转,将数据返回到事务处理开始前的样子。
过程可以完成插入和更新这样的工作。我们已经在前面的例子中看到了向表中INSERT的过程。这些过程可以在更大的事务处理中使用。如果我们的过程中使用了COMMIT,那么以前的工作就会提交,这并不总是好事。考虑向数据库表记录日志的全程。我们不想丢失任何日志信息,所以我们要在每次插入之后使用COMMIT。但是日志只是更大的过程中的很小的组成部分。如果过程遇到了错误,那么用户就可以想要回滚以前执行的所有工作。但是如果日志例程已经提交了工作,就不可能进行回滚。
为了完成这个工作,用户需要在定义用户存储过程的时候,包含PRAGMA AUTONOMOUS_TRANSACTION指示。
create or replace procedure log_message(p_message varchar2) as pragma autonomous_transaction; begin insert into log_table values(p_message); commit; end;
这个简单的示例功能相当强大。用户可以在用户事务处理中的任何地方调用这个过程,无论父事务处理是提交还是回滚,用户都可以保证LOG_TABLE将会具有用户插入的行(当然,P_MESSAGE不能够长于LOG_TABLE中的列)。
试验:使用自动事务处理进行日志记录
我们来分析自动事务处理的工作。我们将要使用以上的日志过程,从向第2个表插入数据行的匿名程序块中调用它,然后进行回滚。
(1) 我们首先要作为用户SCOTT建立LOG_TABLE和TEMP_TABLE表。
SQL> conn scott/tiger; 已连接。 SQL> create table log_table( 2 username varchar2(30), 3 date_time timestamp, 4 message varchar2(4000) 5 ); 表已创建。 SQL> create table temp_table( 2 n number); 表已创建。
对于这个示例,为了获取记录消息的境,我们选择建立一个不仅具有MESSAGE列,也有USERNAME和DATE_TIME列的日志表。TEMP_TABLE表是所使用的第二个表。
(2) 现在,我们建立LOG_MESSAGE()过程。
SQL> create or replace 2 procedure log_message(p_message varchar2) as 3 pragma autonomous_transaction; 4 begin 5 insert into log_table(username,date_time,message) 6 values(user,current_date,p_message); 7 commit; 8 end; 9 / 过程已创建。
注意:
CURRENT_DATE是Oracle 9i的新功能。为了在Oracle 8i中编译这个过程,用户需要使用SYSDATE函数。
这就是我们的AUTONOMOUS_TRANSACTION过程。可以注意到第3行中的PRAGMA。这个过程会向日志表中插入记录,然后提交。
(3) 在分析了LOG_TABLE表和TMEP_TABLE表之后,我们将要执行匿名的PL/SQL程序块,向TEMP_TABLE中进行INSERT,并且在执行ROLLBACK之前调用LOG_MESSAGE();
SQL> select * from temp_table; 未选定行 SQL> select * from log_table; 未选定行 SQL> begin 2 log_message('About to insert into temp_table'); 3 insert into temp_table(n)values(12345); 4 log_message('Rolling back insert into temp_table'); 5 rollback; 6 end; 7 / PL/SQL 过程已成功完成。
我们在插入和回滚之前调用了LOG_MESSAGE()。要记住,LOG_MESSAGE()过程会执行COMMIT。通过查看以上的代码,分析程序的逻辑,用户可能会认为在第2行进行的插入会被第4行调用 的LOG_MESSAGE()中的COMMIT提交,第5行的ROLLBACK不会起任何作用,但是对表进行的分析表明,情况并非如此:
SQL> select * from log_table; USERNAME DATE_TIME MESSAGE ---------- --------------------------------------------------------------------------- -------------------- SCOTT 13-6月 -13 04.11.57.000000 下午 About to insert into temp_table SCOTT 13-6月 -13 04.11.57.000000 下午 Rolling back insert into temp_table
在TEMP_TABLE中没有数据行,而有2行插入到了LOG_TABLE中。外边 的事务处理PL/SQL程序块进行了回滚,而子事务处理,或者自动事务处理已经提交。
自动事务处理非常适用于日志这类的操作、或者其它类型的需要提交,但是不能干涉调用者事务处理的工作。
11.3 函数
11.3.1 语法
建立函数的语法要比建立过程的语法稍微复杂一点。语法的实现中展示了大量的属性,例如PIPELINED,但是本章不会对它们进行进一步地讨论。
(略)
11.3.2 返回值
我们来分析一个示例:
SQL> create or replace 2 function first_function return varchar2 as 3 begin 4 return 'Hello World'; 5 end; 6 / 函数已创建。 SQL> set serverout on SQL> declare 2 l_str varchar2(100):=null; 3 begin 4 l_str:=first_function; 5 dbms_output.put_line(l_str); 6 end; 7 / Hello World
试验:ITE函数
(1) 我们首先定义所需要的函数规范。现在,我们来填充函数的主体,确保所有的退出点都使用了返回VARCAHR2表达式的语句。如果我们不能够涵盖所有的退出点,函数就不能够正确工作,因此我们需要检查P_EXPRESSION的值。
SQL> create or replace function ite( 2 p_expression boolean, 3 p_true varchar2, 4 p_false varchar2) return varchar2 as 5 begin 6 if p_expression then 7 return p_true; 8 end if; 9 return p_false; 10 end; 11 / 函数已创建。
(2) 接下来,我们需要测试代码,看看它是否如我们所愿。
SQL> set serverout on; SQL> exec dbms_output.put_line(ite(1=2,'Equal','Not Equal')); Not Equal PL/SQL 过程已成功完成。 SQL> exec dbms_output.put_line(ite(2>3,'True','False')); False PL/SQL 过程已成功完成。
11.3.3 确定性
如果对于给定的输入,函数总是会返回完全相同的结果,那么就称这个函数具有确定性(deterministic)。UPPER()内嵌函数是确定性的。如果向它输入SaMaNtHa,它就会返回SAMANTHA。如果函数不能够每次都返回相同的值,用户就不能够将其作为确定性函数建立。为了提示函数具有DETERMINISTIC,用户所需完成的全部工作如下所示:
SQL> create or replace 2 function total_compensation( 3 p_salary number, 4 p_commission number) return number 5 deterministic as 6 begin 7 return nvl(p_salary,0)+nvl(p_commission,0); 8 end; 9 / 函数已创建。
我们知道,如果给定任意2个数值作为输入,结果总会是它们的和。
提示函数具有DETERMINISTIC的目的是为了帮助优化器。当提供了相同的输入时,由于它会产生相同的结果,所以优化器就可以选择使用确定性函数以前的结果。对于使用频繁的函数处理,这可以节约许多CPU周期。另外,用户必须为基于函数的索引使用确定性函数。
11.3.4 常见错误
这里是用户在开发函数的时候可能会遇到的一些常见错误:
●忘记获取返回值。
●试图定义不能返回值的函数。
●定义没有返回数据类型的函数。
所以,即使在具有异常处理器的函数中,用户也需要记住从异常处理器中返回一些内容。在定义函数的时候咖一个觉错误是没有定义返回数据类型:
SQL> create or replace 2 function no_return_type as 3 begin 4 return null; 5 end; 6 / 警告: 创建的函数带有编译错误。 SQL> show errors FUNCTION NO_RETURN_TYPE 出现错误: LINE/COL ERROR -------- ----------------------------------------- 1/25 PLS-00103: 出现符号 "AS"在需要下列之一时? ( return compress compiled wrapped
因为PL/SQL函数的定义不正确,所以这里出现了PLS-00103错误。这个函数没有定义返回类型。
11.4 程序包
程序包这种结构可以让用户从逻辑上组织过程、函数、对象类型、以及放入单独的数据库对象中的各种内容。
程序包通常由2部分构成:规范和主体。规范(specification)是程序包的公共接口。所提供的主体(body)包含了规范的实现,以及所有私有例程、数据和变量。
11.4.1 语法
略
11.4.2 规范
程序包规范(或者spec)是程序包的结口。在规范中定义的所有内容都可以由调用者使用,并且可以由具有这个程序包EXECUTE特权的用户引用。在规范中定义的过程可以被执行,变量可以被引用,类型能够被访问。这些是程序包的公共特性。
SQL> create or replace 2 package employee_pkg as 3 procedure print_ename(p_empno number); 4 procedure print_sal(p_empno number); 5 end; 6 / 程序包已创建。
试图立即执行这些过程将会导致错误。
SQL> execute employee_pkg.print_ename(1234); BEGIN employee_pkg.print_ename(1234); END; * ERROR 位于第 1 行: ORA-04068: 已丢弃程序包 的当前状态 ORA-04067: 未执行,package body "SCOTT.EMPLOYEE_PKG" 不存在 ORA-06508: PL/SQL: 无法在调用之前找到程序单元 ORA-06512: 在line 1
所以,在这里我们可以发现程序包主体还没有存在。这些过程还没有实现,所以还没有代码;目前只有接口存在。我们需要编写这些例程的代码,这要在程序包主体中完成。
11.4.3 主体
程序包主体是您实际编写的子例程,实现规范中定义的接口。规范中显示的所有过程和函数都必须在主体中实现:
create or replace package body employee_pkg as procedure print_ename(p_empno number) is l_ename emp.ename%type; begin select ename into l_ename from emp where empno=p_empno; dbms_output.put_line(l_ename); exception when NO_DATA_FOUND then dbms_output.put_line('Invalid employee number'); end; procedure print_sal(p_empno number) is l_sal emp.sal%type; begin select sal into l_sa● from emp where empno=p_empno; dbms_output.put_line(l_sal); exception when NO_DATA_FOUND then dbms_output.put_line('Invalid employee number'); end; end; /
现在,执行这2个过程的时候,我们就会获取结果:
SQL> set serverout on SQL> execute employee_pkg.print_ename(1234); Invalid employee number PL/SQL 过程已成功完成。 SQL> execute employee_pkg.print_sal(7782); 2450 PL/SQL 过程已成功完成。
除了实现规范中所定义过程之外,您还可以定义程序包的私有过程。尽管程序包不能够包含其它的程序包,但是私有过程和函数可以被程序包中定义的任何其它例程所引用。而程序包以外的全程则不能够对它们进行访问。
试验:私有过程和函数
我们来实现以上的2个建议。我们要:
●编写一个私有函数LOG_MESSAGES()
●从公共过程中将共有功能移到一个私有函数中
(1) 我们首先来建立或者重新定义程序包声明。
SQL> CREATE OR REPLACE PACKAGE EMPLOYEE_PKG AS 2 PROCEDURE LOG_MESSAGE(P_MESSAGE VARCHAR2); 3 FUNCTION GET_EMP_RECORD(P_EMPNO NUMBER) RETURN EMP%ROWTYPE; 4 PROCEDURE PRINT_DATA(P_EMP_RECORD EMP%ROWTYPE, P_COLUMN VARCHAR2); 5 PROCEDURE PRINT_ENAME(P_EMPNO NUMBER); 6 PROCEDURE PRINT_SAL(P_EMPNO NUMBER); 7 END; 8 / 程序包已创建。
这个过程与我们在以上使用AUTONOMOUS_TRANSACTIONS的示例中看到的LOG_MESSAGE()过程基本相同。
(2) 我们现在要编写函数,定义程序包主体。
SQL> CREATE OR REPLACE PACKAGE BODY EMPLOYEE_PKG AS 2 PROCEDURE LOG_MESSAGE(P_MESSAGE VARCHAR2) IS 3 PRAGMA AUTONOMOUS_TRANSACTION; 4 BEGIN 5 INSERT INTO LOG_TABLE 6 (USERNAME, DATE_TIME, MESSAGE) 7 VALUES 8 (USER, CURRENT_DATE, P_MESSAGE); 9 COMMIT; 10 END; 11 12 FUNCTION GET_EMP_RECORD(P_EMPNO NUMBER) RETURN EMP%ROWTYPE IS 13 L_EMP_RECORD EMP%ROWTYPE; 14 BEGIN 15 LOG_MESSAGE('Looking for record where EMPNO=' || P_EMPNO); 16 SELECT * INTO L_EMP_RECORD FROM EMP WHERE EMPNO = P_EMPNO; 17 RETURN L_EMP_RECORD; 18 EXCEPTION 19 WHEN NO_DATA_FOUND THEN 20 RETURN NULL; 21 END; 22 23 PROCEDURE PRINT_DATA(P_EMP_RECORD EMP%ROWTYPE, P_COLUMN VARCHAR2) IS 24 L_VALUE VARCHAR2(4000); 25 BEGIN 26 IF P_EMP_RECORD.EMPNO IS NULL THEN 27 LOG_MESSAGE('No Data Found.'); 28 DBMS_OUTPUT.PUT_LINE('No Data Found.'); 29 ELSE 30 CASE P_COLUMN 31 WHEN 'ENAME' THEN 32 L_VALUE := P_EMP_RECORD.ENAME; 33 WHEN 'SAL' THEN 34 L_VALUE := NVL(P_EMP_RECORD.SAL, 0); 35 ELSE 36 L_VALUE := 'Invalid column'; 37 END CASE; 38 LOG_MESSAGE('About to print ' || P_COLUMN || '=' || L_VALUE); 39 DBMS_OUTPUT.PUT_LINE(P_COLUMN || '=' || L_VALUE); 40 END IF; 41 END; 42 43 PROCEDURE PRINT_ENAME(P_EMPNO NUMBER) IS 44 BEGIN 45 PRINT_DATA(GET_EMP_RECORD(P_EMPNO), 'ENAME'); 46 END; 47 48 PROCEDURE PRINT_SAL(P_EMPNO NUMBER) IS 49 BEGIN 50 PRINT_DATA(GET_EMP_RECORD(P_EMPNO), 'SAL'); 51 END; 52 END; 53 54 / 程序包主体已创建。
(3) 我们来测试程序包,看看它是否可以如我们希望的那样工作。
SQL> exec employee_pkg.print_ename(7781); PL/SQL 过程已成功完成。 SQL> set serverout on SQL> exec employee_pkg.print_ename(7781); No Data Found. PL/SQL 过程已成功完成。 SQL> exec employee_pkg.print_ename(7782); ENAME=CLARK PL/SQL 过程已成功完成。 SQL> select * from log_table; USERNAME DATE_TIME ------------------------------ -------------------------------- MESSAGE --------------------------------------------------------------- SCOTT 13-6月 -13 04.11.57.000000 下午 111 SCOTT 13-6月 -13 04.11.57.000000 下午 111 SCOTT 19-7月 -13 07.53.46.000000 下午 Looking for record where EMPNO=7781 USERNAME DATE_TIME ------------------------------ -------------------------------- MESSAGE --------------------------------------------------------------- SCOTT 19-7月 -13 07.53.46.000000 下午 No Data Found. SCOTT 19-7月 -13 07.53.55.000000 下午 Looking for record where EMPNO=7781 SCOTT 19-7月 -13 07.53.55.000000 下午 No Data Found. USERNAME DATE_TIME ------------------------------ -------------------------------- MESSAGE --------------------------------------------------------------- SCOTT 19-7月 -13 07.54.02.000000 下午 Looking for record where EMPNO=7782 SCOTT 19-7月 -13 07.54.02.000000 下午 About to print ENAME=CLARK 已选择8行。
11.4.4 程序包变量和其它声明
用户可以定义称为全局变量(global variables)的程序包级别的变量。这些变量可以定义在程序包规范中,或者也可以定义在程序包主体中。
定义在规范中的那些变量可以像规范中的过程和函数一样被引用,它们被称为公共变量(public variables)。
试验:程序包变量
(1) 首先,我们使用一个仅有和一个私有程序包级别的变量,以及可以设置和输出私有值的过程来生成程序包(VARIABLES)。由于我们可以直接直接设置和读取公共程序包变量值,所以不需要为那个值编写例程。
SQL> create or replace 2 package variables as 3 g_public_number number:=null; 4 procedure set_private_number(p_num number); 5 procedure print_private_number; 6 end; 7 / 程序包已创建。 SQL> create or replace 2 package body variables as 3 g_private_number number:=null; 4 5 procedure set_private_number(p_num number)is 6 begin 7 g_private_number:=p_num; 8 end; 9 10 procedure print_private_number is 11 begin 12 dbms_output.put_line(nvl(to_char(g_private_number),'null')); 13 end; 14 end; 15 / 程序包主体已创建。
(2) 现在,我们可以直接访问公共程序包变量,重新对其初始化。
SQL> set serverout on SQL> exec dbms_output.put_line(nvl(to_char(variables.g_public_number),'null')); null PL/SQL 过程已成功完成。 SQL> exec variables.g_public_number:=123; PL/SQL 过程已成功完成。 SQL> exec dbms_output.put_line(nvl(to_char(variables.g_public_number),'null')); 123 PL/SQL 过程已成功完成。
(3) 试图访问私有程序包变量将会产生错误消息。
SQL> exec variables.g_private_number:=456; BEGIN variables.g_private_number:=456; END; * ERROR 位于第 1 行: ORA-06550: 第 1 行, 第 17 列: PLS-00302: 必须说明 'G_PRIVATE_NUMBER' 组件 ORA-06550: 第 1 行, 第 7 列: PL/SQL: Statement ignored
(4) 通过公共过程访问私有程序包变量可以工作良好
SQL> exec variables.set_private_number(456); PL/SQL 过程已成功完成。 SQL> exec variables.print_private_number; 456 PL/SQL 过程已成功完成。
(5) 然而,如果我们重新连接,或者使用新的会话,程序包的状态就会重置,已经设置的值就会丢失。
SQL> conn scott/tiger 已连接。 SQL> set serverout on SQL> exec dbms_output.put_line(nvl(to_char(variables.g_public_number),'null')); nul●PL/SQL 过程已成功完成。 SQL> exec variables.print_private_number; nul●PL/SQL 过程已成功完成。
实例化程序块
程序包能够拥有一个代码块,它可以在会话第一次访问程序包元素的时候运行一次。它可以是对规范中所定义过程的调用,或者只是简单读取公共程序包变量的值。第一次访问也称为程序包实例化,这时,程序包将要执行实例化代码。它只能够被调用一次,并且会自动进行。
SQL> create or replace 2 package variables as 3 g_public_number number:=null; 4 procedure set_private_number(p_num number); 5 procedure print_private_number; 6 end; 7 / 程序包已创建。 SQL> create or replace 2 package body variables as 3 g_private_number number:=null; 4 5 procedure set_private_number(p_num number)is 6 begin 7 g_private_number:=p_num; 8 end; 9 10 procedure print_private_number is 11 begin 12 dbms_output.put_line(nvl(to_char(g_private_number),'null')); 13 end; 14 15 begin 16 select count(*) into g_public_number from emp; 17 g_private_number:=dbms_random.random; 18 19 end; 20 / 程序包主体已创建。
第15行到第19行是实例化程序块。这些代码将要在第一次引用程序包的时候执行。由于它是PL/SQL,所以我们可以在它的声明部分初始化G_PRIVATE_NUMBER。
SQL> exec variables.print_private_number; 2011415604 PL/SQL 过程已成功完成。 SQL> exec dbms_output.put_line(nvl(to_char(variables.g_public_number),'null')); 14 PL/SQL 过程已成功完成。
11.4.5 重载
重载(Overloading)是在单独的程序包中定义的共享相同名称的两个或者多个过程和函数。
以下声明是合法的,因为参数类型有所区别:
procedure foo(p_parm1 varchar2); procedure foo(p_parm1 number); procedure foo(p_parm1 number,p_parm2 varchar2); procedure foo(p_parm1 varchar2,p_parm2 varchar2); procedure foo; procedure foo(p_parm1 number); procedure foo return number; procedure foo(p_parm1 number) return varchar2;
作为示例,以下的2个就是非法的,因为NUMBER和REAL都属于相同的数据类型家庭。
procedure foo(p_parm1 number); procedure foo(p_parm2 real);
重载可以让编码更容易。用户可能已经使用过重载函数,只是还没有意识到它的存在。考虑内嵌的 Oracle函数TO_CHAR()。
SQL> select to_char(sysdate,'HH24:MI:SS') "DATE" from dual; DATE -------- 11:48:07 SQL> select to_char(111,'099.99') "NUMBER" from dual; NUMBER ------- 111.00
用户可以传入DATE或者NUMBER,它都可以进行处理。
试验:重载SWAP()
回溯到IN OUT参数的讨论,我们编写了一个SWAP()过程。
SQL> create or replace 2 procedure swap( 3 p_parm1 in out number, 4 p_parm2 in out number)as 5 l_temp number; 6 begin 7 l_temp:=p_parm1; 8 p_parm1:=p_parm2; 9 p_parm2:=l_temp; 10 end; 11 / 过程已创建。
如果用户所需完成的工作是交换数值,那么这就是一个非常实用的过程。我们要包装这个过程,对其进行重载,使它也可以交换VARCHAR2和日期。
(1) 首先,建立规范。
SQL> create or replace 2 package utilities as 3 procedure swap(p_parm1 in out number,p_parm2 in out number); 4 procedure swap(p_parm1 in out varchar2,p_parm2 in out varchar2); 5 procedure swap(p_parm1 in out date,p_parm2 in out date); 6 end; 7 / 程序包已创建。 SQL> create or replace 2 package body utilities as 3 procedure swap(p_parm1 in out number,p_parm2 in out number) is 4 l_temp number; 5 begin 6 dbms_output.put_line('Swapping number'); 7 l_temp:=p_parm1; 8 p_parm1:=p_parm2; 9 p_parm2:=l_temp; 10 end; 11 12 procedure swap(p_parm1 in out varchar2,p_parm2 in out varchar2) is 13 l_temp varchar2(32767); 14 begin 15 dbms_output.put_line('Swapping varchar2'); 16 l_temp:=p_parm1; 17 p_parm1:=p_parm2; 18 p_parm2:=l_temp; 19 end; 20 21 procedure swap(p_parm1 in out date,p_parm2 in out date) is 22 l_temp date; 23 begin 24 dbms_output.put_line('Swapping date'); 25 l_temp:=p_parm1; 26 p_parm1:=p_parm2; 27 p_parm2:=l_temp; 28 end; 29 end; 30 / 程序包主体已创建。
(2) 现在,我们就可以测试已经编写的内容,查看它们的运行情况。
SQL> set serverout on SQL> declare 2 l_num1 number:=1; 3 l_num2 number:=2; 4 l_date1 date:=sysdate; 5 l_date2 date:=sysdate+1; 6 begin 7 utilities.swap(l_num1,l_num2); 8 dbms_output.put_line('l_num1='||l_num1); 9 dbms_output.put_line('l_num2='||l_num2); 10 utilities.swap(l_date1,l_date2); 11 dbms_output.put_line('l_date1='||l_date1); 12 dbms_output.put_line('l_date2='||l_date2); 13 end; 14 / Swapping number l_num1=2 l_num2=1 Swapping date l_date1=21-7月 -13 l_date2=20-7月 -13 PL/SQL 过程已成功完成。
11.4.6 依赖性
就如同数据库中的其它对象一样,过程也具有依赖性,会依次依靠其它的对象。具有外键的表要依赖于它们所参考的表,视图依赖于它们的附属表,过程要依赖于它们所引用的数据库对象(例如表、视图、甚至其它存储过程)。
当用户将过程成功编译进数据库之后,就认为其有效(valid)。用户可以查看数据库片辞典视图USER_OBJECTS来验证这个过程。
SQL> select object_name,status 2 from user_objects 3 where object_type='PROCEDURE'; OBJECT_NAME STATUS ----------------- ----------------- DEFAULT_VALUES VALID EMP_LOOKUP VALID INSERT_NUMBERS INVALID LOG_MESSAGE VALID SWAP VALID THREE_PARMS VALID
试验:依赖性实例
在这里,我们要分析当存储过程依赖的对象发生改变时会出现什么情况。
(1) 我们首先要建立名为BAR的表,存储过程要依赖于它。
SQL> create table bar(n number); 表已创建。 SQL> create or replace procedure foo as 2 l_n bar.n%type; 3 begin 4 null; 5 end; 6 / 过程已创建。
过程要依赖于表BAR,是因为FOO()会声明一个局域变量,它的类型是列N的类型。
(2) 现在,我们可以执行如下命令,查看过程的状态。
SQL> select object_name,status 2 from user_objects 3 where object_type='PROCEDURE'; OBJECT_NAME STATUS ----------------- ----------------- FOO VALID INSERT_NUMBERS VALID 已选择2行。
正如我们所料,过程FOO()编译成功,所以,它的状态是VALID
(3) 当向表BAR中增加列,对其进行修改之后会发生什么。
SQL> alter table bar add c char(1); 表已更改。 SQL> select object_name,status 2 from user_objects 3 where object_type='PROCEDURE'; OBJECT_NAME STATUS ----------------- ----------------- FOO INVALID LOG_MESSAGE VALID 已选择2行。
FOO()现在为INVALID,也就是说,对所引用对象的任何改变都可以使月入民的过程无效。无论改变是否直接影响了过程,对象的改变会使引用它的所有过程无效。
(4) 现在我们来看看,当试图执行过程的时候,会发生什么情况。
SQL> exec foo PL/SQL 过程已成功完成。 SQL> select object_name,status 2 from user_objects 3 where object_type='PROCEDURE'; OBJECT_NAME STATUS ----------------- ----------------- FOO VALID LOG_MESSAGE VALID 已选择2行。
这不仅是一个很好的特性,而且也是必要的特性。假如我们的过程调用SEAN的过程,它又要调用 MARK所拥有的过程,而MARK所拥有的过程要引用JOEL模式中的表。如果JOEL向他的表中增加一个列,那么所有这3个存储过程都被Oracle标记为INVALID。让所有的过程所有者都将它们的过程一直维护为VALID几乎是不可能的。就如用户所见,很小的改变就会很容易涉及整个用户数据库,影响许多对象,使它们无效。
11.4.7 程序包的优势
如果用户正在应用中使用一些低层的代码,而每次都要重新编译代码,那么整个应用都会无效。用户的数据库将要消耗大量的时间来进行重新编译,才可以使用户应用能够执行。对于用户CPU以及Oracle来讲,这都相当耗费时间。程序包可以为我们屏蔽这个问题。
试验:改变程序包
(1) 我们将要建立一个程序包过程SHIELD.FOO()
SQL> create or replace 2 package shield as 3 procedure foo; 4 end; 5 / 程序包已创建。 SQL> create or replace 2 package body shield as 3 procedure foo is 4 l_n bar.n%type; 5 begin 6 null; 7 end; 8 end; 9 / 程序包主体已创建。
(2) 现在,我们来改变表,使其包含一个新列,看看这会产生什么影响。
SQL> alter table bar add d date; 表已更改。 SQL> select object_name,status 2 from user_objects 3 where object_name='SHIELD'; OBJECT_NAME STATUS ----------------- ---------------- SHIELD VALID SHIELD INVALID
这时,我们可以清楚地看到,只有程序包主体变为INVALID。规范和引用它的内容仍为VALID。使用程序包可以让我们进行改变,并且可以避免数据库中大量可能的过程和函数变为无效。
通常,程序包规范不会经常改变,但是它们的实现会发生改变。
11.5 数据词典
11.5.1 列出所有的用户存储过程
通过使用如下查询,用户就可以列出用户模式中的所有过程、函数和程序包。
SQL> select object_name,object_type 2 from user_objects 3 where object_type in('PROCEDURE','FUNCTION','PACKAGE','PACKAGE BODY'); OBJECT_NAME OBJECT_TYPE ----------------- ------------------ DEFAULT_VALUES PROCEDURE EMPLOYEE_PKG PACKAGE EMPLOYEE_PKG PACKAGE BODY EMP_LOOKUP PROCEDURE FIRST_FUNCTION FUNCTION FOO PROCEDURE INSERT_NUMBERS PROCEDURE ITE FUNCTION LOG_MESSAGE PROCEDURE NO_RETURN_TYPE FUNCTION SHIELD PACKAGE OBJECT_NAME OBJECT_TYPE ----------------- ------------------ SHIELD PACKAGE BODY SWAP PROCEDURE THREE_PARMS PROCEDURE TOTAL_COMPENSATIO FUNCTION N UTILITIES PACKAGE UTILITIES PACKAGE BODY VARIABLES PACKAGE VARIABLES PACKAGE BODY 已选择19行。
11.5.2 从数据库中检索用户代码
我们不仅可以找到在指定的模式中有哪些过程,还可以获取构成过程的代码。词典视图USER_SOURCE将会向我们提供这些信息:
SQL> desc user_source 名称 是否为空? 类型 ----------------------------------------- -------- -------------- NAME VARCHAR2(30) TYPE VARCHAR2(12) LINE NUMBER TEXT VARCHAR2(4000) SQL> select text from user_source where name='LOG_MESSAGE'; TEXT ------------------------------------------------------------------ procedure log_message(p_message varchar2) as pragma autonomous_transaction; begin insert into log_table(username,date_time,message) values(user,current_date,p_message); commit; end; 已选择7行。
有时也可以使用SQL*Plus DESCRIBE或者DESC命令来描述程序包:
SQL> desc employee_pkg FUNCTION GET_EMP_RECORD RETURNS RECORD 参数名称 类型 输入/输出默认值? ------------------------------ ----------------------- ------ -------- EMPNO NUMBER(4) OUT ENAME VARCHAR2(10) OUT JOB VARCHAR2(9) OUT MGR NUMBER(4) OUT HIREDATE DATE OUT SA● NUMBER(7,2) OUT COMM NUMBER(7,2) OUT DEPTNO NUMBER(2) OUT P_EMPNO NUMBER IN
11.6 包装实用工具
由于PL/SQL在数据库中以纯文本方式存储,所以Oracle提供了一个实用工具来加密(或者包装)用户的PL/SQL,它会将用户的PL/SQL改变为只有数据库能够解释的代码版本。通过采用这种方式,用户就可以使其它人无法知道用户代码的工作方式,进而保护您的智力成果。
WRAP实用工具位于$ORACLE_HOME/bin,用于WRAP的语法为:
wrap –inname=<input_file_name> [-oname=<output_file_name>]
例如:
D:oracleora92in>set NLS_LANG=USA D:oracleora92in>wrap iname=d:001wrap.sql oname=d:001wrap.plb PL/SQL Wrapper: Release 9.2.0.1.0- Production on Sat Jul 20 16:31:21 2013 Copyright (c) Oracle Corporation 1993, 2001. All Rights Reserved. Processing d:001wrap.sql to d:001wrap.plb D:oracleora92in>
注意1:
如果在执行wrap 时出现类似kgepop: no error frame to pop to for error 1801的错误
请先执行
WINDOWS OS :set NLS_LANG=USA LINUX/UNIX OS :$ unset NLS_LANG
注意2:
用户必须在数据库以外维护一个源文件的副本,包装代码只是单向过程,一旦包装,就不能解除包装。要小心!
即WRAP可以加密 包/过程/函数 不能加密触发器 ,加密后无法解密,请保管好源码。
11.7 小结
在本章中,我们以介绍性的概念分析了在Oracle中进行PL/SQL编程。讨论了基于程序块的编程、以及PL/SQL怎样从逻辑上将代码块划分为职责区域,例如变量和常量声明、执行代码、以及错误控制代码。我们还讨论了PL/SQL中数据类型与SQL中数据类型之间的区别。
游标可以让我们将SQL查询引入到PL/SQL代码中,有效地集成程序的结构与数据库中存储的数据。有大量内建的功能可以帮助操作我们的数据、字符、和数值变量。另外,我们还可以将PL/SQL编程结构划分为条件逻辑、循环语句、以及错误控制代码等。
文章根据自己理解浓缩,仅供参考。
摘自:《Oracle编程入门经典》 清华大学出版社 http://www.tup.com.cn