2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

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 选项指定了用于变量的排序规则(请参阅 第 41.3.6 节)。如果指定了 NOT NULL,则将空值赋给它会导致运行时错误。所有声明为 NOT NULL 的变量都必须指定一个非空默认值。等号 (=) 可以代替符合 PL/SQL 标准的 :=

变量的默认值会在每次进入块时进行计算并赋给变量(而不仅仅是每次函数调用时一次)。因此,例如,将 now() 赋值给一个 timestamp 类型的变量会导致该变量具有当前函数调用时的时间,而不是函数预编译时的时间。

示例

quantity integer DEFAULT 32;
url varchar := 'http://mysite.com';
transaction_time CONSTANT timestamp with time zone := now();

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

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;

注意

这两个示例并不完全等价。在第一种情况下,subtotal 可以被引用为 sales_tax.subtotal,但在第二种情况下则不能。(如果我们给内部块添加了一个标签,subtotal 就可以用该标签限定,而不是。)

更多示例

CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
DECLARE
    v_string ALIAS FOR $1;
    index ALIAS FOR $2;
BEGIN
    -- some computations using v_string and index here
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 的变量;它应该在函数执行期间被赋值。参数的最终值就是返回的值。例如,销售税示例也可以这样完成

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

第 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 调用过程时,您应该改为为主任何输出参数编写一个变量;该变量将接收调用结果。有关详细信息,请参阅 第 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 函数的返回类型声明为多态类型时(请参阅 第 36.2.5 节),会创建一个特殊的参数 $0。它的数据类型是函数的实际返回类型,由实际输入类型推导得出。这允许函数访问其实际返回类型,如 第 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);

将能够工作,自动将整数输入提升为数值类型。使用 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];  -- equivalent to the above

就像声明数组列一样,在 %TYPE 之后写多个方括号对还是指定数组维度无关紧要:PostgreSQL 将给定元素类型的所有数组视为同一类型,无论维度如何。(请参阅 第 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 函数有一个或多个可排序数据类型的参数时,会根据分配给实际参数的排序规则为每个函数调用确定一个排序规则,如 第 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 命令中一样。

提交更正

如果您在文档中看到任何不正确的内容,与您使用该功能的经验不符,或者需要进一步澄清,请使用 此表单 报告文档问题。