受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

41.3. 声明 #

在一个块中使用的所有变量,都必须在该块的声明部分声明。(唯一的例外是:在整数范围上迭代的 FOR 循环变量会被自动声明为整数变量;同样,在游标结果上迭代的 FOR 循环变量会被自动声明为记录变量。)

PL/pgSQL 变量可以是任意 SQL 数据类型,例如 integervarcharchar

这里是变量声明的一些示例:

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

一个变量声明的一般语法是:

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

如果给定 DEFAULT 子句,它会指定进入该块时赋给该变量的初始值。如果没有给出 DEFAULT 子句,则变量会被初始化为 SQL 空值。CONSTANT 选项会阻止该变量在初始化之后再次被赋值,因此其值在整个块的持续期间保持不变。COLLATE 选项指定该变量使用的排序规则(见 Section 41.3.6)。如果指定了 NOT NULL,给该变量赋空值将导致运行时错误。所有声明为 NOT NULL 的变量都必须指定非空默认值。等号(=)可以代替兼容 PL/SQL 的 :=

变量的默认值会在每次进入该块时重新计算并赋给该变量,而不是每次函数调用只计算一次。因此,例如把 now() 赋给一个 timestamp 类型变量,会使该变量得到当前函数调用时的时间,而不是函数预编译时的时间。

示例:

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

一旦声明,变量的值可以在同一块中的后续初始化表达式中使用,例如:

DECLARE
  x integer := 1;
  y integer := x + 1;

41.3.1. 声明函数参数 #

传递给函数的参数使用标识符 $1$2 等命名。也可以为这些 $n 形式的参数名声明别名,以提高可读性。之后既可以使用别名,也可以使用数字标识符来引用参数值。

有两种方式可以创建别名。推荐的方式是在 CREATE FUNCTION 命令中直接为参数命名。例如:

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

另一种方式是使用声明语法显式声明别名。

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;

Note

这两个示例并不完全等价。在第一种情况下,subtotal 可以写成 sales_tax.subtotal 来引用;但在第二种情况下则不行。(如果我们给内层块附上一个标签,那么 subtotal 可以用那个标签来限定。)

更多一些示例:

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 sometablename) RETURNS text AS $$
BEGIN
    RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
END;
$$ LANGUAGE plpgsql;

PL/pgSQL 函数声明了输出参数时,输出参数也会像普通输入参数一样获得 $n 名称和可选别名。输出参数本质上是一个初始值为 NULL 的变量,应在函数执行期间给它赋值。该参数的最终值就是返回值。例如,sales-tax 的示例也可以这样写:

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

注意这里省略了 RETURNS real — 当然也可以写上,但那只是冗余。

调用带有 OUT 参数的函数时,应在函数调用中省略输出参数:

     SELECT sales_tax(100.00);
     

当需要返回多个值时,输出参数尤其有用。下面是一个简单示例:

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;
SELECT * FROM sum_n_product(2, 4);
 sum | prod
-----+------
   6 |    8

Section 36.5.4 所述,这实际上会为函数结果创建一个匿名记录类型。如果写了 RETURNS 子句,它必须是 RETURNS record

对于过程也同样适用,例如:

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

调用过程时,必须写出所有参数。对于输出参数,从普通 SQL 调用该过程时可以指定 NULL

     CALL sum_n_product(2, 4, NULL, NULL);
      sum | prod
     -----+------
        6 |    8
     

但是,当从 PL/pgSQL 中调用过程时,你应当为每个输出参数写一个变量;该变量会接收调用结果。详见 Section 41.6.3

声明 PL/pgSQL 函数的另一种方式是使用 RETURNS TABLE,例如:

CREATE FUNCTION extended_sales(p_itemno int)
RETURNS TABLE(quantity int, total numeric) AS $$
BEGIN
    RETURN QUERY SELECT s.quantity, s.quantity * s.price FROM sales AS s
                 WHERE s.itemno = p_itemno;
END;
$$ LANGUAGE plpgsql;

这与声明一个或多个 OUT 参数并指定 RETURNS SETOF sometype 完全等效。

PL/pgSQL 函数的返回类型被声明为多态类型(见 Section 36.2.5)时,会创建一个特殊参数 $0。它的数据类型是函数的实际返回类型,由实际输入类型推导得出。这使得函数可以像 Section 41.3.3 所示那样访问自身的实际返回类型。$0 会被初始化为 null,并且可以在函数中修改,因此如果需要,也可以用它保存返回值,尽管这并不是必需的。$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;

把一个或多个输出参数声明为多态类型,也可以达到同样的效果。在这种情况下,不使用特殊参数 $0,输出参数本身就承担相同作用。例如:

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

在实践中,使用 anycompatible 类型族来声明多态函数可能更有用,因为这样输入参数会自动提升为共同类型。例如:

CREATE FUNCTION add_three_values(v1 anycompatible, v2 anycompatible, v3 anycompatible)
RETURNS anycompatible AS $$
BEGIN
    RETURN v1 + v2 + v3;
END;
$$ LANGUAGE plpgsql;

在这个示例中,像下面这样的调用

SELECT add_three_values(1, 2, 4.7);

可以正常工作,并会自动把整数输入提升为 numeric。如果使用 anyelement,则必须手工把这三个输入转换成同一类型。

41.3.2. ALIAS #

newname ALIAS FOR oldname;

