`
yangzhiyong77
  • 浏览: 972609 次
文章分类
社区版块
存档分类
最新评论

第八章 PL/SQL子程序

 
阅读更多

第八章 PL/SQL子程序

一、什么是子程序

子程序就是能够接受参数并被其他程序所调用的命名PL/SQL块。PL/SQL子程序有两种类型,过程和函数。一般地,过程用于执行一个操作,而函数用于计算一个结果值。

与未命名或匿名PL/SQL块一样,子程序也有声明部分,执行部分和一个可选的异常处理部分。声明部分包含类型、游标、常量、变量、异常和嵌套子程 序的声明。这些内容都是本地的,在程序退出时会自动销毁。执行部分包含赋值语句、流程控制语句和Oracle的数据操作语句。异常处理部分包含异常处理程 序。思考下面用于记入借方银行账户的debit_account过程:

PROCEDURE debit_account(acct_idINTEGER ,amountREAL )IS
old_balanceREAL ;
new_balanceREAL ;
overdrawnEXCEPTION ;
BEGIN
SELECT bal
INTO old_balance
FROM accts
WHERE acct_no=acct_id;

new_balance:=old_balance-amount;

IF new_balance<0THEN
RAISE overdrawn;
ELSE
UPDATE accts
SET bal=new_balance
WHERE acct_no=acct_id;
END IF ;
EXCEPTION
WHEN overdrawnTHEN
...
END debit_account;

在被调用时,这个过程接受一个银行账号和借贷金额。它使用账号从accts表中查询账目结算信息。然后用借款金额计算新的账目结算。如果计算后的余额比零小,异常就会被抛出;否则,该账号相关信息就会被更新。

二、子程序的优点

子程序能提供扩展性,它能够让我们根据自己的需求来编写特定的PL/SQL。比如,我们需要一个能够创建新部门的过程,就可以像下面这样编写代码:

PROCEDURE create_dept(new_dnameVARCHAR2 ,new_locVARCHAR2 )IS
BEGIN
INSERT INTO dept
VALUES (deptno_seq.NEXTVAL ,new_dname,new_loc);
END create_dept;

子程序还能提供模块化,就是说它可以把一个程序定义成多个模块,更易管理。这样,我们就可以用自顶而下的设计(top-down design)和逐步求精(stepwise refinement)的方法来解决问题。

此外,子程序在提高程序的重用性和可维护方面也是很有用的。只要编译成功,子程序就可以放心地用在很多应用程序中。如果它的定义内容发生了改变,受 到影响的只有子程序本身而已,这就简化了维护过程。最后,子程序还有助于逻辑的抽象。使用子程序时,我们需要知道的是它们的功能,而不是它们实现功能的细 节问题。

三、理解PL/SQL过程

过程是一个能执行某个特定操作的子程序。我们可以用下面的语法来编写过程:

[CREATE [OR REPLACE]]
PROCEDURE procedure_name[(parameter[,parameter]...)]
[AUTHID {DEFINER|CURRENT_USER}]{IS |AS }
[PRAGMA AUTONOMOUS_TRANSACTION;]
[localdeclarations]
BEGIN
executablestatements
[EXCEPTION
exceptionhandlers]
END [name];

parameter的含义如下:

parameter_name[IN |OUT [NOCOPY ]|IN OUT [NOCOPY ]]datatype
[{:=|DEFAULT }expression]

CREATE子句能让我们创建保存在数据库中的独立过程。我们可以从SQL*Plus中或是在使用动态SQL的程序中执行CREATE PROCEDURE语句。

AUTHID子句决定了存储过程是按所有者权限(默认)调用还是按当前用户权限执行,也能决定在没有限定修饰词的情况下,对所引用的对象是按所有者模式进行解析还是按当前用户模式进行解析。我们可以指定CURRENT_USER来覆盖掉程序的默认行为。

编译指示AUTONOMOUS_TRANSACTION会告诉PL/SQL编译器把过程标记为自治(独立)。自治事务能让我们把主事务挂起,执行SQL操作,提交或回滚自治事务,然后再恢复主事务。

我们不能对参数的数据类型进行约束,如下例中对acct_id的声明就是不合法的,因为它对CHAR类型进行了长度限制:

PROCEDURE reconcile(acct_idCHAR (5))IS ...--illegal

但是,我们可以使用下面的方法间接的对字符的长度进行限制:

DECLARE
SUBTYPE Char5IS CHAR (5);
PROCEDURE reconcile(acct_idChar5)IS ...

过程有两个部分,过程说明和过程体。说明部分由关键字PROCEDURE开头,以过程名或参数列表结尾。参数声明是可选的。没有参数的过程是不用使用圆括号的。

过程体由关键字IS(或AS)开头,并以END结尾,END后面可以跟上一个可选的过程名。过程体有三个部分:声明、执行和可选的异常处理。

声明部分包括本地声明,它处于IS和BEGIN之间。在匿名PL/SQL块使用的关键字DECLARE在这里不再需要。执行部分包括许多语句,它们 被放到BEGIN和EXCEPTION(或END)之间,并且至少要有一条语句出现在过程的执行部分。NULL语句可以满足这个需求。异常处理部分包含异 常处理程序,它被放在关键字EXCEPTION和END之间。

在下面的过程raise_salary中,我们会根据给定的金额来为雇员加薪:

PROCEDURE raise_salary(emp_idINTEGER ,amountREAL )IS
current_salaryREAL ;
salary_missingEXCEPTION ;
BEGIN
SELECT sal
INTO current_salary
FROM emp
WHERE empno=emp_id;

IF current_salaryIS NULL THEN
RAISE salary_missing;
ELSE
UPDATE emp
SET sal=sal+amount
WHERE empno=emp_id;
END IF ;
EXCEPTION
WHEN NO_DATA_FOUNDTHEN
INSERT INTO emp_audit
VALUES (emp_id,'Nosuchnumber' );
WHEN salary_missingTHEN
INSERT INTO emp_audit
VALUES (emp_id,'Salaryisnull' );
END raise_salary;

在调用时,过程接受雇员编号和薪资调整金额,然后用雇员编号从emp表找出指定雇员的当前工资。如果雇员编号无法找到或是当前工资为空,异常就会被抛出,否则工资就会被更新。

过程可以作为一个PL/SQL语句来调用。例如,我们可以像下面这样调用raise_salary:

raise_salary(emp_id,amount);

四、理解PL/SQL函数

函数是一个能够计算结果值的子程序,函数除了有一个RETURN子句之外,其它结构跟过程类似。我们可以用下面的语法来编写(本地)函数:

[CREATE [OR REPLACE]]
FUNCTION function_name[(parameter[,parameter]...)]RETURN datatype
[AUTHID {DEFINER|CURRENT_USER}]
[PARALLEL_ENABLE
[{[CLUSTER parameterBY (column_name[,column_name]...)]|
[ORDER parameterBY (column_name[,column_name]...)]}]
[(PARTITION parameterBY
{[{RANGE |HASH}(column_name[,column_name]...)]|ANY }
)]
]
[DETERMINISTIC][PIPELINED[USINGimplementation_type]]
[AGGREGATE[UPDATE VALUE][WITH EXTERNALCONTEXT]
USINGimplementation_type]{IS |AS }
[PRAGMA AUTONOMOUS_TRANSACTION;]
[localdeclarations]
BEGIN
executablestatements
[EXCEPTION
exceptionhandlers]
END [name];

函数的语法结构与过程类似,这里就不再重复。但有几个不同点还是需要注意的。

PARALLEL_ENABLE选项能声明一个在并发DML操作的从属会话(slave session)中被安全调用的存储函数。主(logon)会话的状态不会被从属会话所共享。每个从属会话都有它自己的状态,这是在会话开始时初始化的。 函数的结果不应依赖于会话(静态)变量的状态。否则结果就可能随着会话而发生变化。

提示DETERMINISTIC能帮助优化程序避免冗余的函数调用。如果存储函数的调用跟前一次调用时所使用的参数相同,优化程序就直接选出前一次 的计算结果值。函数结果不应该依赖于会话变量或模式对象的状态。否则结果会随着调用而发生变化。只有DETERMINISTIC函数才允许被函数索引或是 参数query_rewrite_enabled为TRUE的实体化视图调用。

我们不能对参数或是函数返回值的类型添加约束,但可以像前面的过程那样使用间接的约束方法。

思考下面的函数sal_ok,它的作用是检查工资是否超出限定范围:

FUNCTION sal_ok(salaryREAL ,titleVARCHAR2 )
RETURN BOOLEAN IS
min_salREAL ;
max_salREAL ;
BEGIN
SELECT losal,hisal
INTO min_sal,max_sal
FROM sals
WHERE job=title;

RETURN (salary>=min_sal)AND (salary<=max_sal);
END sal_ok;

调用时,函数接受雇员的工资金额和职别,然后使用职别从数据表sals选择工资范围。函数标识符sal_ok由RETURN语句返回的布尔值赋值。如果salary超过限定范围,sal_ok值就是FALSE,否则就是TRUE。

函数可以作为表达式的一部分而被调用,如下例所示。函数标识符sal_ok就像一个变量一样,它的值是由传递进去的参数所决定的。

IF sal_ok(new_sal,new_title)THEN ...

1、使用RETURN语句

RETURN语句能够立即结束当前执行的子程序并把控制权交还给调用者。然后程序继续执行子程序调用之后的语句。(不要把RETURN语句和函数声明中的RETURN子句搞混淆了,声明中的RETURN只是用于指明函数返回值的数据类型。)

子程序能包含几个RETURN语句。最后一个语句不一定非得是RETURN语句。只要执行RETURN语句就能立即结束当前子程序。但是,在一个子程序包含有多个出口点不是一个好习惯。

在过程中,RETURN语句不能返回值,也不能返回任何表达式。它的作用只是在过程到达正常的过程结尾之前将控制权交给调用者。

但是,在函数中,RETURN语句必须包含一个表达式,该表达式的值会在RETURN语句执行时被计算。计算的结果赋给函数标识符,标识符的作用相当于RETURN说明中的类型的变量。观察下面返回指定银行账户的余额的函数balance:

FUNCTION balance(acct_idINTEGER )
RETURN REAL IS
acct_balREAL ;
BEGIN
SELECT bal
INTO acct_bal
FROM accts
WHERE acct_no=acct_id;

RETURN acct_bal;
END balance;

下例演示了如何在RETURN语句中使用复杂的表达式:

FUNCTION compound(yearsNUMBER ,amountNUMBER ,rateNUMBER )
RETURN NUMBER IS
BEGIN
RETURN amount*POWER((rate/100)+1,years);
END compound;

函数中,至少要有一条执行路径能够到达RETURN语句。否则,在运行时就会得到函数没有返回值的错误。

2、控制PL/SQL子程序的副影响

为了能在SQL中被调用,存储函数必须遵守以下"纯度"规则,这些规则能控制函数副作用:

  1. 当从SELECT语句或是并发的INSERT、UPDATE、DELETE语句中调用函数时,它不能修改任何数据表。
  2. 当从INSERT、UPDATE或DELETE语句中调用函数时,它不能查询或修改这些语句所能影响到的数据表。
  3. 当 从SELECT、INSERT、UPDATE或DELETE语句中调用函数时,它不能执行SQL事务控制语句(如COMMIT),会话控制语句(如SET ROLE)或系统控制语句(如ALTER SYSTEM)。同样,它也不能执行数据定义语句(如CREATE),因为这些语句都是自动提交事务的。

如果函数内部任何一条SQL语句与上述规则相冲突,我们就会在运行时得到错误(语句被分析的时候)。

为了检查这些冲突项,我们可以使用编译指示RESTRICT_REFERENCES(编译器指令)。编译指示能判断函数是否读写数据表或包中的变量。例如在下面的函数中,编译指示就能判断出函数credit_ok不写数据库(WNDS)也不读取包(RNPS):

CREATE PACKAGE loansAS
...
FUNCTION credit_okRETURN BOOLEAN ;
PRAGMA RESTRICT_REFERENCES(credit_ok,WNDS,RNPS);
END loans;

注意:一个静态的INSERT、UPDATE或DELETE语句总是与WNDS相冲突的;如果读取了数据库字段,它也会与RNDS冲突。一个动态INSERT、UPDATE或DELETE语句总与WNDS和RNDS冲突。

五、声明PL/SQL子程序

我们可以在PL/SQL块、子程序或包中声明子程序。但是,子程序只能在其他内容声明之后再声明。

PL/SQL需要我们先声明标识然后才能引用它们。所以,在使用子程序之前必须要先声明。例如,下面对过程award_bonus的声明就是非法的,因为它在过程calc_rating未声明之前就开始调用它:

DECLARE
...
PROCEDURE award_bonusIS
BEGIN
calc_rating(...);--undeclaredidentifier
...
END ;
PROCEDURE calc_rating(...)IS
BEGIN
...
END ;

这种情况下,我们只要简单地把过程calc_rating放到award_bonus之前就可以了。但是简单的做法不一定总是有效的,如:两个过程相互引用或是我们就想按逻辑或字母顺序来定义他们。

我们可以使用"向前声明"来解决这个问题,向前声明由子程序说明和一个分号组成。在下面的例子中,向前声明通知PL/SQL,过程calc_rating的体部分可以在块的后面找到。

DECLARE
PROCEDURE calc_rating(...);--forwarddeclaration
...

虽然形式参数列表在向前声明中已经出现过了,但它也必须出现在子程序体中。子程序体可以放在向前声明之后的任何地方,但它们必须出现在同一个程序单元中。

六、子程序打包

我们可以把逻辑相关的子程序打包后放到数据库中。那样,子程序就能被许多应用程序共享。子程序说明部分放在包说明部分;子程序体放在包体,子程序体对应用程序是不可见的。因此,包能帮助我们隐藏程序的实现细节。如下例:

CREATE PACKAGE emp_actionsAS --packagespec
PROCEDURE hire_employee(emp_idINTEGER ,NAMEVARCHAR2 ,...);

PROCEDURE fire_employee(emp_idINTEGER );

PROCEDURE raise_salary(emp_idINTEGER ,amountREAL );
...
END emp_actions;

CREATE PACKAGE BODY emp_actionsAS --packagebody
PROCEDURE hire_employee(emp_idINTEGER ,NAMEVARCHAR2 ,...)IS
BEGIN
...
INSERT INTO emp
VALUES (emp_id,NAME,...);
END hire_employee;

PROCEDURE fire_employee(emp_idINTEGER )IS
BEGIN
DELETE FROM emp
WHERE empno=emp_id;
END fire_employee;

PROCEDURE raise_salary(emp_idINTEGER ,amountREAL )IS
BEGIN
UPDATE emp
SET sal=sal+amount
WHERE empno=emp_id;
END raise_salary;
...
END emp_actions;

我们还能够直接在包体内定义子程序而不用在包说明部分编写它们的说明。但是,这样的子程序只能在包内的使用。

七、形参VS实参

子程序使用参数来传递信息。调用时子程序参数列表中引用的变量或表达式是实际参数(actual parameter,以下简称实参)。例如,下面的过程列出了两个实参emp_num和amout:

raise_salary(emp_num,amount);

下面的过程调用演示了用表达式作为实参:

raise_salary(emp_num,merit+cola);

子程序声明和子程序体中引用的变量是形式参数(formal parameter,以下简称形参)。例如,下面的过程声明了两个形参emp_id和amount:

PROCEDURE raise_salary(emp_idINTEGER ,amountREAL )IS
BEGIN
UPDATE emp
SET sal=sal+amount
WHERE empno=emp_id;
END raise_salary;

好的编程习惯就是使用不同命名的形参和实参。

调用过程raise_salary时,实参的内容会被赋值到对应的形参上,如果有必要的话,在赋值之前PL/SQL会帮助我们进行类型转换。例如,下面对raise_salary的调用就是有效的:

raise_salary(emp_num,'2500' );

实参和它对应的形参必须类型兼容,例如,PL/SQL是不能把数据从DATE类型转到REAL类型的。下面的过程调用就会引起预定义异常VALUE_ERROR,因为PL/SQL不能把第二个实参转成一个数字:

raise_salary(emp_num,'$2500' );--notethedollarsign

八、位置标示法VS名字标示法

在调用子程序时,我们既可以使用位置标示法又可以使用名字标示法来编写实参。也就是说,我们可以按位置或名称来把实参和形参关联起来。如下例所示:

DECLARE
acctINTEGER ;
amtREAL ;

PROCEDURE credit_acct(acct_noINTEGER ,amountREAL )IS ...

我们可以使用四种等价的方法来调用过程credit_acct:

BEGIN
credit_acct(acct,amt);--positionalnotation
credit_acct(amount=>amt,acct_no=>acct);--namednotation
credit_acct(acct_no=>acct,amount=>amt);--namednotation
credit_acct(acct,amount=>amt);--mixednotation

1、使用位置标示法

第一个过程调用使用了位置标示法。PL/SQL编译器将第一个实参acct和第一个形参acct_no关联,并把第二个实参amt和第二个形参amount关联。

2、使用名字标示法

第二个过程调用使用了名字标示法。箭头(=>)作为关联操作符,把左边的实参和右边的形参关联起来。

第三个过程调用也使用了名字标示法,而且我们可以随意安排参数的位置。所以,我们不需要知道形参的在参数列表中的顺序。

3、使用混合标示法

第四个过程调用使用了名字标示法和位置标示法。在这种情况下,位置标示法必须在名字标示法之前,不能反过来使用,像下面这样的调用方法就是不合法的:

credit_acct(acct_no=>acct,amt);--illegal

九、指定子程序参数模式

我们可以使用参数的模式来定义形式参数的行为。一共有三种模式:IN、OUT和IN OUT。但是,最好避免在函数中使用OUT和IN OUT模式。函数的作用是用来接受零个或多个参数然后返回一个值。用函数返回多个值不是个好习惯。同样,函数应该避免产生负影响,那样会改变那些对子程序 来说是非本地的变量值。

1、使用IN模式

IN模式能让我们把值传递给被调用子程序,在子程序中,IN模式参数的作用就像常量一样。因此,它不能被赋值。例如,下面的赋值语句就会引起编译错误:

PROCEDURE debit_account(acct_idIN INTEGER ,amountIN REAL )IS
minimum_purchaseCONSTANT REAL DEFAULT 10.0;
service_chargeCONSTANT REAL DEFAULT 0.50;
BEGIN
IF amount<minimum_purchaseTHEN
amount:=amount+service_charge;--causescompilationerror
END IF ;
...
END debit_account;

IN模式形参对应的实参可以是常量、文字、被初始化的变量或是表达式。与OUT和IN OUT模式的参数不同,我们可以为IN模式的参数初始化一个默认值。

2、使用OUT模式

OUT模式的参数能让我们把值返回给子程序的调用者。在子程序中,OUT模式参数的作用就像变量。这也就意味着我们可以把它当作本地变量来使用,例如:

PROCEDURE calc_bonus(emp_idIN INTEGER ,bonusOUT REAL )IS
hire_dateDATE ;
bonus_missingEXCEPTION ;
BEGIN
SELECT sal*0.10,hiredate
INTO bonus,hire_date
FROM emp
WHERE empno=emp_id;

IF bonusIS NULL THEN
RAISE bonus_missing;
END IF ;

IF MONTHS_BETWEEN(SYSDATE ,hire_date)>60THEN
bonus:=bonus+500;
END IF ;
...
EXCEPTION
WHEN bonus_missingTHEN
...
END calc_bonus;

与OUT模式的形参对应的实参必须是变量;它不能是常量或表达式。例如,下面的调用就不合法:

calc_bonus(7499,salary+commission);--causescompilationerror

一个OUT实参在子程序调用之前是可以有值的。但是,在子程序调用时,这个值就会丢失,除非我们使用了NOCOPY编译器提示或是子程序因未捕获异常而终止。

与变量一样,OUT模式的形参会被初始化为NULL.所以,一个OUT模式的形参的数据类型是不能有NOT NULL约束的(包括内置类型NATURALN和POSITIVEN)。否则的话,PL/SQL就会抛出VALUE_ERROR异常,见下例:

DECLARE
SUBTYPE counterIS INTEGER NOT NULL ;

ROWScounter:=0;

PROCEDURE count_emps(nOUT counter)IS
BEGIN
SELECT COUNT(*)
INTO n
FROM emp;
END ;
BEGIN
count_emps(ROWS);--raisesVALUE_ERROR

在子程序退出之前,它必须要显式地为所有的OUT模式形参赋值。否则对应的实参值就为空。如果成功地退出子程序,PL/SQL就会把值赋给实参。但是,如果有未捕获异常发生,PL/SQL就不会为实参赋值。

3、使用IN OUT模式

一个IN OUT模式的参数能让我们把它的初始值传递给被调用的子程序,然后再把子程序更新后的值传递给调用者。在子程序中,一个IN OUT模式参数的作用就像一个初始化了的变量。因此,它能够被赋值,而且它的值还可以赋给其他的变量。

与IN OUT模式形参对应的实参必须是变量;它不可以是常量或表达式。如果成功地退出子程序,PL/SQL就会为实参赋值。但是,如果有未捕获异常发生,PL/SQL就不会为实参赋值。

4、子程序参数模式总结

下表总结了我们应该知道关于参数模式的所有内容:

IN OUT IN OUT
默认 必须被指定 必须被指定
向子程序传值 向调用者返回值 向子程序传递初始值并向调用者返回
更新后的结果值
形参的作用同常量相同 形参的作用同变量相同 形参的作用同被初始化过的变量相同
形参不能被赋值 形参必须被赋值 形参应该被赋值
实参可以是常量、被初始化的变量、
文字或表达式
形参必须是变量 形参必须是变量
形参按引用传递 形参按值传递,除非使用了NOCOPY 形参按值传递,除非使用了NOCOPY

十、使用NOCOPY编译提示传递大型数据结构

假定子程序声明了一个IN模式参数、一个OUT模式参数和一个IN OUT模式参数。在调用子程序时,IN模式的是按引用传递的,即把指向IN模式的实参指针赋给形参。所以,两个参数引用都指向同一块内存地址,这块内存存放了实参的值。

默认情况下,OUT和IN OUT模式的参数都是按值传递的。就是把实参的值拷贝到对应的形参上。然后,如果子程序正常结束,被赋到OUT和IN OUT形参上的值就会拷贝到对应的实参上。

当参数是大型数据结构时,如集合、记录和对象实例,把它们的内容全部拷贝给形参会降低执行速度,消耗大量内存。为了防止这样的情况发生,我们可以使 用NOCOPY提示来让编译器按引用传递OUT和IN OUT模式的参数。在下面的例子中,我们请求编译器按引用的方式来传递IN OUT参数my_staff:

DECLARE
TYPE StaffIS VARRAY(200)OF Employee;
PROCEDURE reorganize(my_staffIN OUT NOCOPY Staff)IS ...

记住,NOCOPY只是一个提示,而不是指令。所以,编译器也许仍旧会把my_staff按值传递,即使我们已经发出请求了。但是,通常情况下 NOCOPY是可以成功的。下例中,我们把一个含有25000条记录的本地嵌套表中分别传递给两个没有任何功能的过程。没有使用NOCOPY的记录花费 21秒,而使用的花费不到1秒:

SQL >SET SERVEROUTPUTON
SQL >GETtest.sql
1DECLARE
2TYPE EmpTabTypIS TABLE OF emp%ROWTYPE ;
3emp_tabEmpTabTyp:=EmpTabTyp(NULL );--initialize
4t1NUMBER (5);
5t2NUMBER (5);
6t3NUMBER (5);
7PROCEDURE get_time(tOUT NUMBER )IS
8BEGIN SELECT TO_CHAR(SYSDATE ,'SSSSS' )INTO tFROM dual;END ;
9PROCEDURE do_nothing1(tabIN OUT EmpTabTyp)IS
10BEGIN NULL ;END ;
11PROCEDURE do_nothing2(tabIN OUT NOCOPY EmpTabTyp)IS
12BEGIN NULL ;END ;
13BEGIN
14SELECT *INTO emp_tab(1)FROM empWHERE empno=7788;
15emp_tab.EXTEND(24999,1);--copyelement1into2..25000
16get_time(t1);
17do_nothing1(emp_tab);--passINOUTparameter
18get_time(t2);
19do_nothing2(emp_tab);--passINOUTNOCOPYparameter
20get_time(t3);
21dbms_output.put_line('CallDuration(secs)' );
22dbms_output.put_line('--------------------' );
23dbms_output.put_line('JustINOUT:' ||TO_CHAR(t2-t1));
24dbms_output.put_line('WithNOCOPY:' ||TO_CHAR(t3-t2));
25*END ;
SQL >/
CallDuration(secs)
--------------------
JustIN OUT :21
WithNOCOPY :0

1、权衡NOCOPY所带来的良好性能

NOCOPY能为我们带来良好的性能,但它也能带来以下几个方面的影响:

  1. 因为NOCOPY只是一个提示,不是指令,所以编译器可以把NOCOPY参数按值或按引用的方式传递给子程序。所以,如果子程序因发生未捕获异常而退出时,我们就不能再信赖实参中的值了。
  2. 默 认地,如果子程序异常退出,赋给OUT和IN OUT参数的值就不会拷贝到对应的实参上,这看起来有点像回滚操作。但是,对于按引用传递的NOCOPY参数来说,我们对形参所作的更改会立即在对应的实 参上体现出来。所以,即使子程序是因异常发生而结束,它所做的变更内容也不会"回滚"。
  3. 目前,RPC协议允许我们只按值传递参数。例如,如果我们把一个含有NOCOPY参数的本地过程传到远程站点,这些参数就不再按引用传递了。

还有,使用NOCOPY会增加参数别名出现的可能性。

2、NOCOPY的限制

在以下几种情况中,PL/SQL编译器会忽略NOCOPY提示而直接使用按值传递参数的方法(不发生错误的情况下):

  1. 实参是索引表中的一个元素。这个限制并不适用于整个索引表。
  2. 实参是受约束的(如精度或NOT NULL等)。这个约束不会扩展到元素或属性。同样,对长度受限的字符串也不适用。
  3. 实参和形参都是记录,其中一个或两个使用了%ROWTYPE或%TYPE声明,且在记录中对应域的约束不同。
  4. 实参和形参都是记录,实参是作为游标FOR循环的索引而被声明的(隐式声明),记录之间对应域的约束不同。
  5. 实参传递需要进行隐式地数据类型转换。
  6. 子程序被外部或远程过程调用。

十一、使用子程序参数的默认值

如下例所示,我们可以为IN模式的参数初始化默认值。这样,我们就可以把不同个数的参数传给子程序,其中既可以使用参数默认值又可以使用我们传入的参数值覆盖掉默认值。并且,我们还可以在不修改每个子程序调用的情况下添加新的参数。

PROCEDURE create_dept(
new_dnameVARCHAR2 DEFAULT ’temp’,
new_locVARCHAR2 DEFAULT ’temp’
)IS
BEGIN
INSERT INTO dept
VALUES (deptno_seq.NEXTVAL ,new_dname,new_loc);
...
END ;

如果实参没有被传入,它所对应的形参就会使用定义时的默认值。下面是对过程create_dept的调用:

create_dept;
create_dept('MARKETING' );
create_dept('MARKETING' ,'NEWYORK' );

第一个调用没有传入任何实参,所以子程序会使用两个默认的参数值;而第二个调用只为第一个参数指定了实参,这样,子程序会使用第一个参数传入的值和 第二个参数的默认值;最后一个调用接受两个实参,它们将对应的形参默认值覆盖,子程序就使用两个传入的值。通常我们使用位置标示法覆盖形参的默认值,但 是,我们不能靠省略实参来跳过它们对应的形参。例如,像下面这样把实参值"NEW YORK"和形参new_dname关联的做法是不对的:

create_dept('NEWYORK' );--incorrect
create_dept(,'NEWYORK' );--notallowed

我们也不可以靠放置一个占位符来解决这个问题。例如,下面的调用就是不允许的:

create_dept(,'NEWYORK' );--notallowed

当出现这种情况,我们就需要使用名字标示法:

create_dept(new_loc=>'NEWY

分享到:
评论

相关推荐

    PL/SQL 用户指南与参考

    PL/SQL 用户指南与参考 第一章 PL/SQL一览 第二章 PL/SQL基础 第三章 PL/SQL数据类型 ...第八章 PL/SQL子程序 第九章 PL/SQL包 第十章 PL/SQL对象类型 第十一章 本地动态SQL 第十二章 PL/SQL应用程序性能调优

    PL-SQL用户指南与参考

    · 第八章 PL/SQL子程序 2008-04-08 · 第七章 控制PL/SQL错误 2008-04-08 · 第六章 PL/SQL与Oracle间交互 2008-04-08 · 第五章 PL/SQL集合与记录(2) 2008-04-08 · 第五章 PL/SQL集合与记录(1) ...

    PL/SQL经典介绍

    第一章 PL-SQL一览 第二章 PL-SQL基础 第三章 PL-SQL数据类型 第四章 PL-SQL的控制结构 ...第九章 PL-SQL子程序 第十章 PL-SQL包 第十一章 PL-SQL对象类型 第十二章 本地动态SQL 第十三章 PL-SQL应用程序性能调优

    Oracle8i PL/SQL高级程序设计 高清晰版

    共分为7部分(7个pdf文档...第一部分:PL/SQL介绍及开发环境 1.PL/SQL介绍 2.PL/SQL开发和运行环境 3.跟踪和调试 第二部分:非对象功能 4.创建子程序和包 5.使用子程序和包 6.数据库触发器 7.数据库作业和文件输入输出

    PL/SQL课件

    异常处理(第8~10章) 游标(第11~12章) 触发器(第13~14章) 复合数据类型(第15~16章) 动态SQL(第17章) 批量SQL(第18章) 子程序和包(第19~22章) Oracle中对象类型(第23章) Oracle提供的包(第24章)

    PLSQL用户指南与参考.pdf

    目 录 第一章 PL/SQL 一览 第二章 PL/SQL 基础 第三章 PL/SQL 数据类型 ...第八章 PL/SQL 子程序 第九章 PL/SQL 包 第十章 PL/SQL 对象类型 第十一章 本地动态 SQL 第十二章 PL/SQL 应用程序性能调优

    PL/SQL 基础.doc

    ---- 第一章 PL/SQL 简介 ---- 1. Oracle应用编辑方法概览 1) Pro*C/C++/... : C语言和数据库打交道的方法,比OCI更常用; 2) ODBC 3) OCI: C语言和数据库打交道的方法,和Pro*C很相似,更底层,很少用 只适合...

    精通Oracle.10g.PLSQL编程

    编写控制结构 7.1 条件分支语句 7.2 CASE语句 7.3 循环语句 7.4 顺序控制语句 7.5 习题 第8章 使用复合数据类型 8.1 PL/SQL记录 8.1.1 定义PL/SQL记录 8.1.2 使用PL/SQL...

    PLSQL程序设计

    第八章 触发器 13 §8.1 触发器类型 13 §8.1.1 DML触发器 13 §8.1.2 替代触发器 13 §8.1.3 系统触发器 13 §8.2 创建触发器 13 §8.2.1 触发器触发次序 13 §8.2.2 创建DML触发器 13 §8.2.3 创建替代(Instead_of...

    PLSQL基础教程

    第八章 触发器 13 §8.1 触发器类型 13 §8.1.1 DML触发器 13 §8.1.2 替代触发器 13 §8.1.3 系统触发器 13 §8.2 创建触发器 13 §8.2.1 触发器触发次序 13 §8.2.2 创建DML触发器 13 §8.2.3 创建替代...

    plsql_oracle 8i 编程讲义

    第八章 触发器 13 §8.1 触发器类型 13 §8.1.1 DML触发器 13 §8.1.2 替代触发器 13 §8.1.3 系统触发器 13 §8.2 创建触发器 13 §8.2.1 触发器触发次序 13 §8.2.2 创建DML触发器 13 §8.2.3 创建替代(Instead_of...

    oracle学习资料

    第八章 触发器 13 §8.1 触发器类型 13 §8.1.1 DML触发器 13 §8.1.2 替代触发器 13 §8.1.3 系统触发器 13 §8.2 创建触发器 13 §8.2.1 触发器触发次序 13 §8.2.2 创建DML触发器 13 §8.2.3 创建替代(Instead_of...

    plsql_oracle 编程

    第八章 触发器 13 §8.1 触发器类型 13 §8.1.1 DML触发器 13 §8.1.2 替代触发器 13 §8.1.3 系统触发器 13 §8.2 创建触发器 13 §8.2.1 触发器触发次序 13 §8.2.2 创建DML触发器 13 §8.2.3 创建替代(Instead_of...

    Oracle10数据库电子教案

    Oracle10数据库电子教案 第1章 Oracle Database 10g数据库基础 ...第8章 存储过程和触发器 第9章 序列、同义词、程序包 第10章 Oracle数据库安全管理 第11章 数据库的备份与恢复 第12章 数据库应用系统开发

    Oracle课件.pdf

    PL/SQL程序设计 1. PL/SQL简介 2. PL/SQL基础 2.1声明 2.2条件控制 2.3循环控制 2.4游标 2.5动态SQL 3.创建存储过程 4.创建自定义函数 5.异常处理 6.同步数据 7.创建包 8.创建日志 第6章

    《Oracle数据库开发实用教程》电子教案

    总 目 录 第1章 数据库引论 第2章 Oracle数据库实用工具 第3章 SQL语言与查询 第4章 数据的定义、操纵与...第8章 数据库对象 第9章 权限、角色与用户 第10章 Oracle数据库的逻辑备份 第11章 数据库设计过程与方法

    oracle数据库11G初学者指南.Oracle.Database.11g,.A.Beginner's.Guide

    《Oracle Database 11g初学者指南》能使读者快捷地掌握Oracle Database 11g的基础知识。通过自我评估教程,介绍了核心数据库技术、...第8章 高可用性:RAC、ASM和Data Guand 第9章 大型数据库特性 附录 各章测验答案

    北大青鸟Oracle教程集

    PPT目录: 第一章 Oracle入门; 第二章 查询和SQL函数; 第三章 锁和表分区; 第四章 数据库对象; 第五章 Oracle 中的 OOP ...第八章 子程序和程序包; 第九章 数据库触发器和内置程序包; 第十章 集合和成员函数。

    数据库基础

    第八章 一些高级的用法 188 §8.1 关于DECODE 188 §8.1.1 DECODE 中的if-then-else逻辑 188 §8.1.2 DECODE 的简单例子 188 §8.1.3 DECODE实现表的转置 189 §8.2 关于访问远程数据库 192 §8.2.1 数据库链接 192 ...

    Oracle实际操作应用

    oracle的一些简单应用! 第一章 走进Oracle 1 第二章 SQL数据操作和查询 第三章 常用函数、事务...第七章 子程序和程序包 第八章 游标、内置程序包 第九章 触发器、数据字典 第十章 数据库管理 附录 数据库导入导出

Global site tag (gtag.js) - Google Analytics