选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

38.5. 查询语言(SQL)函数 #

SQL 函数执行由任意 SQL 语句组成的一个列表,并返回列表中最后一个查询的结果。在简单(非集合)情况下,将返回最后一个查询结果的第一行。(请记住,除非使用 ORDER BY,否则多行结果中的第一行并没有明确定义。)如果最后一个查询完全没有返回任何行,则返回空值。

或者,一个 SQL 函数可以通过指定函数的返回类型为SETOF sometype被声明为返回一个集合(也就是多个行),或者等效地声明它为RETURNS TABLE(columns)。在这种情况下,最后一个查询的结果的所有行会被返回。下文将给出进一步的细节。

SQL 函数的函数体必须是一个由分号分隔的 SQL 语句列表。最后一条语句后的分号是可选的。除非该函数被声明为返回 void,否则最后一条语句必须是 SELECT,或者是 INSERTUPDATEDELETE 并带有 RETURNING 子句。

任意一组 SQL 语言命令都可以打包并定义成函数。除 SELECT 查询外,这些命令还可以包含数据修改查询(INSERTUPDATEDELETE),以及其他 SQL 命令。(不能使用事务控制命令,例如 COMMITSAVEPOINT,以及某些实用命令,例如 VACUUM,来编写 SQL 函数。)不过,最后一条命令必须是 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)

Note

SQL 函数的整个函数体都会先经过解析,然后才开始执行。虽然 SQL 函数可以包含修改系统目录的命令(例如 CREATE TABLE),但在对函数中的后续命令进行解析分析时,这些命令的效果还不可见。因此,例如将 CREATE TABLE foo (...); INSERT INTO foo VALUES(...); 打包到同一个 SQL 函数中,就不会按预期工作,因为 foo 在解析 INSERT 命令时还不存在。在这类情况下,建议使用 PL/pgSQL 而不是 SQL 函数。

CREATE FUNCTION 命令的语法要求把函数体写成一个字符串常量。对于字符串常量,通常使用美元引用最方便(见Section 4.1.2.4)。如果你选择使用常规的单引号字符串常量语法,那么必须在函数体中将单引号(')和反斜线(\)写成双份(假定使用转义字符串语法,见Section 4.1.2.1)。

38.5.1. SQL函数的参数 #

一个 SQL 函数的参数可以在函数体中用名称或编号引用。下面会有两种方法的示例。

要使用一个名称,将函数参数声明为带有一个名称,然后在函数体中只写该名称。如果参数名称与函数内当前 SQL 命令中的任意列名相同,列名将优先。如果不想这样,可以用函数本身的名称来限定参数名,也就是function_name.argument_name(如果这会与一个被限定的列名冲突,照例还是列名赢得优先。你可以通过为 SQL 命令中的表选择一个不同的别名来避免这种混淆)。

在更旧的数字方法中,参数可以用语法$n引用:$1指的是第一个输入参数,$2指的是第二个,以此类推。不管特定的参数是否使用名称声明,这种方法都有效。

如果一个参数是一种复合类型,那么点号记法(如 argname.fieldname$1.fieldname)也可以被用来 访问该参数的属性。同样,你可能需要用函数的名称来限定参数的名称以避免歧义。

SQL 函数参数只能被用做数据值而不能作为标识符。例如这是合理的:

INSERT INTO mytable VALUES ($1);

但这样就不行:

INSERT INTO $1 VALUES (42);

Note

使用名称来引用 SQL 函数参数的能力是在PostgreSQL 9.2 中加入的。要在老的服务器中使用的函数必须使用$n记法。

38.5.2. 基本类型上的SQL函数 #

最简单的 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;

用户可以这样执行该函数,从 17 号账户中扣除 $100.00:

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 函数必须返回与其声明的结果类型完全一致的类型。这可能需要插入显式类型转换。例如,假设我们希望前面的 add_em 函数改为返回 float8 类型。下面这样是不行的:

CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
    SELECT $1 + $2;
$$ LANGUAGE SQL;

即便在其他上下文中,PostgreSQL愿意插入隐式类型转换,把 integer 转换为 float8。我们需要将它写成:

CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
    SELECT ($1 + $2)::float8;
$$ LANGUAGE SQL;

38.5.3. 复合类型上的SQL函数 #

编写接受复合类型参数的函数时,我们不仅必须指定要用哪个参数,还必须指定该参数的哪个属性(字段)。例如,假设 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)';

