36.4. 声明

所有在块里使用的变量都必须在一个块的声明段里声明。 (唯一的例外是一个FOR循环里的循环变量是在一个整数范围内迭代的,被自动声明为整数变量。)

PL/pgSQL变量可以用任意的 SQL 数据类型,比如 integervarcharchar

下面是一些变量声明的例子∶

user_id integer;
quantity numeric(5);
url varchar;
myrow tablename%ROWTYPE;
myfield tablename.fieldname%TYPE;
arow RECORD;

一个变量声明的一般性语法是∶

name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ];

如果给出了DEFAULT子句,那么它声明了在进入该块的时候赋予该变量的初始值。 如果没有给出DEFAULT子句,那么该变量初始化为 SQL 空值。 CONSTANT选项避免了该变量被赋值,这样其数值在该块的范围内保持常量。 如果声明了NOT NULL,那么赋予NULL数值将导致一个运行时错误。 所以所有声明为NOT NULL的变量还必须声明一个非空的缺省值。

缺省值是在每次进入该块的时候计算的。因此,如果把 now() 赋予一个类型为 timestamp 的变量会令变量拥有函数实际调用的时间,而不是函数预编译的时间。

例子∶

quantity integer DEFAULT 32;
url varchar := 'http://mysite.com';
user_id CONSTANT integer := 10;

36.4.1. 函数参数的别名

传递给函数的参数都是用 $1$2,等等这样的标识符。 为了增加可读性,我们可以为 $n 参数名声明别名。 然后别名或者数字标识符都可以指向参数值。

有两种创建别名的方法,比较好的是在 CREATE FUNCTION 命令里给出参数名, 比如:

CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

另外一个方法,是 PostgreSQL 8.0 以前的唯一的方法, 是明确地声明为别名,使用声明语法

name ALIAS FOR $n;

这个风格的同一个例子看起来像下面这样

CREATE FUNCTION sales_tax(REAL) RETURNS real AS $$
DECLARE
    subtotal ALIAS FOR $1;
BEGIN
    RETURN subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

更多例子:

CREATE FUNCTION instr(varchar,integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- 这里放一些使用 v_string 和 index 的计算
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION concat_selected_fields(in_t tablename) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

如果一个 PL/pgSQL 函数声明为输出参数, 那么给予输出参数就会 $n 名字和可选的别名, 方法和其它正常输入参数一样。一个输出参数实际上是初始值为 NULL 的变量; 在函数执行的过程中,应该给它赋值。该参数的最后数值是返回的东西。 比如,销售额-税费的例子也可以这么做:

CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
BEGIN
    tax := subtotal * 0.06;
END;
$$ LANGUAGE plpgsql;

请注意我们忽略了 RETURNS real — 我们可以包含它,不过那样就多余了。

输出参数在返回多个数值的时候非常有用。一个简单的例子是:

CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
BEGIN
    sum := x + y;
    prod := x * y;
END;
$$ LANGUAGE plpgsql;

如我们在 Section 32.4.3 里面讨论的, 这样做实际上为函数的结果创建了一个匿名的记录类型。 如果给出一个 RETURNS 子句,那么它就必须说 RETURNS record

如果一个 PL/pgSQL 函数的返回类型声明为一个多态类型 (anyelement 或者 anyarray),那么就会创建一个特殊的参数, $0。它的数据类型是函数的实际返回类型,和从实际输入类型推导推导类型一样 (参阅 Section 32.2.5)。 这样就允许函数访问它的实际返回类型,像 Section 36.4.2 里显示的那样。 $0 初始化为空,并且可以被函数修改,所以,如果需要,它可以用于保存返回值, 虽然这并非必须的。$0 还可以给予一个别名。 比如,这个函数可以在任何有 + 操作符的数据类型上运转:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
    result ALIAS FOR $0;
BEGIN
    result := v1 + v2 + v3;
    RETURN result;
END;
$$ LANGUAGE plpgsql;

定义一个或者多个参数为 anyelement 或者 anyarray 也可以实现同样的效果。 在这种情况下,特殊的参数 $0 不会使用;输出参数自己起这个作用。比如:

CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
                                 OUT sum anyelement)
