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

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

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

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

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

任意一组 SQL 语言命令都可以打包并定义成函数。除 SELECT 查询外,这些命令还可以包含数据修改查询 (INSERTUPDATEDELETE ),以及其他 SQL 命令。(在 SQL 函数中不能使用事务控制命令,例如 COMMITSAVEPOINT,也不能使用某些 实用命令,例如 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 命令的语法要求把函数体写成一个字符串常量。对于字符串常量,通常使用美元引用最方便(见Section 4.1.2.4)。如果你选择使用常规的单引号字符串常量语法,那么必须在函数体中将单引号(')和反斜线(\)写成双份(假定使用转义字符串语法,见Section 4.1.2.1)。

36.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记法。

36.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;

如果在SELECTRETURNING子句中, SQL函数的最终返回值类型与函数声明的结果类型不完全匹配, 如果可以使用隐式或赋值转换, PostgreSQL将自动将该值转换为所需类型。 否则,你必须写出显式类型转换。 例如,假设我们希望前面的add_em函数返回float8类型。 只需编写

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

因为integer的和可以隐式转换为float8。 (有关转换的更多信息,请参见Chapter 10CREATE CAST。)

36.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:  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)

第二种方式会在Section 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

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

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

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

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

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

36.5.5. SQL有输出参数的过程 #

过程也支持输出参数,但是它们工作方式与函数略有不同。 在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,而必须写一个用于接收过程输出的变量。详见Section 41.6.3

36.5.6. 带有可变数量参数的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]);

36.5.7. 带有参数默认值的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

36.5.8. 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。 这会在下一节中介绍。

36.5.9. 返回集合的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放在嵌套循环连接的外侧,因为gtab没有实际的横向依赖。那会导致一种不同的输出行顺序。选择列表中的集合返回函数总是会被计算,就好像它们在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

如果一个函数的最后一条命令是带有 RETURNING 子句的 INSERTUPDATEDELETEMERGE,那么即使该函数没有声明为 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 中都能正常工作。

36.5.10. 返回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列表中。

36.5.11. 多态SQL函数 #

SQL 函数可以被声明为接受并返回Section 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

另一种方法是使用common这一族多态类型,它允许系统尝试识别一个合适的公共类型:

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)

多态也可以用于 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)

36.5.12. 带有排序规则的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 函数会把参数视为带有其数据类型的默认排序规则 (通常是数据库的默认排序规则,但对域类型参数来说也可能不同)。

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

提交更正

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