但这种用法已被废弃,因为它很容易让人混淆(关于表行组合值这两种记法的更多细节,见Section 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;

在这个示例中,我们为每一个属性指定了一个常量值,但是可以用任何计算来替换这些常量。

定义该函数时要注意两件重要的事情:

  • 查询中的选择列表顺序必须与列在该复合类型所关联的表中出现的顺序完全相同。(正如我们上面所做的那样,命名列与系统无关。)

  • 我们必须确保每个表达式的类型与复合类型中对应列的类型相匹配,必要时插入类型转换。否则,就会得到这样的错误:

    
    ERROR:  function declared to return emp returns varchar instead of text at column 1
    
    

    与基础类型的情况一样,该函数不会自动插入任何类型转换。

定义同一个函数的另一种方法是:

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)

第二种方式会在Section 38.5.7中进一步说明。

当你使用返回复合类型的函数时,可能只需要取其结果中的一个字段(属性)。可以使用下面这样的语法:

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

Section 8.16.5所述,字段记法和函数记法是等价的。

另一种使用函数返回复合类型的方法是将结果传递给另一个接受正确行类型作为输入的函数:

CREATE FUNCTION getname(emp) RETURNS text AS $$
    SELECT $1.name;
$$ LANGUAGE SQL;

SELECT getname(new_emp());
 getname
---------
 None
(1 row)

38.5.4. 带有输出参数的SQL函数 #

描述函数结果的另一种方法是使用输出参数,例如:

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)

这与Section 38.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(默认)、OUTINOUT或者VARIADIC。 一个INOUT参数既作为一个输入参数(调用参数列表的一部分)又作为一个输出参数(结果记录类型的一部分)。 VARIADIC参数是输入参数,但被按照下文所述特殊对待。

38.5.5. 带有可变数量参数的SQL函数 #

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 的参数匹配的是其元素类型出现一次或多次, 而不是它自身的数组类型。

有时候,能够把一个已经构造好的数组传给 variadic 函数会很有用,尤其是当一个 variadic 函数想把它的数组参数再传给另一个函数时。此外,在允许不受信任用户创建对象的模式中调用 variadic 函数时,这是唯一安全的方式,见Section 10.3。你可以在调用中指定VARIADIC来做到这一点:

SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]);

这样会阻止函数的 variadic 参数按其元素类型展开,从而让数组实参能够按常规方式匹配。VARIADIC 只能附加在函数调用的最后一个实参上。

在调用中指定VARIADIC也是向 variadic 函数传递空数组的唯一方式,例如:

SELECT mleast(VARIADIC ARRAY[]::numeric[]);

仅仅写成SELECT mleast()是行不通的,因为 variadic 参数必须匹配至少一个实参。(如果你希望允许这种调用,可以再定义一个同名且不带参数的函数mleast。)

从 variadic 参数派生出的数组元素参数会被视为没有自己的名字。这意味着除非你指定了 VARIADIC,否则不能使用命名参数来调用 variadic 函数(Section 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]);

38.5.6. 带有参数默认值的SQL函数 #

函数可以被声明为对一些或者所有输入参数具有默认值。只要调用函数时 没有给出足够多的实参,就会插入默认值来弥补缺失的实参。由于参数只 能从实参列表的尾部开始被省略,在一个有默认值的参数之后的所有参数 都不得不也具有默认值(尽管使用命名参数记法可以允许放松这种限制, 这种限制仍然会被强制以便位置参数记法能工作)。不管你是否使用它,这种能力都要求在某些用户不信任其他用户的数据中调用函数时做一些预防措施,见Section 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

38.5.7. SQL 函数作为表来源 #

所有的 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。 这会在下一节中介绍。

38.5.8. 返回集合的SQL函数 #

当一个 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 关键字,详见Section 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 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 中,注意没有为 Child2Child3 等显示任何输出行。这是因为 listchildren 对这些参数返回的是空集,因此不会生成结果行。这与使用 LATERAL 语法把函数结果做内连接时得到的行为相同。

