SQL 函数执行任意数量的 SQL 语句,并返回列表中最后一个查询的结果。在简单(非集合)的情况下,将返回最后一个查询结果的第一行。(请注意,除非使用 ORDER BY
,否则多行结果的“第一行”并不明确定义)。如果最后一个查询恰好没有返回任何行,则将返回 NULL 值。
或者,可以通过将函数的返回类型指定为 SETOF
,或通过将其声明为 sometype
RETURNS TABLE(
来声明 SQL 函数返回集合(即多行)。在这种情况下,将返回最后一个查询结果的所有行。下面将提供更多详细信息。columns
)
SQL 函数的主体必须是由分号分隔的 SQL 语句列表。最后一个语句后的分号是可选的。除非函数被声明为返回 void
,否则最后一个语句必须是 SELECT
,或带有 RETURNING
子句的 INSERT
、UPDATE
、DELETE
或 MERGE
。
可以打包任何一系列命令SQL语言并将其定义为函数。除了 SELECT
查询之外,命令还可以包括数据修改查询(INSERT
、UPDATE
、DELETE
和 MERGE
)以及其他 SQL 命令。(您不能在SQL函数中使用事务控制命令,例如 COMMIT
、SAVEPOINT
,以及一些实用命令,例如 VACUUM
。)然而,最后一个命令必须是 SELECT
或带有 RETURNING
子句,该子句返回函数返回类型指定的内容。或者,如果您想定义一个执行操作但没有有意义的值返回的 SQL 函数,您可以将其定义为返回 void
。例如,此函数从 emp
表中删除薪水为负数的行
CREATE FUNCTION clean_emp() RETURNS void AS ' DELETE FROM emp WHERE salary < 0; ' LANGUAGE SQL; SELECT clean_emp(); clean_emp ----------- (1 row)
您也可以将其写为存储过程,从而避免返回类型问题。例如
CREATE PROCEDURE clean_emp() AS ' DELETE FROM emp WHERE salary < 0; ' LANGUAGE SQL; CALL clean_emp();
在简单的示例中,返回 void
的函数与存储过程之间的区别主要在于风格。但是,存储过程提供了函数中不可用的其他功能,例如事务控制。此外,存储过程是 SQL 标准,而返回 void
是 PostgreSQL 的扩展。
CREATE FUNCTION 命令的语法要求函数体必须写成字符串常量。通常最方便的方法是使用美元引用(请参阅 第 4.1.2.4 节)作为字符串常量。如果您选择使用常规的单引号字符串常量语法,则必须在函数体中将单引号('
)和反斜杠(\
)(假设使用的是转义字符串语法)加倍(请参阅 第 4.1.2.1 节)。
可以使用名称或编号在函数体中引用 SQL 函数的参数。下面提供了这两种方法的示例。
要使用名称,请将函数参数声明为具有名称,然后仅在函数体中写该名称。如果参数名称与函数中的当前 SQL 命令中的任何列名称相同,则列名称将优先。要覆盖此行为,请使用函数本身的名称限定参数名称,即
。(如果这与限定的列名称冲突,则列名称仍然优先。您可以通过为 SQL 命令中的表选择不同的别名来避免歧义。)function_name
.argument_name
在旧的数字方法中,使用语法 $
引用参数:n
$1
指第一个输入参数,$2
指第二个,依此类推。这将在特定参数是否已命名的情况下工作。
如果参数是复合类型,则可以使用点表示法,例如
或 argname
.fieldname
$1.
来访问参数的属性。同样,您可能需要用函数名称限定参数名称,以使带参数名称的形式不产生歧义。fieldname
SQL 函数参数只能用作数据值,不能用作标识符。因此,例如这是合理的
INSERT INTO mytable VALUES ($1);
但这将不起作用
INSERT INTO $1 VALUES (42);
在 PostgreSQL 9.2 中添加了使用名称引用 SQL 函数参数的功能。要用于旧服务器的函数必须使用 $n
符号。
最简单的SQL函数没有参数,只返回一个基本类型,例如 integer
CREATE FUNCTION one() RETURNS integer AS $$ SELECT 1 AS result; $$ LANGUAGE SQL; -- Alternative syntax for string literal: CREATE FUNCTION one() RETURNS integer AS ' SELECT 1 AS result; ' LANGUAGE SQL; SELECT one(); one ----- 1
请注意,我们在函数体内为函数结果(名称为 result
)定义了一个列别名,但此列别名在函数外部不可见。因此,结果被标记为 one
而不是 result
。
定义SQL接受基本类型作为参数的函数
CREATE FUNCTION add_em(x integer, y integer) RETURNS integer AS $$ SELECT x + y; $$ LANGUAGE SQL; SELECT add_em(1, 2) AS answer; answer -------- 3
同样容易。或者,我们可以省略参数名称并使用数字
CREATE FUNCTION add_em(integer, integer) RETURNS integer AS $$ SELECT $1 + $2; $$ LANGUAGE SQL; SELECT add_em(1, 2) AS answer; answer -------- 3
这是一个更有用的函数,可以用来借记银行账户
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$ UPDATE bank SET balance = balance - debit WHERE accountno = tf1.accountno; SELECT 1; $$ LANGUAGE SQL;
用户可以执行此函数,以 100.00 美元的价格借记账户 17,如下所示
SELECT tf1(17, 100.0);
在此示例中,我们将第一个参数命名为 accountno
,但这与 bank
表的列名相同。在 UPDATE
命令中,accountno
指的是 bank.accountno
列,因此必须使用 tf1.accountno
来引用参数。当然,我们可以通过为参数使用不同的名称来避免这种情况。
实际上,人们可能希望从函数中获得比常数 1 更有用的结果,因此更可能定义的定义是
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$ UPDATE bank SET balance = balance - debit WHERE accountno = tf1.accountno; SELECT balance FROM bank WHERE accountno = tf1.accountno; $$ LANGUAGE SQL;
它调整余额并返回新余额。使用 RETURNING
可以在一个命令中完成同样的事情
CREATE FUNCTION tf1 (accountno integer, debit numeric) RETURNS numeric AS $$ UPDATE bank SET balance = balance - debit WHERE accountno = tf1.accountno RETURNING balance; $$ LANGUAGE SQL;
如果SQL函数中的最后一个 SELECT
或 RETURNING
子句没有返回与函数声明的结果类型完全相同的值,PostgreSQL 将自动将该值转换为所需类型(如果可以通过隐式或赋值转换完成)。否则,您必须编写显式转换。例如,假设我们希望前面的 add_em
函数返回类型 float8
而不是。写
CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$ SELECT $1 + $2; $$ LANGUAGE SQL;
就足够了,因为 integer
总和可以隐式转换为 float8
。(有关转换的更多信息,请参阅 第 10 章 或 CREATE CAST)。
在编写带有复合类型参数的函数时,我们不仅必须指定我们想要的参数,还要指定该参数所需的属性(字段)。例如,假设 emp
是一个包含员工数据的表,因此也是表中每行复合类型的名称。这是一个计算如果薪水加倍,某个人的薪水会是多少的函数 double_salary
CREATE TABLE emp ( name text, salary numeric, age integer, cubicle point ); INSERT INTO emp VALUES ('Bill', 4200, 45, '(2,1)'); CREATE FUNCTION double_salary(emp) RETURNS numeric AS $$ SELECT $1.salary * 2 AS salary; $$ LANGUAGE SQL; SELECT name, double_salary(emp.*) AS dream FROM emp WHERE emp.cubicle ~= point '(2,1)'; name | dream ------+------- Bill | 8400
注意使用语法 $1.salary
来选择参数行的单个字段。还请注意,调用 SELECT
命令如何使用 table_name
.*
将表的整个当前行选择为复合值。表行也可以仅使用表名来引用,如下所示
SELECT name, double_salary(emp) AS dream FROM emp WHERE emp.cubicle ~= point '(2,1)';
但这是一种不推荐的使用方式,因为它很容易混淆。(有关表行复合值的这两种表示法的详细信息,请参阅 第 8.16.5 节)。
有时,在动态构建复合参数值会很方便。可以使用 ROW
构造来实现。例如,我们可以调整传递给函数的数据
SELECT name, double_salary(ROW(name, salary*1.1, age, cubicle)) AS dream FROM emp;
还可以构建一个返回复合类型的函数。这是一个返回单个 emp
行的函数示例
CREATE FUNCTION new_emp() RETURNS emp AS $$ SELECT text 'None' AS name, 1000.0 AS salary, 25 AS age, point '(2,2)' AS cubicle; $$ LANGUAGE SQL;
在此示例中,我们为每个属性指定了一个常量值,但任何计算都可以替换这些常量。
请注意定义函数时的两个重要事项
查询中的 SELECT 列表顺序必须与列在复合类型中出现的顺序完全相同。(如上所示,为列命名对系统无关紧要。)
我们必须确保每个表达式的类型都可以转换为复合类型的相应列的类型。否则,我们将收到类似以下的错误
ERROR: return type mismatch in function declared to return emp
DETAIL: Final statement returns text instead of point at column 4.
与基本类型情况一样,系统不会自动插入显式转换,只会插入隐式或赋值转换。
定义相同函数的另一种方法是
CREATE FUNCTION new_emp() RETURNS emp AS $$ SELECT ROW('None', 1000.0, 25, '(2,2)')::emp; $$ LANGUAGE SQL;
在这里,我们编写了一个只返回正确复合类型单个列的 SELECT
。在这种情况下,这并没有真正更好,但在某些情况下,这是一个方便的选择——例如,如果我们通过调用另一个返回所需复合值的函数来计算结果。另一个例子是,如果我们试图编写一个返回复合域而不是纯复合类型的函数,总是需要将其声明为返回单个列,因为没有办法强制转换整个行结果。
我们可以直接调用此函数,方法是将其用于值表达式
SELECT new_emp(); new_emp -------------------------- (None,1000.0,25,"(2,2)")
或将其作为表函数调用
SELECT * FROM new_emp(); name | salary | age | cubicle ------+--------+-----+--------- None | 1000.0 | 25 | (2,2)
第二种方法将在 第 36.5.8 节 中更全面地介绍。
当使用返回复合类型的函数时,您可能只想要其结果中的一个字段(属性)。您可以使用类似以下的语法来实现
SELECT (new_emp()).name; name ------ None
需要额外的括号以避免解析器混淆。如果您尝试不带括号执行此操作,则会得到类似以下内容
SELECT new_emp().name; ERROR: syntax error at or near "." LINE 1: SELECT new_emp().name; ^
另一个选择是使用函数表示法提取属性
SELECT name(new_emp()); name ------ None
如 第 8.16.5 节 中所述,字段表示法和函数表示法是等效的。
使用返回复合类型的函数的另一种方法是将结果传递给接受正确行类型作为输入的另一个函数
CREATE FUNCTION getname(emp) RETURNS text AS $$ SELECT $1.name; $$ LANGUAGE SQL; SELECT getname(new_emp()); getname --------- None (1 row)
描述函数结果的另一种方法是使用 输出参数 定义函数,如下例所示
CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int) AS 'SELECT x + y' LANGUAGE SQL; SELECT add_em(3,7); add_em -------- 10 (1 row)
这与 第 36.5.2 节 中显示的 add_em
版本没有本质区别。输出参数的真正价值在于它们提供了一种方便的方法来定义返回多个列的函数。例如,
CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int) AS 'SELECT x + y, x * y' LANGUAGE SQL; SELECT * FROM sum_n_product(11,42); sum | product -----+--------- 53 | 462 (1 row)
本质上发生的情况是,我们为函数的返回创建了一个匿名的复合类型。上面的示例与以下内容具有相同的最终结果
CREATE TYPE sum_prod AS (sum int, product int); CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod AS 'SELECT $1 + $2, $1 * $2' LANGUAGE SQL;
但不必麻烦单独定义复合类型通常很方便。请注意,附加到输出参数的名称不仅仅是装饰,而是决定了匿名复合类型的列名。(如果您省略了输出参数的名称,系统将自行选择一个名称。)
从 SQL 调用此类函数时,输出参数不包含在调用参数列表中。这是因为 PostgreSQL 仅考虑输入参数来定义函数的调用签名。这意味着在删除函数等目的引用函数时,只有输入参数才相关。我们可以使用以下任一方式删除上述函数
DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int); DROP FUNCTION sum_n_product (int, int);
参数可以标记为 IN
(默认)、OUT
、INOUT
或 VARIADIC
。INOUT
参数既是输入参数(调用参数列表的一部分),也是输出参数(结果记录类型的一部分)。VARIADIC
参数是输入参数,但按如下方式特殊处理。
存储过程也支持输出参数,但它们的工作方式与函数略有不同。在 CALL
命令中,输出参数必须包含在参数列表中。例如,前面的银行账户借记例程可以这样编写
CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$ UPDATE bank SET balance = balance - debit WHERE accountno = tp1.accountno RETURNING balance; $$ LANGUAGE SQL;
要调用此存储过程,必须包含与 OUT
参数匹配的参数。习惯上写 NULL
CALL tp1(17, 100.0, NULL);
如果编写其他内容,则它必须是一个表达式,该表达式可以隐式强制转换为参数的声明类型,就像输入参数一样。但请注意,这样的表达式将不会被求值。
从 PL/pgSQL 调用存储过程时,而不是编写 NULL
,必须编写一个变量来接收存储过程的输出。有关详细信息,请参阅 第 41.6.3 节。
SQL函数可以声明为接受可变数量的参数,只要所有“可选”参数的数据类型都相同。可选参数将作为数组传递给函数。通过将最后一个参数标记为 VARIADIC
来声明函数;此参数必须声明为数组类型。例如
CREATE FUNCTION mleast(VARIADIC arr numeric[]) RETURNS numeric AS $$ SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i); $$ LANGUAGE SQL; SELECT mleast(10, -1, 5, 4.4); mleast -------- -1 (1 row)
有效地,VARIADIC
位置及之后的所有实际参数都将收集到一个一维数组中,就好像您编写了
SELECT mleast(ARRAY[10, -1, 5, 4.4]); -- doesn't work
但是,您实际上无法编写该内容——或者至少,它不会匹配此函数定义。标记为 VARIADIC
的参数匹配其元素类型的一次或多次出现,而不是匹配其自身类型。
有时,能够将已构建的数组传递给可变函数很有用;当一个可变函数想要将其数组参数传递给另一个函数时,这特别方便。此外,这是在允许不受信任用户创建对象的模式中查找可变函数的唯一安全方法;请参阅 第 10.3 节。您可以通过在调用中指定 VARIADIC
来执行此操作
SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]);
这可以防止函数的可变参数展开为其元素类型,从而允许数组参数值正常匹配。VARIADIC
只能附加到函数调用的最后一个实际参数。
在调用中指定 VARIADIC
也是将空数组传递给可变函数的唯一方法,例如
SELECT mleast(VARIADIC ARRAY[]::numeric[]);
简单地写 SELECT mleast()
不起作用,因为可变参数必须匹配至少一个实际参数。(如果您想允许此类调用,可以定义另一个名为 mleast
的函数,但没有参数。)
从可变参数生成的数组元素参数被视为没有自己的名称。这意味着除了指定 VARIADIC
之外,无法使用命名参数 (第 4.3 节) 来调用可变函数。例如,这可以正常工作
SELECT mleast(VARIADIC arr => ARRAY[10, -1, 5, 4.4]);
但这些不行
SELECT mleast(arr => 10); SELECT mleast(arr => ARRAY[10, -1, 5, 4.4]);
函数可以声明一些或所有输入参数具有默认值。当函数被调用时,如果实际参数不足,将插入默认值。由于只能从实际参数列表的末尾省略参数,因此在具有默认值的参数之后的任何参数也必须具有默认值。(尽管使用命名参数表示法可以放宽此限制,但仍会强制执行此限制,以便位置参数表示法可以正常工作。)无论您是否使用它,此功能都会在调用数据库中某些用户不信任其他用户的函数时产生预防措施;请参阅 第 10.3 节。
例如
CREATE FUNCTION foo(a int, b int DEFAULT 2, c int DEFAULT 3) RETURNS int LANGUAGE SQL AS $$ SELECT $1 + $2 + $3; $$; SELECT foo(10, 20, 30); foo ----- 60 (1 row) SELECT foo(10, 20); foo ----- 33 (1 row) SELECT foo(10); foo ----- 15 (1 row) SELECT foo(); -- fails since there is no default for the first argument ERROR: function foo() does not exist
可以使用 =
号代替关键字 DEFAULT
。
所有 SQL 函数都可以在查询的 FROM
子句中使用,但这对于返回复合类型的函数尤其有用。如果函数定义为返回基本类型,则表函数生成一个单列表。如果函数定义为返回复合类型,则表函数为复合类型的每个属性生成一列。
以下是一个例子
CREATE TABLE foo (fooid int, foosubid int, fooname text); INSERT INTO foo VALUES (1, 1, 'Joe'); INSERT INTO foo VALUES (1, 2, 'Ed'); INSERT INTO foo VALUES (2, 1, 'Mary'); CREATE FUNCTION getfoo(int) RETURNS foo AS $$ SELECT * FROM foo WHERE fooid = $1; $$ LANGUAGE SQL; SELECT *, upper(fooname) FROM getfoo(1) AS t1; fooid | foosubid | fooname | upper -------+----------+---------+------- 1 | 1 | Joe | JOE (1 row)
如示例所示,我们可以像处理常规表的列一样处理函数结果的列。
请注意,我们只从函数中得到了一行。这是因为我们没有使用 SETOF
。下一节将对此进行描述。
当 SQL 函数声明为返回 SETOF
时,将执行函数中的最后一个查询直到完成,并且它输出的每一行都将作为结果集的一个元素返回。sometype
此功能通常在 FROM
子句中调用函数时使用。在这种情况下,函数返回的每一行都将成为查询看到的表的一行。例如,假设表 foo
的内容与上面相同,并且我们说
CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$ SELECT * FROM foo WHERE fooid = $1; $$ LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1;
然后我们将得到
fooid | foosubid | fooname -------+----------+--------- 1 | 1 | Joe 1 | 2 | Ed (2 rows)
通过使用输出参数定义列,也可以返回多行,如下所示
CREATE TABLE tab (y int, z int); INSERT INTO tab VALUES (1, 2), (3, 4), (5, 6), (7, 8); CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int) RETURNS SETOF record AS $$ SELECT $1 + tab.y, $1 * tab.y FROM tab; $$ LANGUAGE SQL; SELECT * FROM sum_n_product_with_tab(10); sum | product -----+--------- 11 | 10 13 | 30 15 | 50 17 | 70 (4 rows)
这里的关键点是,您必须编写 RETURNS SETOF record
来指示函数返回多行而不是一行。如果只有一个输出参数,请使用该参数的类型而不是 record
。
通过调用集返回函数(其参数来自表或子查询的连续行)来构造查询结果,这通常很有用。首选方法是使用 LATERAL
关键字,该关键字在 第 7.2.1.5 节 中进行了描述。以下是一个使用集返回函数来枚举树结构元素的示例
SELECT * FROM nodes; name | parent -----------+-------- Top | Child1 | Top Child2 | Top Child3 | Top SubChild1 | Child1 SubChild2 | Child1 (6 rows) CREATE FUNCTION listchildren(text) RETURNS SETOF text AS $$ SELECT name FROM nodes WHERE parent = $1 $$ LANGUAGE SQL STABLE; SELECT * FROM listchildren('Top'); listchildren -------------- Child1 Child2 Child3 (3 rows) SELECT name, child FROM nodes, LATERAL listchildren(name) AS child; name | child --------+----------- Top | Child1 Top | Child2 Top | Child3 Child1 | SubChild1 Child1 | SubChild2 (5 rows)
此示例不做任何我们无法通过简单连接完成的事情,但在更复杂的计算中,将一些工作放入函数中的选项可能非常方便。
集返回函数也可以在查询的 select 列表中调用。对于查询本身生成的每一行,都将调用集返回函数,并为函数结果集中的每个元素生成一个输出行。上面的示例也可以通过以下查询完成
SELECT listchildren('Top'); listchildren -------------- Child1 Child2 Child3 (3 rows) SELECT name, listchildren(name) FROM nodes; name | listchildren --------+-------------- Top | Child1 Top | Child2 Top | Child3 Child1 | SubChild1 Child1 | SubChild2 (5 rows)
在最后一个 SELECT
中,请注意对于 Child2
、Child3
等没有输出行。这是因为 listchildren
对这些参数返回空集,因此不生成结果行。这与我们在使用 LATERAL
语法时从内部连接到函数结果的行为相同。
PostgreSQL 在查询 select 列表中的集返回函数的行为几乎与该集返回函数在 LATERAL FROM
子句项中编写的行为完全相同。例如,
SELECT x, generate_series(1,5) AS g FROM tab;
几乎等同于
SELECT x, g FROM tab, LATERAL generate_series(1,5) AS g;
它将完全相同,只是在此特定示例中,计划程序可以将 g
放在嵌套循环连接的外部,因为 g
对 tab
没有实际的横向依赖。这将导致不同的输出行顺序。Select 列表中的集返回函数始终作为嵌套循环连接的内部进行评估,以便在考虑 FROM
子句的下一行之前,先将函数运行完毕。
如果查询的 select 列表中有多个集返回函数,其行为与将这些函数放入单个 LATERAL ROWS FROM( ... )
FROM
子句项中的行为类似。对于底层查询的每一行,将有一个输出行使用每个函数的第一个结果,然后一个输出行使用第二个结果,依此类推。如果一些集返回函数产生的行少于其他函数,则会用 NULL 值替换缺失的数据,以便为每个底层行发出的总行数与产生最多输出的集返回函数相同。因此,集返回函数运行“锁定”,直到它们全部耗尽,然后执行继续处理下一个底层行。
集返回函数可以嵌套在 select 列表中,尽管在 FROM
子句项中不允许这样做。在这种情况下,每个嵌套级别都将单独处理,就好像它是单独的 LATERAL ROWS FROM( ... )
项一样。例如,在
SELECT srf1(srf2(x), srf3(y)), srf4(srf5(z)) FROM tab;
对于 tab
的每一行,集返回函数 srf2
、srf3
和 srf5
将以锁定方式运行,然后 srf1
和 srf4
将以锁定方式应用于由较低函数产生的每一行。
集返回函数不能在条件求值构造中使用,例如 CASE
或 COALESCE
。例如,考虑
SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;
看起来这应该为 x > 0
的输入行产生五次重复,为不满足此条件的输入行产生一次重复;但实际上,因为 generate_series(1, 5)
将在 CASE
表达式求值之前在隐式的 LATERAL FROM
项中运行,它将为每个输入行产生五次重复。为了减少混淆,此类情况将在解析时产生错误。
如果函数的最后一个命令是带有 RETURNING
的 INSERT
、UPDATE
、DELETE
或 MERGE
,则即使函数未声明为 SETOF
或调用查询未获取所有结果行,该命令也将始终执行直到完成。由 RETURNING
子句产生的任何额外行都将被静默丢弃,但命令的表修改仍然会发生(并且在从函数返回之前都会完成)。
在 PostgreSQL 10 之前,在同一个 select 列表中放置多个集返回函数,除非它们总是产生相等数量的行,否则行为并不十分合理。否则,您得到的结果行数是集返回函数产生的行数的最小公倍数。此外,嵌套的集返回函数不像上面描述的那样工作;相反,一个集返回函数最多可以有一个集返回参数,并且每个集返回函数的嵌套都是独立运行的。此外,之前允许条件执行(CASE
等中的集返回函数),这使得事情更加复杂。在编写需要与旧版 PostgreSQL 兼容的查询时,建议使用 LATERAL
语法,因为这将提供跨不同版本的Consistent的结果。如果您有一个依赖于集返回函数的条件执行的查询,您可以通过将条件测试移到一个自定义的集返回函数来修复它。例如,
SELECT x, CASE WHEN y > 0 THEN generate_series(1, z) ELSE 5 END FROM tab;
可以变成
CREATE FUNCTION case_generate_series(cond bool, start int, fin int, els int) RETURNS SETOF int AS $$ BEGIN IF cond THEN RETURN QUERY SELECT generate_series(start, fin); ELSE RETURN QUERY SELECT els; END IF; END$$ LANGUAGE plpgsql; SELECT x, case_generate_series(y > 0, 1, z, 5) FROM tab;
这种形式将在所有版本的 PostgreSQL 中以相同的方式工作。
TABLE
的函数 #还有一种声明函数返回集合的方法,即使用语法 RETURNS TABLE(
。这等同于使用一个或多个 columns
)OUT
参数,再加上将函数声明为返回 SETOF record
(或 SETOF
单个输出参数的类型,视情况而定)。此表示法在 SQL 标准的最新版本中指定,因此可能比使用 SETOF
更具可移植性。
例如,前面的求和与求积示例也可以这样完成
CREATE FUNCTION sum_n_product_with_tab (x int) RETURNS TABLE(sum int, product int) AS $$ SELECT $1 + tab.y, $1 * tab.y FROM tab; $$ LANGUAGE SQL;
不允许将显式 OUT
或 INOUT
参数与 RETURNS TABLE
表示法一起使用——您必须将所有输出列放在 TABLE
列表中。
SQL函数可以声明为接受和返回 第 36.2.5 节 中描述的多态类型。这是一个多态函数 make_array
,它从两个任意数据类型的元素构建数组
CREATE FUNCTION make_array(anyelement, anyelement) RETURNS anyarray AS $$ SELECT ARRAY[$1, $2]; $$ LANGUAGE SQL; SELECT make_array(1, 2) AS intarray, make_array('a'::text, 'b') AS textarray; intarray | textarray ----------+----------- {1,2} | {a,b} (1 row)
注意类型转换 'a'::text
的使用,以指定参数的类型为 text
。如果参数只是一个字符串文字,则需要这样做,否则它将被视为 unknown
类型,并且 unknown
类型的数组不是有效类型。如果不进行类型转换,您将收到类似以下的错误
ERROR: could not determine polymorphic type because input has type unknown
使用上面声明的 make_array
,您必须提供两个相同数据类型的参数;系统不会尝试解析任何类型差异。因此,例如,这不起作用
SELECT make_array(1, 2.5) AS numericarray; ERROR: function make_array(integer, numeric) does not exist
另一种方法是使用“通用”多态类型系列,它允许系统尝试识别合适的通用类型
CREATE FUNCTION make_array2(anycompatible, anycompatible) RETURNS anycompatiblearray AS $$ SELECT ARRAY[$1, $2]; $$ LANGUAGE SQL; SELECT make_array2(1, 2.5) AS numericarray; numericarray -------------- {1,2.5} (1 row)
由于通用类型解析规则在所有输入类型未知时默认为选择 text
类型,因此这也有效
SELECT make_array2('a', 'b') AS textarray; textarray ----------- {a,b} (1 row)
允许多态参数具有固定返回类型,但反之则不然。例如
CREATE FUNCTION is_greater(anyelement, anyelement) RETURNS boolean AS $$ SELECT $1 > $2; $$ LANGUAGE SQL; SELECT is_greater(1, 2); is_greater ------------ f (1 row) CREATE FUNCTION invalid_func() RETURNS anyelement AS $$ SELECT 1; $$ LANGUAGE SQL; ERROR: cannot determine result data type DETAIL: A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
多态性可以与带有输出参数的函数一起使用。例如
CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray) AS 'select $1, array[$1,$1]' LANGUAGE SQL; SELECT * FROM dup(22); f2 | f3 ----+--------- 22 | {22,22} (1 row)
多态性也可以与可变函数一起使用。例如
CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$ SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i); $$ LANGUAGE SQL; SELECT anyleast(10, -1, 5, 4); anyleast ---------- -1 (1 row) SELECT anyleast('abc'::text, 'def'); anyleast ---------- abc (1 row) CREATE FUNCTION concat_values(text, VARIADIC anyarray) RETURNS text AS $$ SELECT array_to_string($2, $1); $$ LANGUAGE SQL; SELECT concat_values('|', 1, 4, 2); concat_values --------------- 1|4|2 (1 row)
当 SQL 函数有一个或多个可排序数据类型的参数时,会根据分配给实际参数的排序规则为每个函数调用确定排序规则,如 第 23.2 节 所述。如果成功确定了排序规则(即,参数之间没有隐式排序规则冲突),则所有可排序参数都将被隐式视为具有该排序规则。这将影响函数内对排序规则敏感的操作的行为。例如,使用上面描述的 anyleast
函数,结果将是
SELECT anyleast('abc'::text, 'ABC');
将取决于数据库的默认排序规则。在 C
区域设置中,结果将是 ABC
,但在许多其他区域设置中,结果将是 abc
。可以通过向任何参数添加 COLLATE
子句来强制使用排序规则,例如
SELECT anyleast('abc'::text, 'ABC' COLLATE "C");
或者,如果您希望某个函数在被调用时无论使用何种排序规则,都始终以特定的排序规则运行,则可以在函数定义中根据需要插入 COLLATE
子句。此版本的 anyleast
将始终使用 en_US
区域设置来比较字符串。
CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$ SELECT min($1[i] COLLATE "en_US") FROM generate_subscripts($1, 1) g(i); $$ LANGUAGE SQL;
但请注意,如果应用于不可排序的数据类型,此操作将引发错误。
如果实际参数之间无法识别出共同的排序规则,则 SQL 函数会将参数视为具有其数据类型的默认排序规则(通常是数据库的默认排序规则,但对于域类型参数可能不同)。
可排序参数的行为可以被认为是多态性的一种有限形式,仅适用于文本数据类型。
如果您在文档中发现任何不正确、与您对特定功能的实际使用体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。