3.5.4 函数与过程的递归调用
子程序定义好以后,需要在主程序或其他子程序中调用后才能执行,执行完后返回到调用者。在有些情况下,子程序在执行过程中还可能要调用自己,调用结束后返回当前调用的地方。子程序自己调用自己的现象称为递归调用。
考虑求整数n的阶乘的情况。n!的值为n*(n-1)!,为了求n的阶乘,首先要求出(n-1)!。同样,要计算(n-1)!,首先要计算(n-2)!的值,一直到1的阶乘,而1的阶乘的值是已知的。如果编写一个函数fact,这个函数可以求得任何整数的阶乘,那么这个函数就是一个递归函数。下面是求阶乘的递归过程:
fact(n)=n*fact(n-1)
=n(n-1)fact(n-2)
=……
=n(n-1)(n-2)……fact(1)
在调用函数fact求n的阶乘时,首先要求n-1的阶乘,这时需要调用函数自己,不过这次传递的参数是n-1。同样,求n-1的阶乘时,需要再次调用函数本身,求得n-2的阶乘,这次传递的参数是n-2。依此类推,最后要调用fact函数求1的阶乘,而1的阶乘是已知的,这是递归返回的条件。求得1的阶乘后,便可返回到调用fact(1)的地方,求得2的阶乘。求得2的阶乘后再返回到调用fact(2)的地方,求得3的阶乘。这样每返回一次,就可求得上一个数的阶乘,直到求得n的阶乘。下面是一个求整数m的阶乘的PL/SQL块。
DECLARE
m integer;
result integer;
function fact(n integer)
RETURN integer
is
BEGIN
if n=1 then
RETURN 1;
else
RETURN n*fact(n-1);—递归调用
END if;
END;
BEGIN
m:=10;
result:=fact(m);—调用函数,求整数10的阶乘
dbms_output.put_line(m||'的阶乘为:'||result);
END;
这个PL/SQL块的执行结果为:
SQL>/
10的阶乘为:3628800
让我们再来看一个递归调用的例子。表emp中存放的是公司员工的信息,其中包括员工号、员工姓名以及经理编号等信息。以下是表emp中这三个列的数据:
SQL>SELECT empno, ename, mgr FROM emp
EMPNO ENAME MGR
7369 SMITH 7902
7566 JONES 7839
7839 KING
7902 FORD 7566
从查询的结果可以看出,除员工KING外,其他人都有一个经理,而经理同时也是一个员工,其中KING是公司的最高领导。如果指定任何一个员工号,希望得到这个员工的经理,以及这个经理的经理,一直到最高领导这样的垂直、直接领导关系。借助于子程序的递归调用,可以完成这样的要求。以下是用过程递归的方法编写的一个PL/SQL块:
DECLARE
procedure manager(employee_no emp.empno%type)
is
name emp.ename%type;—员工姓名
manager_no emp.empno%type;—员工经理的编号
manager_name emp.ename%type;—员工经理的姓名
BEGIN
SELECT ename, mgr INTO name, manager_no
FROM emp WHERE empno=employee_no;
if manager_no is not null then—如果员工的经理编号不为空,则查询其姓名
SELECT ename INTO manager_name FROM emp WHERE empno=manager_no;
dbms_output.put_line(name||'->'||manager_name);
manager(manager_no);—递归调用,查询该经理的经理
else—如果员工的经理编号为空,说明该员工即为最高领导
dbms_output.put_line(name||'是最高层领导');
END if;
END;
BEGIN
manager(7369);
EXCEPTION
when NO_DATA_FOUND then
dbms_output.put_line('没有这样的员工');
END;
这个PL/SQL块的执行结果为:
SMITH->FORD
FORD->JONES
JONES->KING
KING是最高层领导
过程manager以一个员工号为参数,首先查询该员工的经理编号。如果经理编号为空,则说明该员工为公司的最高领导,这时打印相应的信息,并结束过程的执行。否则查询该经理的姓名,并打印他们之间的领导关系,然后递归调用过程本身,以该经理的编号作为参数,继续查询他的经理的信息。
从程序的运行结果可以看出,编号为7369的员工姓名为SMITH,他的经理为FORD,而FORD的经理是JONES,这样可以一直向上追溯到最高领导KING。为了防止在程序中指定一个不存在的员工号而导致程序执行出错,在程序中增加了异常处理,如果没有查询到任何信息,则打印相应的出错信息。