PostgreSQL对查询选择列表中的集合返回函数的处理方式,几乎与将该函数写在 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。这会导致输出行的顺序不同。对选择列表中的集合返回函数求值时,总是将其视为位于与 FROM 子句其余部分的嵌套循环连接的内侧,因此在考虑 FROM 子句的下一行之前,这些函数都会运行到完成。

如果在查询的选择列表中有不止一个集合返回函数,则行为类似于把那些函数放到一个单一的LATERAL ROWS FROM( ... ) FROM子句项中的行为。对于来自底层查询的每一行,都有一个用到每个函数首个结果的输出行,然后是一个使用每个函数第二个结果的输出行,以此类推。如果某些集合返回函数产生的输出比其他函数少,会用空值代替缺失的数据,因此为一个底层行形成的总行数等于产生最多输出的集合返回函数的输出行数。因此集合返回函数会步调一致地运行直到它们的输出被耗尽,然后用下一个底层行继续执行。

集合返回函数可以被嵌套在一个选择列表中,不过在FROM子句项中不允许这样做。在这种情况下,嵌套的每一层会被单独对待,就像它是一个单独的LATERAL ROWS FROM( ... )项一样。例如,在

SELECT srf1(srf2(x), srf3(y)), srf4(srf5(z)) FROM tab;

中,集合返回函数srf2srf3srf5将为tab的每一行步调一致地运行,然后会对较低层的函数产生的每一行以步调一致的形式应用srf1srf4

CASECOALESCE这样的条件计算结构中,不能使用集合返回函数。例如,考虑

SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;

看起来这个语句应该产生满足x > 0的输入行的五次重复,以及不满足的行的一次重复。但实际上,由于在CASE表达时被计算前,generate_series(1, 5)会被运行在一个隐式的LATERAL FROM项中,它会为每个输入行产生五次重复。为了减少混乱,这类情况会产生一个解析时错误。

Note

如果一个函数的最后一条命令是 INSERTUPDATEDELETE 并带有 RETURNING,那么即使该函数没有声明为 SETOF,或者调用它的查询并未取走全部结果行,该命令也总会执行到完成。RETURNING 子句产生的额外结果行会被静默丢弃,但相应的表修改仍然会发生,并且会在函数返回前全部完成。

Note

PostgreSQL 10 之前,除非多个集合返回函数始终产生相同数量的行,否则把它们放在同一个选择列表中的行为并不合理。 否则,得到的输出行数会是这些集合返回函数各自产生行数的最小公倍数。 此外,嵌套的集合返回函数也不像上文所描述的那样工作;相反,一个集合返回函数至多只能有一个集合返回参数, 并且每一层嵌套都会独立运行。再加上过去还允许条件执行(即在 CASE 等结构内部使用集合返回函数), 事情就更加复杂了。 如果你要编写需要在旧版 PostgreSQL 上运行的查询,建议使用 LATERAL 语法, 因为这样在不同版本之间能够得到一致的结果。 如果你的查询依赖于集合返回函数的条件执行,可以把条件判断移动到自定义集合返回函数内部来修复。例如,

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 中都能正常工作。

38.5.9. 返回TABLESQL函数 #

还有另一种方法可以把函数声明为返回一个集合,即使用 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列表中。

38.5.10. 多态SQL函数

SQL 函数可以被声明为接受并返回这些多态类型:anyelementanyarrayanynonarrayanyenumanyrange。关于多态函数的更详细解释,见 Section 38.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"

允许存在具有固定返回类型的多态参数,但反过来则不允许。例如:

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 function returning a polymorphic type must have at least one polymorphic argument.

多态也可以和带输出参数的函数一起使用。例如:

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)

多态也可以用于 variadic 函数。例如:

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)

38.5.11. 带有排序规则的SQL函数

当一个 SQL 函数有一个或多个可设定排序规则的数据类型参数时,会根据实参所带的排序规则,为每次函数调用确定一个排序规则,详见 Section 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 函数会把参数视为带有其数据类型的默认排序规则 (通常是数据库的默认排序规则,但对域类型参数来说也可能不同)。

这种可设定排序规则参数的行为,可以看作是仅适用于文本数据类型的一种受限多态形式。