2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本: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

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

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

或者,可以通过将函数的返回类型指定为 SETOF sometype,或者等效地将其声明为 RETURNS TABLE(columns),来声明 SQL 函数返回一个集合(即多行)。在这种情况下,将返回最后一个查询结果的所有行。更多细节如下所示。

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

中的任何命令集合SQL语言都可以打包在一起并定义为函数。除了 SELECT 查询之外,这些命令还可以包括数据修改查询(INSERTUPDATEDELETEMERGE),以及其他 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 扩展。

注意

在执行 SQL 函数的任何部分之前,都会解析其整个主体。虽然 SQL 函数可以包含更改系统目录的命令(例如,CREATE TABLE),但此类命令的效果在函数中后面命令的解析分析期间将不可见。因此,例如,如果将 CREATE TABLE foo (...); INSERT INTO foo VALUES(...); 打包到单个 SQL 函数中,则不会按预期工作,因为在解析 INSERT 命令时 foo 尚不存在。建议在这种情况下使用 PL/pgSQL 而不是 SQL 函数。

CREATE FUNCTION 命令的语法要求将函数主体编写为字符串常量。通常最方便的是对字符串常量使用美元引用(请参阅 第 4.1.2.4 节)。如果您选择使用常规单引号字符串常量语法,则必须在函数体中将单引号 (') 和反斜杠 (\) 加倍(假设使用转义字符串语法)(请参阅 第 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);

注意

PostgreSQL 9.2 中添加了使用名称引用 SQL 函数参数的功能。要在较旧的服务器中使用的函数必须使用 $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。(有关转换的更多信息,请参阅 第 10 章CREATE 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)';

但这种用法已弃用,因为它很容易造成混淆。(有关表行的复合值的这两种表示法的详细信息,请参阅 第 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)

第二种方法在 第 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)

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)

这与 第 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(默认值)、OUTINOUTVARIADICINOUT 参数既用作输入参数(调用参数列表的一部分),也用作输出参数(结果记录类型的一部分)。 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。有关详细信息,请参阅 第 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 的参数与其元素类型的一个或多个实例匹配,而不是与其自身类型匹配。

有时,能够将已经构造好的数组传递给可变参数函数很有用;当一个可变参数函数想要将其数组参数传递给另一个函数时,这尤其方便。此外,这是调用在允许不受信任用户创建对象的模式中找到的可变参数函数的唯一安全方法;请参阅 第 10.3 节。您可以通过在调用中指定 VARIADIC 来做到这一点

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

这可以防止函数的可变参数扩展为其元素类型,从而允许数组参数值正常匹配。 VARIADIC 只能附加到函数调用的最后一个实际参数。

在调用中指定 VARIADIC 也是将空数组传递给可变参数函数的唯一方法,例如

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

简单地编写 SELECT mleast() 是行不通的,因为可变参数必须至少匹配一个实际参数。(如果要允许此类调用,您可以定义另一个也名为 mleast 的函数,该函数不带参数。)

从可变参数生成的数组元素参数被视为没有自己的名称。这意味着无法使用命名参数调用可变参数函数(第 4.3 节),除非您指定 VARIADIC。例如,这将起作用

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具有参数默认值的函数 #

可以为部分或所有输入参数声明具有默认值的函数。只要使用不足够多的实际参数调用函数,就会插入默认值。由于参数只能从实际参数列表的末尾省略,因此具有默认值的参数之后的所有参数也必须具有默认值。(尽管使用命名参数表示法可以放宽此限制,但它仍然被强制执行,以便位置参数表示法能够正常工作。)无论您是否使用它,此功能都需要在某些用户不信任其他用户的数据库中调用函数时采取预防措施;请参阅 第 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 关键字,这在 第 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 中,注意没有为 Child2Child3 等显示输出行。这是因为 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 放在嵌套循环连接的外部,因为 gtab 没有实际的横向依赖关系。这将导致不同的输出行顺序。select 列表中的返回集合函数始终按它们位于与 FROM 子句其余部分的嵌套循环连接内部的方式进行计算,以便在考虑来自 FROM 子句的下一行之前,先运行函数。

如果查询的 select 列表中有多个返回集合的函数,则行为类似于将函数放入单个 LATERAL ROWS FROM( ... ) FROM 子句项中获得的行为。对于来自底层查询的每一行,都有一个使用每个函数的第一个结果的输出行,然后是一个使用第二个结果的输出行,依此类推。如果某些返回集合的函数生成的输出少于其他函数,则会用空值替换缺失的数据,以便为一个底层行发出的总行数与生成最多输出的返回集合的函数相同。因此,返回集合的函数会以 同步 方式运行,直到它们全部耗尽,然后执行继续处理下一底层行。

返回集合的函数可以在 select 列表中嵌套,尽管在 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 的输入行的五次重复,以及不满足条件的输入行的一次重复;但实际上,由于 generate_series(1, 5) 会在隐式的 LATERAL FROM 项中运行,然后再对 CASE 表达式求值,因此它会生成每个输入行的五次重复。为了减少混淆,这种情况会在解析时产生错误。

注意

如果函数的最后一个命令是带有 RETURNINGINSERTUPDATEDELETEMERGE,则该命令将始终执行到完成,即使该函数未使用 SETOF 声明,或者调用查询未获取所有结果行。 RETURNING 子句生成的任何额外行都会被静默删除,但命令的表修改仍然会发生(并且在从函数返回之前全部完成)。

注意

PostgreSQL 10 之前,将多个返回集合的函数放在同一个 select 列表中的行为不是很明智,除非它们始终生成相同数量的行。否则,您获得的输出行数等于返回集合的函数生成的行数的最小公倍数。此外,嵌套的返回集合函数不像上面描述的那样工作;相反,返回集合的函数最多可以有一个返回集合的参数,并且每个嵌套的返回集合函数都是独立运行的。此外,以前允许条件执行(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. SQL返回 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;

不允许将显式的 OUTINOUT 参数与 RETURNS TABLE 符号一起使用 — 您必须将所有输出列放在 TABLE 列表中。

36.5.11. 多态SQL函数 #

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)

36.5.12. SQL具有排序规则的函数 #

当 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 函数会将其参数视为具有其数据类型的默认排序规则(通常是数据库的默认排序规则,但对于域类型的参数可能不同)。

可排序参数的行为可以被认为是多态性的一种有限形式,仅适用于文本数据类型。

提交更正

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