支持的版本:当前17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 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 命令。(您不能在函数中使用事务控制命令,例如 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)

您也可以将其编写为过程,从而避免了返回类型的问题。例如

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);

注意

使用名称引用 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。(有关类型转换的更多信息,请参阅 第 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 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,而没有的那些则生成单个重复行;但实际上,由于 generate_series(1, 5) 将在隐式的 LATERAL FROM 项中运行,然后再评估 CASE 表达式,因此它将生成每个输入行的五个重复行。为了减少混淆,这种情况会产生解析时错误。

注意

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

注意

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

另一种方法是使用 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)

多态性也可以与可变参数函数一起使用。例如

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

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

提交更正

如果您在文档中发现任何不正确、与您使用特定功能的经验不符或需要进一步澄清的地方,请使用此表格报告文档问题。