AS $$
BEGIN
    sum := v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

36.4.2. 拷贝类型

variable%TYPE

%TYPE 提供一个变量或者表字段的数据类型。 你可以用这个声明将要保存数据库数值的变量。比如,假如你在 users 表里面有一个字段叫 user_id。要声明一个和 users.user_id 类型相同的变量,你可以写:

user_id users.user_id%TYPE;

通过使用 %TYPE,你必须知道你引用的结构的数据类型, 并且,最重要的是,如果被引用项的数据类型在将来变化了(比如:你把 user_id 的类型从 integer 改成 real),你也不需要修改你的函数定义。

%TYPE 对多态的函数特别有用,因为内部变量的数据类型可能在不同调用中是不一样的。 我们可以通过给函数的参数或者结果占位符附加 %TYPE 的方法来创建合适的变量。

36.4.3. 行类型

name table_name%ROWTYPE;
name composite_type_name;

一个复合类型变量叫做变量(或者row-type变量)。 这样的一个变量可以保存一次SELECT或者 FOR命令结果的完整一行,只要命令的字段集匹配该变量声明的类型。 行数值的独立的字段是使用常用的点表示法访问的,比如 rowvar.field

一个行变量可以声明为和一个现有的表或者视图的行类型相同,方法是使用 table_name%ROWTYPE 表示法; 或者你也可以声明它的类型是一个复合类型的名字。(因为每个表都有一个相关联的同名数据类型, 在 PostgreSQL 里实在是无所谓你写不写 %ROWTYPE。但是有 %ROWTYPE 的形式移植性更好。)

函数的参数可以是复合类型(表的完整行)。这个时候, 对应的标识符 $n 将是一个行变量,并且可以从中选取字段,比如 $1.user_id

在一个行类型的变量中,只可以访问用户定义的表中行的属性, 不包括 OID 或者其他系统属性(因为该行可能来自一个视图)。 该行类型的数据域继承表中象 char(n) 这种类型字段的尺寸和精度。

这里是一个使用复合类型的例子。table1table2 是现有的表,至少包含代码中提到的字段:

CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
DECLARE
    t2_row table2%ROWTYPE;
BEGIN
    SELECT * INTO t2_row FROM table2 WHERE ... ;
    RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
END;
$$ LANGUAGE plpgsql;

SELECT merge_fields(t.*) FROM table1 t WHERE ... ;

36.4.4. 记录类型

name RECORD;

纪录变量类似行类型变量,但是它们没有预定义的结构。 它们在SELECT或者FOR命令中获取实际的行结构。 一个行变量的子结构可以在每次赋值的时候改变。 这样做的一个结果是:在一个记录变量被赋予数值之前,它没有子结构, 并且任何对其中的数据域进行访问的企图都将产生一个运行时错误。

请注意 RECORD 不是真正的数据类型,只是一个占位符。 我们还应该意识到在把一个 PL/pgSQL 函数声明为返回record类型的时候, 它和一个记录变量的概念并不完全相同,即使这个函数可能使用一个记录变量保存它的结果也如此。 在两种情况下,在书写函数的时候,实际的行结构都是不知道的,但是对于返回 record 的函数来说, 实际的结构是在调用它的查询被分析的时候决定的,而行变量可以在运行中改变其行结构。

36.4.5. RENAME

RENAME oldname TO newname;

你可以用 RENAME 声明修改一个变量,记录或者行的名字。 如果 NEW 或者 OLD 在个触发器过程里被另外一个名字引用, 那么这个东西就很有用。又见 ALIAS

例子∶

RENAME id TO user_id;
RENAME this_var TO that_var;

注意: RENAMEPostgreSQL7.3 里好像有问题。修补这个毛病的优先级比较低, 因为 ALIAS 覆盖了大多数 RENAME 的实际用途。