43.3. 声明#
块中使用的所有变量都必须在块的声明部分中声明。(唯一的例外是FOR
循环的循环变量在整数值范围内迭代时自动声明为整数变量,同样,FOR
循环的循环变量在游标结果中迭代时自动声明为记录变量。)
PL/pgSQL变量可以具有任何 SQL 数据类型,例如integer
、varchar
和char
。
下面是变量声明的一些示例
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
选项指定要用于变量的排序规则(请参见第 43.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;
43.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
如第 38.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调用过程时,应该为任何输出参数编写一个变量;该变量将接收调用的结果。有关详细信息,请参见第 43.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函数的返回类型被声明为多态类型(参见第 38.2.5 节)时,将创建一个特殊参数$0
。它的数据类型是函数的实际返回类型,这是从实际输入类型推导出来的。这允许函数访问其实际返回类型,如第 43.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
的函数要求您手动将三个输入强制转换为相同的类型。
43.3.2.ALIAS
#
newname ALIAS FOR oldname;
在上一节中,ALIAS
语法比建议的更通用:您可以为任何变量声明一个别名,而不仅仅是函数参数。这样做的主要实际用途是为具有预定名称的变量分配一个不同的名称,例如触发器函数中的NEW
或OLD
。
示例
DECLARE
prior ALIAS FOR old;
updated ALIAS FOR new;
由于ALIAS
创建了两种不同的方式来命名同一个对象,因此不受限制的使用可能会令人困惑。最好仅将其用于覆盖预定名称的目的。
43.3.3. 复制类型#
variable%TYPE
%TYPE
提供变量或表列的数据类型。你可以使用它来声明将保存数据库值的变量。例如,假设你在users
表中有一个名为user_id
的列。要声明一个数据类型与users.user_id
相同的变量,你可以写
user_id users.user_id%TYPE;
通过使用%TYPE
,你无需了解所引用结构的数据类型,最重要的是,如果所引用项的数据类型在将来发生更改(例如:你将user_id
的类型从integer
更改为real
),你可能无需更改函数定义。
%TYPE
在多态函数中特别有价值,因为内部变量所需的数据类型可能在一次调用到下一次调用之间发生变化。可以通过将%TYPE
应用于函数的参数或结果占位符来创建适当的变量。
43.3.4. 行类型#
name table_name%ROWTYPE;
name composite_type_name;
复合类型的变量称为行变量(或行类型变量)。此类变量可以保存SELECT
或FOR
查询结果的整行,只要该查询的列集与变量的声明类型匹配即可。行值的各个字段使用常规的点表示法进行访问,例如rowvar.field
。
可以通过使用*table_name
*%ROWTYPE
表示法来声明行变量与现有表或视图的行具有相同的类型;或者可以通过给出一个复合类型的名称来声明它。(由于每个表都有一个同名的关联复合类型,因此在PostgreSQL中你是否编写%ROWTYPE
实际上无关紧要。但带有%ROWTYPE
的形式更具可移植性。)
函数的参数可以是复合类型(完整的表行)。在这种情况下,相应的标识符$*
n*
将是行变量,并且可以从中选择字段,例如$1.user_id
。
以下是用复合类型的示例。table1
和table2
是至少具有所述字段的现有表
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 ... ;
43.3.5. 记录类型#
name RECORD;
记录变量类似于行类型变量,但它们没有预定义的结构。它们采用在SELECT
或FOR
命令期间分配给它们的行的实际行结构。记录变量的子结构每次分配给它时都可能发生变化。由此产生的一个后果是,在记录变量首次被分配之前,它没有子结构,并且任何尝试访问其中的字段都会导致运行时错误。
请注意,RECORD
不是真正的类型,而只是一个占位符。还需要了解的是,当一个PL/pgSQL函数被声明为返回类型record
时,这与记录变量并不是完全相同的一个概念,即使这样的函数可能使用一个记录变量来保存其结果。在这两种情况下,当函数被编写时实际的行结构都是未知的,但是对于返回record
的函数,实际结构是在调用查询被解析时确定的,而记录变量可以在运行时更改其行结构。
43.3.6. 排序PL/pgSQL变量#
当一个PL/pgSQL函数有一个或多个可排序数据类型的参数时,会根据分配给实际参数的排序为每个函数调用识别一个排序,如第 24.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_1
和text_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 命令中发生的那样。