ALIAS 语法比上一节所展示的更一般化:你可以为任何变量声明别名,而不只是函数参数。它最主要的实际用途,是为那些名称预先固定的变量指定另一个名字,例如触发器函数中的 NEWOLD

示例:

DECLARE
  prior ALIAS FOR old;
  updated ALIAS FOR new;

由于 ALIAS 为同一个对象提供了两种命名方式,滥用它会让代码变得混乱。最好只把它用于改写那些预先固定的名称。

41.3.3. 复制类型 #

name table.column%TYPE
name variable%TYPE

%TYPE 提供表列或先前已声明的 PL/pgSQL 变量的数据类型。你可以用它来声明用于保存数据库值的变量。例如,假设 users 表中有一个名为 user_id 的列。要声明一个与 users.user_id 具有相同数据类型的变量,可以这样写:

user_id users.user_id%TYPE;

也可以在 %TYPE 后写数组修饰,从而创建保存被引用类型数组的变量:

user_ids users.user_id%TYPE[];
user_ids users.user_id%TYPE ARRAY[4];  -- 等价于上面一行

与声明数组表列时一样,写多个方括号还是写具体数组维度都无关紧要:PostgreSQL 会把同一元素类型的所有数组都视为同一种类型,而不考虑维度数。(见 Section 8.15.1。)

使用 %TYPE 的好处是,你不必知道所引用结构的实际数据类型;更重要的是,如果被引用项的数据类型将来发生变化(例如把 user_id 的类型从 integer 改成 real),你可能就不需要修改函数定义。

%TYPE 在多态函数中特别有价值,因为内部变量所需的数据类型可能在不同调用之间变化。可以把 %TYPE 应用到函数参数或结果占位符上,以创建合适的变量。

41.3.4. 行类型 #

name table_name%ROWTYPE;
name composite_type_name;

复合类型的变量称为变量(或行类型变量)。只要查询的列集合与该变量声明的类型相匹配,这种变量就可以保存 SELECTFOR 查询结果中的整行。行值的各个字段可以使用通常的点号记法访问,例如 rowvar.field

行变量既可以通过 table_name%ROWTYPE 记法声明为与现有表或视图的行具有相同类型,也可以通过给出某个复合类型的名称来声明。(由于每个表都有一个同名的关联复合类型,所以在 PostgreSQL 中实际上写不写 %ROWTYPE 并无区别;不过带 %ROWTYPE 的形式可移植性更好。)

%TYPE 一样,%ROWTYPE 后也可以跟数组修饰,用于声明保存被引用复合类型数组的变量。

函数参数也可以是复合类型(完整的表行)。在这种情况下,相应的标识符 $n 就是一个行变量,并且可以从中选取字段,例如 $1.user_id

下面是一个使用复合类型的示例。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 ... ;

41.3.5. 记录类型 #

name RECORD;

记录变量与行类型变量类似,但没有预定义结构。它会在 SELECTFOR 命令为其赋值时采用相应行的实际结构。记录变量的内部结构在每次被赋值时都可能变化。其后果是:在记录变量第一次被赋值之前,它没有任何子结构,任何试图访问其中字段的行为都会引发运行时错误。

注意,RECORD 并不是真正的数据类型,它只是一个占位符。还需要认识到,PL/pgSQL 函数被声明为返回 record,与记录变量并不是完全相同的概念,尽管这样的函数可能会用记录变量保存结果。这两种情况下,在编写函数时都不知道实际的行结构;但对于返回 record 的函数,实际结构会在解析调用查询时确定,而记录变量的行结构则可以在运行过程中随时变化。

41.3.6. PL/pgSQL变量的排序规则 #

PL/pgSQL 函数具有一个或多个可排序数据类型参数时,每次函数调用都会根据分配给实际参数的排序规则确定出一个排序规则,如 Section 23.2 所述。如果该排序规则能成功确定出来(即参数之间的隐式排序规则没有冲突),那么所有可排序参数都会被视为隐式带有该排序规则。这会影响函数中那些受排序规则影响的操作。例如,考虑

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b;
END;
$$ LANGUAGE plpgsql;

SELECT less_than(text_field_1, text_field_2) FROM table1;
SELECT less_than(text_field_1, text_field_2 COLLATE "C") FROM table1;

第一次调用 less_than 时,比较会使用 text_field_1text_field_2 的共同排序规则;第二次则会使用 C 排序规则。

此外,确定出的排序规则也会被视为任何可排序数据类型局部变量的排序规则。因此,即使把这个函数写成下面这样,其行为也不会有任何不同:

CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
DECLARE
    local_a text := a;
    local_b text := b;
BEGIN
    RETURN local_a < local_b;
END;
$$ LANGUAGE plpgsql;

如果函数没有可排序数据类型的参数,或者无法为它们确定共同排序规则,那么参数和局部变量将使用其数据类型的默认排序规则(通常是数据库默认排序规则,但对于域类型变量也可能不同)。

通过在可排序数据类型局部变量的声明中加入 COLLATE 选项,可以为其指定不同的排序规则,例如

DECLARE
    local_a text COLLATE "en_US";

这个选项会覆盖按上述规则原本应赋给该变量的排序规则。

当然,如果某个函数希望在特定操作中强制使用特定排序规则,也可以在函数内部显式写出 COLLATE 子句。例如:

CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
BEGIN
    RETURN a < b COLLATE "C";
END;
$$ LANGUAGE plpgsql;

这会覆盖表达式中表列、参数或局部变量所关联的排序规则,就像在普通 SQL 命令中一样。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。