局部程序

本文介绍了局部程序(subprogram)的声明和使用方法等相关内容。

简介

程序包括函数和过程。在PL/SQL块中创建的程序被称作嵌套子程序。您可以在PL/SQL块(可以是另一个子程序)、包或全局级别创建子程序。您可以同时声明和定义程序,也可以先声明,然后在同一个块中后续定义(即前向声明)。可以使用 CREATE FUNCTIONCREATE PROCEDURE声明一个全局程序,或是在一个包中声明和定义程序(通常是在包头声明程序,在包体定义程序)。这两种程序会被存储在系统表中。此外,其他嵌套子程序在这里将其称作局部程序。它不会被存入系统表,当嵌套块中调用局部程序的时候,局部程序才会被编译和执行。

局部程序的声明

您可以通过以下语法声明局部程序。

DECLARE -- 外层匿名块,当然也可以是全局过程

  [FUNCTION] -- 局部函数
      name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
  {RETURNS | RETURN} rettype 
  {AS | IS}
  [DECLARE] -- 局部函数声明段
    ...
  BEGIN -- 局部函数程序段
    ...
    RETURN ...
  END;

BEGIN -- 外层的程序段

END;
说明

您也可以将 FUNCTION替换为 PROCEDURE来声明一个局部过程,并且不需要返回任何值。

局部程序的使用

以下为一个简单的使用局部程序的示例。

DECLARE
  a INT;
  FUNCTION local_func RETURN INT -- 声明局部函数
  IS
  BEGIN
    RETURN 10;
  END;
BEGIN
  a := local_func(); -- 调用局部函数
  RAISE NOTICE 'a: %', a;
END;

由于函数可以被重载(同一个函数名,不同的参数列表),通常情况下,系统将会使用 local_func这个名称去系统表中找到一系列候选函数,然后从中选择参数列表最为匹配的来执行(当然,也可能没有任何函数被匹配到)。在此之前,系统会优先尝试寻找匹配的局部函数。由于函数可以嵌套,系统将会一层一层的向上查找匹配的局部函数,如下所示:

DECLARE
    PROCEDURE local_proc1 IS
    BEGIN
        RAISE NOTICE 'call outer local_procedure1';
    END;

    PROCEDURE local_proc2 IS
        proc2_str VARCHAR(50);
        FUNCTION local_func RETURN VARCHAR IS
            func_str VARCHAR(50) := 'raise inner local_func';
        BEGIN
            local_proc1(); -- 寻找到外层的 local_proc1
            RETURN func_str;
        END;
    BEGIN
        proc2_str := local_func();
        raise notice '%', proc2_str;
    END;
BEGIN
    local_proc2();
END;

结果显示如下:

NOTICE:  call outer local_procedure1
NOTICE:  raise inner local_func

如果在某一层寻找到至少一个同名的局部函数后,将会停止继续向上搜索,并在当前层的所有局部函数中选择出一个优胜者函数来执行。此时,如果已经找到同名的局部函数但无法选出优胜者,那么将直接报错。

DECLARE
    PROCEDURE local_proc1 IS
    BEGIN
        RAISE NOTICE 'call outer local_procedure1';
    END;

    PROCEDURE local_proc2 IS
        proc2_str VARCHAR(50);
        FUNCTION local_func RETURN VARCHAR IS
            func_str VARCHAR(50) := 'raise inner local_func';
        BEGIN
            local_proc1(1); -- 寻找到外层的 local_proc1, 但无法匹配
            RETURN func_str;
        END;
    BEGIN
        proc2_str := local_func();
        raise notice '%', proc2_str;
    END;
BEGIN
    local_proc2();
END;

结果显示如下:

ERROR:  wrong number or types of arguments in call to local function local_proc1

前向声明

如果同一个PL/SQL块中的嵌套子程序相互调用,则需要前向声明,因为必须先声明子程序,然后才能调用子程序。如果声明后没有在同一个PL/SQL块中定义子程序,系统会检查出这种错误。

DECLARE
    FUNCTION func_test(id INT) return INT;  -- 只声明,未定义
BEGIN
END;

结果显示如下:

ERROR:  subroutine body must be defined for forward-declared function "func_test"

以下示例展示如何正确使用前向声明。

DECLARE
  PROCEDURE proc1(number1 NUMBER); -- 前向声明 proc1

  PROCEDURE proc2(number2 NUMBER) IS -- 声明并定义 proc2
  BEGIN
    proc1(number2); -- 调用 proc1
  END;

  -- Define proc 1:
  PROCEDURE proc1(number1 NUMBER) IS -- 定义 proc1
  BEGIN
    proc2 (number1); -- 调用 proc2
  END;

BEGIN
  NULL;
END;
说明

您必须保证嵌套调用有正确的退出条件,否则会在反复嵌套调用中超过最大调用栈而报错。

使用外层程序的变量

局部程序的使用中,给出了一个使用外层局部函数的示例。同理,在局部程序之前声明的所有变量、游标、自定义异常等等都是可以直接使用的。您可以对这些变量取值、赋值,开启游标并从中取行,或是使用外层的自定义异常来抛出一个异常。以下是一个说明变量可见性的示例。

DECLARE
    a INT := 1; -- 可以使用 a
    PROCEDURE local_proc IS
    BEGIN
        RAISE NOTICE 'call outer local_procedure';
        RAISE NOTICE 'inner raise a: %', a;
        a := 10;
    END;
    b INT; -- 无法使用 b
BEGIN
    RAISE NOTICE 'outer raise a: %', a;
    local_proc();
    RAISE NOTICE 'outer raise a: %', a;
END;

结果显示如下:

NOTICE:  outer raise a: 1
NOTICE:  call outer local_procedure
NOTICE:  inner raise a: 1
NOTICE:  outer raise a: 10

如果您尝试使用在 local_proc之后声明的变量,则会出现语法错误。

DECLARE
    a INT := 1; -- 可以使用 a
    PROCEDURE local_proc IS
    BEGIN
        RAISE NOTICE 'call outer local_procedure';
        RAISE NOTICE 'inner raise a: %', a;
        a := 10;
    END;
    b INT; -- 无法使用 b
BEGIN
    local_proc();
END;

结果显示如下:

ERROR:  "b" is not a known variable