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。为了防止在程序中指定一个不存在的员工号而导致程序执行出错,在程序中增加了异常处理,如果没有查询到任何信息,则打印相应的出错信息。