ORACLE PL/SQL子程序--过程和函数学习笔记
2009-10-26 21:35
1021 查看
温故而知新,果然如此呀,第二次再翻开同样的内容果然有不同的收获,有些是第一次看的时候没有仔细理解的,还有些可能是在第一次看匆匆就跳过的,当然,可能还有部分是自己当时记住了完了又给忘记了。今天第二次看到子程序这一章节,发现了些新的内容,呵呵。在这里我就写下一些基本内容和容易忘记的,免得下次又给忘了。内容可能不太全面,有点针对我个人哦,呵呵!
1.创建子程序的语法
创建过程
创建函数
2.参数模式
IN
调用过程的时候,实际参数的值就会传递到该过程中。在过程内部,形式参数就像PL/SQL的常量一样--它会被看作是只读且不能修改的。当过程执行完毕,程序的控制返回到调用环境的时候,实际参数的值不会有任何变化。
OUT
调用过程的时候,会忽略所有实际参数的值。在过程内部,形式参数就像未初始化的PL/SQL变量一样,也会有一个NULL值。形式参数的值既可以读取,也可以写入。过程执行完成以后,控制就会返回到调用环境,同时会将形式参数的值赋给实际参数。
IN OUT
这是IN和OUT的复合模式。调用过程的时候,实际参数的值会传递到过程中。在过程内部,形式参数就像初始化以后的变量,可以读取和写入。过程结束以后,控制会返回到调用环境,同时会将形式参数的内容分配给实际参数。
3.以传值和传引用的方式传递参数
传递子程序参数的方式有两种--传值和传引用。当以引用的方式传递参数的时候,就将指向实际参数的一个指针传递到相应的形式参数。另一方面,当以传值的方式传递参数的时候,就将实际参数的值复制到相应的形式参数。以引用的方式传递参数通常会更快,因为它避免了复制。对集合类型的参数而言,这表现更加明显,因为集合类型的数据一般都非常多。
默认情况下,PL/SQL对IN参数都使用传引用的方式,而对IN OUT和OUT参数都使用传值的方式。
1)NOCOPY的使用方法
parameter_name [mode] NOCOPY datatype
其中parameter_name是参数的名称,mode是参数模式,而datatype是参数类型。如果有NOCOPY,PL/SQL编译器就会尝试通过传引用的方式传递参数,而不是通过传值方式传递参数。注意,NOCPY只是一个编译器提示,而不是编译器命令,因此,这种提示并不一定总会被接受。
在IN参数上使用NOCOPY时,会引发一个编译错误,因为IN参数总是以传引用方式传递参数的,因此不允许使用编译器提示NOCOPY。
2)带NOCOPY的异常语义
以传递引用的方式传递参数的时候,对形式参数所做的任何更改都会同时反应到实际参数上,因为这二者指向的是同一个位置。这也意味着,如果过程在形式参数的值发生了变化以后,又以一个未处理的异常结束,那么实际参数的原始值也会丢失。
我们可以看到,即使发生了异常,还是两次修改了实际参数的值。
3)使用NOCOPY的一些限制
在某些情况下,编译器会忽略NOCOPY的存在,参数仍然以传值的方式进行传递,而且也不会产生任何错误。记住,NOCOPY只是一种pragma,编译器没有责任完全遵守这个提示。在下面几种情形中,会忽略NOCOPY的存在:
实际参数是联合数组的一个成员。但是,如果实际参数是整个数组,就不受这种约束的限制。
使用长度、精度或NOT NULL约束限制的实际参数。
实际参数和形式参数都是记录,并且它们要么被隐式声明为一个循环变量,要么是使用%ROWTYPE进行声明的,而且相应字段上的约束又不同。
传递的实际参数需要进行隐式的数据类型转换。
子程序被包含在进行远程过程调用(remote procedures call,RPC)中。
4.子程序的调用
如果过程或者函数没有参数,那么在过程声明或过程调用的时候都不需要括号了。
既然说到这里了,那就简单的说一下EXEC和CALL的区别吧。
a.
exec是sqlplus里的命令,只能在sqlplus中执行。
b.
call是sql命令,任何工具都可以使用,使用的时候必须加括号。
位置标识法和带名标识法
5.子程序的位置
1)内置子程序(Stored Subprogram)
当通过CREATE OR REPLACE命令创建子程序的时候,它被存储在数据库中。该子程序是以被编译的形式进行存储的,这就是p-code。
2)本地子程序
先看一个例子吧,这个例子说明了在PL/SQL块的声明部分声明的本地子程序
上面这个例子中的FormatName函数是在匿名块的声明部分进行声明的。函数名是一个PL/SQL标识符,因为遵循其他PL/SQL标识符所遵循的同样的作用域和可见性规则。
特别地,它仅在声明所在的块中是可见的。它的作用域从声明所在的位置开始一直持续到该块的结尾。其他的块都不能调用FormatName,因为它对其他的块来说是不可见的。
所有本地子程序都必须在声明部分的末尾进行声明。
这里就不能不提到一个概念:
前向声明(Forward Declaration)
因为本地PL/SQL子程序的名字是标识符,所以它们必须要在被引用之前进行声明。通常,这不是一个问题。但是,对于相互交叉引用的子程序,这会出现一个问题。考虑下面这个例子
这个例子是不可能通过编译的。因为过程A和B互相调用。为了解决这个问题,我们可以使用前向声明。这只是一个过程名和它的形式参数,这样就可以在程序中使用相互的交叉引用过程了。如下所示
6.子程序授权问题
先来看如下代码
假设RecordFullClasses以及其所依赖的对象(函数almostfull和表classes以及temp_table)都是由数据库用户USERA所拥有。如果我们把对RecordFullClasses的EXECUTE权限使用下面的命令授予USERB数据库用户
GRANT EXECUTE ON RecordFullClasses TO USERB
那么USERB可以通过下述的块来执行RecordFullClasses
BEGIN
USERA.RecordFullClasses;
END;
现在假设USERB有另外一个表,也叫temp_table,那么如果USERB调用USERA.RecordFullClasses,那么哪一个表会被改变呢?答案是USERA中的表会被改变。这个概念可以这样表述:
子程序在其拥有者的权限的控制下执行
既然USERB调用RecordFullClasses,而RecordFullClasses由USERA所拥有。这样标识符temp_table必须要对属于USERA而不是属于USERB的表进行求值。
还有一点值得注意的是子程序在授权时不能通过角色进行
GRANT SELECT ON CLASEES TO USERB;
GRANT EXECUTE ON ALMOST FULL TO USERB;
通过间接的角色授权则不行
CREATE ROLE USERA_ROLE;
GRANT EXECUTE ON ALMOST FULL TO USERA_ROLE;
GRANT USERA_ROLE TO USERB;
GRANT SELECT ON CLASEES TO USERA_ROLE;
1.创建子程序的语法
创建过程
CREATE [OR REPLACE] PROCEDURE procedure_name [(argument [{IN | OUT| IN OUT}] type, ... argument [{IN | OUT| IN OUT}] type)] {IS | AS} --IS和AS没区别 procedure_body
创建函数
CREATE [OR REPLACE] FUNCTION function_name [(argument [{IN | OUT| IN OUT}] type, ... argument [{IN | OUT| IN OUT}] type)] RETURN return_type {IS | AS} --IS和AS没区别 procedure_body
2.参数模式
IN
调用过程的时候,实际参数的值就会传递到该过程中。在过程内部,形式参数就像PL/SQL的常量一样--它会被看作是只读且不能修改的。当过程执行完毕,程序的控制返回到调用环境的时候,实际参数的值不会有任何变化。
OUT
调用过程的时候,会忽略所有实际参数的值。在过程内部,形式参数就像未初始化的PL/SQL变量一样,也会有一个NULL值。形式参数的值既可以读取,也可以写入。过程执行完成以后,控制就会返回到调用环境,同时会将形式参数的值赋给实际参数。
IN OUT
这是IN和OUT的复合模式。调用过程的时候,实际参数的值会传递到过程中。在过程内部,形式参数就像初始化以后的变量,可以读取和写入。过程结束以后,控制会返回到调用环境,同时会将形式参数的内容分配给实际参数。
3.以传值和传引用的方式传递参数
传递子程序参数的方式有两种--传值和传引用。当以引用的方式传递参数的时候,就将指向实际参数的一个指针传递到相应的形式参数。另一方面,当以传值的方式传递参数的时候,就将实际参数的值复制到相应的形式参数。以引用的方式传递参数通常会更快,因为它避免了复制。对集合类型的参数而言,这表现更加明显,因为集合类型的数据一般都非常多。
默认情况下,PL/SQL对IN参数都使用传引用的方式,而对IN OUT和OUT参数都使用传值的方式。
1)NOCOPY的使用方法
parameter_name [mode] NOCOPY datatype
其中parameter_name是参数的名称,mode是参数模式,而datatype是参数类型。如果有NOCOPY,PL/SQL编译器就会尝试通过传引用的方式传递参数,而不是通过传值方式传递参数。注意,NOCPY只是一个编译器提示,而不是编译器命令,因此,这种提示并不一定总会被接受。
-- This procedure demonstrates the syntax of the NOCOPY compiler -- hint. CREATE OR REPLACE PROCEDURE NoCopyTest ( p_InParameter IN NUMBER, p_OutParameter OUT NOCOPY VARCHAR2, p_InOutParameter IN OUT NOCOPY CHAR) IS BEGIN NULL; END NoCopyTest; /
在IN参数上使用NOCOPY时,会引发一个编译错误,因为IN参数总是以传引用方式传递参数的,因此不允许使用编译器提示NOCOPY。
2)带NOCOPY的异常语义
以传递引用的方式传递参数的时候,对形式参数所做的任何更改都会同时反应到实际参数上,因为这二者指向的是同一个位置。这也意味着,如果过程在形式参数的值发生了变化以后,又以一个未处理的异常结束,那么实际参数的原始值也会丢失。
CREATE OR REPLACE PROCEDURE RaiseErrorNoCopy ( p_Raise IN BOOLEAN, p_ParameterA OUT NOCOPY NUMBER) AS BEGIN p_ParameterA := 7; IF p_Raise THEN RAISE DUP_VAL_ON_INDEX; ELSE RETURN; END IF; END RaiseErrorNoCopy; / DECLARE v_Num NUMBER := 1; BEGIN DBMS_OUTPUT.PUT_LINE('Value before first call: ' || v_Num); RaiseErrorNoCopy(FALSE, v_Num); DBMS_OUTPUT.PUT_LINE('Value after successful call: ' || v_Num); DBMS_OUTPUT.PUT_LINE(''); v_Num := 2; DBMS_OUTPUT.PUT_LINE('Value before second call: ' || v_Num); RaiseErrorNoCopy(TRUE, v_Num); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Value after unsuccessful call: ' || v_Num); END; / --结果如下 Value before first call:1 Value after successful call:7 Value before second call:2 Value after unsuccessful call:7
我们可以看到,即使发生了异常,还是两次修改了实际参数的值。
3)使用NOCOPY的一些限制
在某些情况下,编译器会忽略NOCOPY的存在,参数仍然以传值的方式进行传递,而且也不会产生任何错误。记住,NOCOPY只是一种pragma,编译器没有责任完全遵守这个提示。在下面几种情形中,会忽略NOCOPY的存在:
实际参数是联合数组的一个成员。但是,如果实际参数是整个数组,就不受这种约束的限制。
使用长度、精度或NOT NULL约束限制的实际参数。
实际参数和形式参数都是记录,并且它们要么被隐式声明为一个循环变量,要么是使用%ROWTYPE进行声明的,而且相应字段上的约束又不同。
传递的实际参数需要进行隐式的数据类型转换。
子程序被包含在进行远程过程调用(remote procedures call,RPC)中。
4.子程序的调用
如果过程或者函数没有参数,那么在过程声明或过程调用的时候都不需要括号了。
既然说到这里了,那就简单的说一下EXEC和CALL的区别吧。
a.
exec是sqlplus里的命令,只能在sqlplus中执行。
b.
call是sql命令,任何工具都可以使用,使用的时候必须加括号。
位置标识法和带名标识法
CREATE OR REPLACE PROCEDURE CallMe ( p_ParameterA VARCHAR2, p_ParameterB NUMBER, p_ParameterC BOOLEAN, p_ParameterD DATE) AS BEGIN NULL; END CallMe; / DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(v_Variable1, v_Variable2, v_Variable3, v_Variable4); END; / DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(p_ParameterA => v_Variable1, p_ParameterB => v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4); END; / DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN CallMe(p_ParameterB => v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4, p_ParameterA => v_Variable1); END; / DECLARE v_Variable1 VARCHAR2(10); v_Variable2 NUMBER(7,6); v_Variable3 BOOLEAN; v_Variable4 DATE; BEGIN -- First 2 parameters passed by position, the second 2 are -- passed by name. CallMe(v_Variable1, v_Variable2, p_ParameterC => v_Variable3, p_ParameterD => v_Variable4); END; /
5.子程序的位置
1)内置子程序(Stored Subprogram)
当通过CREATE OR REPLACE命令创建子程序的时候,它被存储在数据库中。该子程序是以被编译的形式进行存储的,这就是p-code。
2)本地子程序
先看一个例子吧,这个例子说明了在PL/SQL块的声明部分声明的本地子程序
DECLARE CURSOR c_AllStudents IS SELECT first_name, last_name FROM students; v_FormattedName VARCHAR2(50); /* Function which will return the first and last name concatenated together, separated by a space. */ FUNCTION FormatName(p_FirstName IN VARCHAR2, p_LastName IN VARCHAR2) RETURN VARCHAR2 IS BEGIN RETURN p_FirstName || ' ' || p_LastName; END FormatName; -- Begin main block. BEGIN FOR v_StudentRecord IN c_AllStudents LOOP v_FormattedName := FormatName(v_StudentRecord.first_name, v_StudentRecord.last_name); INSERT INTO temp_table (char_col) VALUES (v_FormattedName); END LOOP; COMMIT; END; /
上面这个例子中的FormatName函数是在匿名块的声明部分进行声明的。函数名是一个PL/SQL标识符,因为遵循其他PL/SQL标识符所遵循的同样的作用域和可见性规则。
特别地,它仅在声明所在的块中是可见的。它的作用域从声明所在的位置开始一直持续到该块的结尾。其他的块都不能调用FormatName,因为它对其他的块来说是不可见的。
所有本地子程序都必须在声明部分的末尾进行声明。
这里就不能不提到一个概念:
前向声明(Forward Declaration)
因为本地PL/SQL子程序的名字是标识符,所以它们必须要在被引用之前进行声明。通常,这不是一个问题。但是,对于相互交叉引用的子程序,这会出现一个问题。考虑下面这个例子
DECLARE v_TempVal BINARY_INTEGER := 5; -- Local procedure A. Note that the code of A calls procedure B. PROCEDURE A(p_Counter IN OUT BINARY_INTEGER) IS BEGIN IF p_Counter > 0 THEN B(p_Counter); p_Counter := p_Counter - 1; END IF; END A; -- Local procedure B. Note that the code of B calls procedure A. PROCEDURE B(p_Counter IN OUT BINARY_INTEGER) IS BEGIN p_Counter := p_Counter - 1; A(p_Counter); END B; BEGIN B(v_TempVal); END; /
这个例子是不可能通过编译的。因为过程A和B互相调用。为了解决这个问题,我们可以使用前向声明。这只是一个过程名和它的形式参数,这样就可以在程序中使用相互的交叉引用过程了。如下所示
DECLARE v_TempVal BINARY_INTEGER := 5; -- Forward declaration of procedure B. PROCEDURE B(p_Counter IN OUT BINARY_INTEGER); PROCEDURE A(p_Counter IN OUT BINARY_INTEGER) IS BEGIN IF p_Counter > 0 THEN B(p_Counter); p_Counter := p_Counter - 1; END IF; END A; PROCEDURE B(p_Counter IN OUT BINARY_INTEGER) IS BEGIN p_Counter := p_Counter - 1; A(p_Counter); END B; BEGIN B(v_TempVal); END; /
6.子程序授权问题
先来看如下代码
CREATE OR REPLACE PROCEDURE RecordFullClasses AS CURSOR c_Classes IS SELECT department, course FROM classes; BEGIN FOR v_ClassRecord IN c_Classes LOOP -- Record all classes which don't have very much room left -- in temp_table. IF AlmostFull(v_ClassRecord.department, v_ClassRecord.course) THEN INSERT INTO temp_table (char_col) VALUES (v_ClassRecord.department || ' ' || v_ClassRecord.course || ' is almost full!'); END IF; END LOOP; END RecordFullClasses; /
假设RecordFullClasses以及其所依赖的对象(函数almostfull和表classes以及temp_table)都是由数据库用户USERA所拥有。如果我们把对RecordFullClasses的EXECUTE权限使用下面的命令授予USERB数据库用户
GRANT EXECUTE ON RecordFullClasses TO USERB
那么USERB可以通过下述的块来执行RecordFullClasses
BEGIN
USERA.RecordFullClasses;
END;
现在假设USERB有另外一个表,也叫temp_table,那么如果USERB调用USERA.RecordFullClasses,那么哪一个表会被改变呢?答案是USERA中的表会被改变。这个概念可以这样表述:
子程序在其拥有者的权限的控制下执行
既然USERB调用RecordFullClasses,而RecordFullClasses由USERA所拥有。这样标识符temp_table必须要对属于USERA而不是属于USERB的表进行求值。
还有一点值得注意的是子程序在授权时不能通过角色进行
GRANT SELECT ON CLASEES TO USERB;
GRANT EXECUTE ON ALMOST FULL TO USERB;
通过间接的角色授权则不行
CREATE ROLE USERA_ROLE;
GRANT EXECUTE ON ALMOST FULL TO USERA_ROLE;
GRANT USERA_ROLE TO USERB;
GRANT SELECT ON CLASEES TO USERA_ROLE;
相关文章推荐
- oracle 存储过程和函数学习笔记
- oracle 存储过程和函数学习笔记
- Oracle 学习笔记 18 -- 存储函数和存储过程(PL/SQL子程序)
- 【Oracle 学习笔记】Day 3 存储过程及函数
- Oracle PLSQL 学习笔记(块、控制结构、过程、函数、包)
- Oracle 存储过程和存储函数学习笔记
- 学习笔记_oracle——过程函数触发器
- Oracle PLSQL 学习笔记(块、控制结构、过程、函数、包)
- oracle过程、函数、触发器、程序包学习笔记
- oracle 最简单的学习笔记,增删改查,PLSQL基本语法,游标,函数,存储过程的实现
- Oracle 学习笔记 18 -- 存储函数和存储过程(PL/SQL子程序)
- Oracle函数学习笔记
- 学习笔记-mysql_存储过程和函数
- 《零基础入门学习Python》学习过程笔记【018函数开头文档,及参数相关问题】
- Oracle学习操作(6)函数与存储过程
- 转:mysql存储过程学习笔记--常用函数收藏
- 学习三十四天笔记——mysql事务触发器函数过程:变量
- oracle过程查询学习笔记
- Java学习笔记之数据库(触发器、事物、索引、投影和除、视图、存储过程和函数 )含各种链)___ 一直补充
- 29.Oracle深度学习笔记——分析函数