在SQL中调用子程序
如果编写的PL/SQL函数可以像Oracle内置函数一样被SQL调用,需要遵循一定的规则:
- 所有函数的参数必须是IN模式。
- 参数的数据类型和RETURN子句的返回类型必须能被Oracle数据库识别,这是因为PL/SQL兼容所有的Oracle数据类型,但是PL/SQL扩充了自己的类型,比如BOOLEAN、INTEGER、记录、集合、自定义的子类型等。
- 函数必须存储在数据库中,在客户端PL/SQL环境中定义的PL/SQL函数是不能 被SQL语句调用的到的。
- 函数不能修改数据库表,不能执行DDL语句如CREATE TABLED、DROP INDEX,不能执行DML语句如INSERT、DELETE、UPDATE、MERGE等,不能使用COMMIT或ROLLBACK提交或回滚事务。不过 当函数定义在自治事务中时,这是限制会稍稍宽松一些。因为在自治事务中会与调用事务独立出来。
- 当调用远程的函数或通过并行行为调用其他会话中的函数时,函数可能读取或写入包变量中的值,因为Oracle服务器不支持跨用户会话访问。
- 仅当函数在一个SELECT列表中被调用时,后者是VALUES或SET子句中,函数才能够更新包变量的值,如果在WHERE或GROUP BY子句中,它可能无法改写包变量的值。
- 函数不能调用其他模块,比如说存储过程或函数,否则将打断任何先前定义的规则。
- 函数不能引用一个视图。
嵌套子程序
内嵌子程序是一个过程或函数,定义在PL/SQL块声明区中,仅能被这个快调用。
嵌套子程序的定义跟普通子程序定义相似,只是因为它不需要被单独地存储在数据字典中,因此不需要使用CERAETE OR REPLACE语句,只需要直接使用FUNCTION或PROCEDURE开始定义即可。
嵌套子程序与存储子程序的异同:
存储子程序 | 嵌套子程序 |
---|---|
子程序以编译的形式存储在数据字典中,当调用过程时,不需要重新编译 | 嵌套子程序被编译为包含它的语句块的一部分,如果包含它的语句块是匿名的,并多次运行,那么每一次都必须编译子程序 |
子程序可以从对该子程序具有EXECUTE权限的用户所提交的任何语句块中调用 | 嵌套子程序只能从包含子程序的语句块中调用 |
通过将自称曾许代码与调用代码块分开,使调用块更短,更容易理解和维护 | 子程序和调用块是完全相同的,这会导致混乱,如果对调用块进行了更改,那么子程序也将被编译为包含它的重编译块的一部分 |
可以使用DBMS_SHARED_POOL.KEEP包过程把已编译伪代码锁定在共享池中以便重用,这样可以改进其性能 | 嵌套子程序不能被其他子程序锁定在共享池中 |
独立的存储子程序不能重载,但是包中的子程序可以在同一个包中重载 | 嵌套子程序可以在同一个语句块中重载 |
子程序的前向声明
PL/SQL要求必须要在调用子程序之前先定义好子程序,这在大多数情况下并没有问题,但是当遇到子程序相互调用时,情况就会变得有些复杂。
比如,子程序A调用子程序B,但是子程序B又调用子程序A:
DECLARE
v_val BINARY_INTEGER:=5;
PROCEDURE A(p_counter IN OUT BINARY_INTEGER) IS --声明嵌套子程序A
BEGIN
B(p_counter); --在嵌套子程序中调用B
END A;
PROCEDURE B(p_counter IN OUT BINARY_INTEGER) IS --声明嵌套子程序B
BEGIN
A(p_counter); --在嵌套子程序中调用A
END B;
BEGIN
B(v_val); --调用嵌套子程序B
END;
上述代码会产生一个异常:PLS-00313:在此作用域中没有声明'B'
。
此时可以使用前向声明,或者称为预声明。前向声明仅包含子程序的结构定义,并不包含具体的实现代码,这种声明也用于包中的包头中,如下代码可以顺利编译通过:
DECLARE
v_val BINARY_INTEGER:=5;
PROCEDURE B(p_counter IN OUT BINARY_INTEGER); --前向声明嵌套子程序B
PROCEDURE A(p_counter IN OUT BINARY_INTEGER) IS --声明嵌套子程序A
BEGIN
B(p_counter); --在嵌套子程序中调用B
END A;
PROCEDURE B(p_counter IN OUT BINARY_INTEGER) IS --声明嵌套子程序B
BEGIN
A(p_counter); --在嵌套子程序中调用A
END B;
BEGIN
B(v_val); --调用嵌套子程序B
END;
重载子程序
重载是面向对象的编程语言中非常常见的一种编写对象方法的方式,重载是指具有相同名称的方法,但是方法签名(参数的类型或顺序或者是数据类型)不同。在PL/SQL的包中,可以编写重载特性的子程序。
重载的几点限制:
- 只有本地货包中的子程序,或者是对象中的方法才可以被重载,不能对使用CREATE OR REPLACE的子程序进行重载。
- 只是参数的名称不同,或者是参数的传递模式不同的子程序不能重载。
- 如果形式参数类型是属于同一基类的子类,那么也不能重载,比如INTEGER和REAL就同属于NUMBER类型。
- 不能对只是返回值不同的两个函数进行重载。
子程序自治事务
当进行子程序开发时,如果想独立于主事务开始一个独立的事务,在子程序中使用COMMIT或ROLLBACK语句时,不影响主事务的状态,这种事务称为自治事务。
自治事务是由主事务开启的独立事务,自治事务把主事务挂起来,然后执行SQL操作,在提交或回滚这些操作后,重新恢复主事务。
自治事务使用AUTONOMOUS_TRANSACTION编译提示来定义,这个编译提示会让PL/SQL编译器把子程序或数据库触发器标记为自治事务。可以将这个指令放到程序声明部分的任何地方,但是为了良好的可读性,一般把他放到声明区的顶部,基本语法如下:
PRAGMA AUTONOMOUS_TRANSACTION;
递归调用子程序
递归算法中有如下两个原则:
- 必须要有一个方法能够退出递归循环,以免永远递归下去。如果递归程序无限制地执行下去,PL/SQL最终会用光内存然后抛出预定义的STORAGE_ERROR异常。
- 要有一个方法调用自身,通过不断地改变条件来使得递归接近退出的位置。
递归最经典的示例就是阶乘了,代码如下:
DECLARE
v_result INTEGER;
FUNCTION fac(n POSITIVE)
RETURN INTEGER IS --阶乘的返回结果
BEGIN
IF n=1 THEN --如果n=1则终止条件
RETURN 1;
ELSE
RETURN n*fac(n-1); --否则进行递归调用自身
END IF;
END fac;
BEGIN
v_result:= fac(10); --调用阶乘函数
DBMS_OUTPUT.put_line('结果是:'||v_result); --输出阶乘结果
END;
在使用PL/SQL进行开发时,通常使用递归来查询层次结构的数据,例如emp表中的empno和mgr是两个自引用的字段,mgr是当前empno的上级,mgr本身也是emp表中的一个员工,也具有自己的mgr,因此形成了树状的层次结构。
下面的代码使用递归算法查询某特定的员工编号下面的所有员工列表:
DECLARE
PROCEDURE find_staff (mgr_no NUMBER, tier NUMBER := 1)
IS
boss_name VARCHAR2 (10); --定义老板的名称
CURSOR c1 (boss_no NUMBER) --定义游标来查询emp表中当前编号下的员工列表
IS
SELECT empno, ename
FROM emp
WHERE mgr = boss_no;
BEGIN
SELECT ename INTO boss_name FROM emp
WHERE empno = mgr_no; --获取管理者名称
IF tier = 1 --如果tier指定1,表示从最顶层开始查询
THEN
INSERT INTO staff
VALUES (boss_name || ' 是老板 '); --因为第1层是老板,下面的才是经理
END IF;
FOR ee IN c1 (mgr_no) --通过游标FOR循环向staff表插入员工信息
LOOP
INSERT INTO staff
VALUES ( boss_name
|| ' 管理 '
|| ee.ename
|| ' 在层次 '
|| TO_CHAR (tier));
find_staff (ee.empno, tier + 1); --在游标中,递归调用下层的员工列表
END LOOP;
COMMIT;
END find_staff;
BEGIN
find_staff(7839); --查询7839的管理下的员工的列表和层次结构
END;
实际上这个例子也可以通过SQL语句的CONNECT BY子句来完成,但是通过使用子过程的递归调用方式,是的在进行PL/SQL编程时又多了一项利器,这在一些特殊的需要使用递归调用的场合非常有用。
理解子程序的依赖性
在创建子程序时,往往需要引用到其他对象,比如过程insertdept需要更新dept表,那么可以将过程insertdept称为对dept表的对象依赖,而将表dept称为被引用对象。以上依赖是直接依赖,如果insertdept过程调用了insertemp,而insertemp过程中需要更新emp表,那么insertdept对emp表就是间接依赖。
查看依赖
对于直接依赖,可以通过视图user_dependencies
找出其直接依赖的引用对象。
如果要查询间接依赖,需要先进行一番安装:
1. 首先要执行一下Oracle安装目录下的u-tldtree.sql
,
如F:\app\Administrator\product\11.2.0\dbhome_1\RDBMS\ADMIN\u-tldtree.sql
。这个SQL将创建一系列的表和视图,以及一个用来填充依赖性关系的deptree_fill
过程。为确保具有相应的创建视图与过程的权限,请以SYSDBA用户进行登录。
2. 执行如下的语句,向deptree视图中填充数据。
EXEC deptree_fill('TABLE', 'SCOTT', 'EMP');
一切操作完成后,就可以通过查询deptree获取到对emp表的直接或间接依赖。nested_level
是依赖的层次数据,1表示直接依赖,2表示间接依赖。
查看对象有效性
在user_objects
字典中查询子程序对象时,会看到一个status字段标识当前的子程序时候有效,可选的值为INVALID和VALID,用来标识当前对象的有效性状态。当对子程序直接或间接依赖的对象进修改之后,比如对依赖对象进行DDL操作,那么存储子程序就可能变得无效。
重新编译子程序
如果一个依赖对象失效,PL/SQL引擎将自动视图在下一次调用时重新编译,但这并不总是有效的。
可以使用如下语句对过程重新编译:ALTER PROCEDURE testProcedure COMPILE;