本文介绍了局部程序(subprogram)的声明和使用方法等相关内容。
简介
程序包括函数和过程。在PL/SQL块中创建的程序被称作嵌套子程序。您可以在PL/SQL块(可以是另一个子程序)、包或全局级别创建子程序。您可以同时声明和定义程序,也可以先声明,然后在同一个块中后续定义(即前向声明)。可以使用 CREATE FUNCTION
或 CREATE 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