选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.0 已于 2015 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

35.4. Query Language (SQL) Functions #

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

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

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

SQL 语言中的任何命令集合都可以打包在一起并 定义为函数。除了 SELECT 查询之外,这些命令 还可以包括数据修改查询(INSERTUPDATEDELETE),以及 其他 SQL 命令。(唯一的例外是不能把 BEGINCOMMITROLLBACKSAVEPOINT 命令放进 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 FUNCTION 命令的语法要求把函数体写成一个字符串常量。对于字符串常量,通常使用美元引用最方便(见第 4.1.2.4 节)。如果你选择使用常规的单引号字符串常量语法,那么必须在函数体中将单引号(')和反斜线(\)写成双份(假定使用转义字符串语法,见第 4.1.2.1 节)。

SQL 函数的参数在函数体中使用语法 $n 引用:$1 引用第一个参数,$2 引用第二个参数,依此类推。如果 参数是组合类型,则可以用点号记法(例如 $1.name)访问该参数的属性。这些参数只能用作 数据值,而不能用作标识符。因此,举例来说,这样写是合理的:

INSERT INTO mytable VALUES ($1);

但这样写行不通:

INSERT INTO $1 VALUES (42);

35.4.1. 基本类型上的 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 函数几乎 同样容易。在下面的示例中,注意我们如何在函数中把参数引用为 $1$2

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 (integer, numeric) RETURNS integer AS $$
    UPDATE bank
        SET balance = balance - $2
        WHERE accountno = $1;
    SELECT 1;
$$ LANGUAGE SQL;

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

SELECT tf1(17, 100.0);

实际上我们可能希望从该函数得到比常数 1 更有用的结果,因此一个 更可能的定义是:

CREATE FUNCTION tf1 (integer, numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - $2
        WHERE accountno = $1;
    SELECT balance FROM bank WHERE accountno = $1;
$$ LANGUAGE SQL;

它会调整余额并返回新余额。同样的事情也可以用 RETURNING 在一条命令中完成:

CREATE FUNCTION tf1 (integer, numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - $2
        WHERE accountno = $1
    RETURNING balance;
$$ LANGUAGE SQL;

35.4.2. 组合类型上的 SQL 函数

在编写参数为组合类型的函数时,我们必须不仅指定想要哪个参数 (如上面用 $1$2 那样),还要 指定该参数的所需属性(字段)。例如,假设 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 命令使用 * 将表的当前整行取作一个复合值。该表行也 可以仅用表名来引用:

SELECT name, double_salary(emp) AS dream
    FROM emp
    WHERE emp.cubicle ~= point '(2,1)';

但这种用法已被弃用,因为它很容易让人混淆。

有时候即时构造一个复合参数值会很方便。这可以用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)

第二种方式在 第 35.4.7 节 中有更完整的描述。

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

SELECT (new_emp()).name;

 name
------
 None

这里需要额外的括号,以免解析器产生歧义。如果不加括号,结果会是这样:

SELECT new_emp().name;
ERROR:  syntax error at or near "."
LINE 1: SELECT new_emp().name;
                        ^

另一种选择是使用函数记法来提取属性。简单的解释是:记法 attribute(table)table.attribute 可以互换使用。

SELECT name(new_emp());

 name
------
 None
-- This is the same as:
-- SELECT emp.name AS youngster FROM emp WHERE emp.age < 30;

SELECT name(emp) AS youngster FROM emp WHERE age(emp) < 30;

 youngster
-----------
 Sam
 Andy

提示

函数记法与属性记法之间的等价性,使得可以用组合类型上的函数 来模拟计算字段 For example, using the previous definition for double_salary(emp), we can write

SELECT emp.name, emp.double_salary FROM emp;

使用这种写法的应用不需要直接了解 double_salary 并不是表的真实列。(也可以用视图来 模拟计算字段。)

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

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

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

使用返回复合类型的函数的又一种方式,是把它作为表函数调用, 如 第 35.4.7 节 所述。

35.4.3. 带参数名的 SQL 函数 #

可以为函数的参数附上名字,例如

CREATE FUNCTION tf1 (acct_no integer, debit numeric) RETURNS numeric AS $$
    UPDATE bank
        SET balance = balance - $2
        WHERE accountno = $1
    RETURNING balance;
$$ LANGUAGE SQL;

这里第一个参数被命名为 acct_no,第二个参数被命名 为 debit。就 SQL 函数本身而言,这些名字只是装饰; 在函数体中你仍必须把参数引用为 $1$2 等。(有些过程语言允许改用参数名。)不过,为 参数附上名字对文档目的很有用。当函数有很多参数时,在调用 函数时使用这些名字也很有用,见 第 4.3 节 中的描述。

35.4.4. 带输出参数的 SQL 函数 #

描述函数结果的另一种方式是用输出参数 来定义它,如下例所示:

CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int)
AS 'SELECT $1 + $2'
LANGUAGE SQL;

SELECT add_em(3,7);
 add_em
--------
     10
(1 row)

这与 第 35.4.1 节 中所示的 add_em 版本没有本质区别。输出参数的真正价值在于, 它们为定义返回若干列的函数提供了一种便捷方式。例如,

CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
AS 'SELECT $1 + $2, $1 * $2'
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参数是输入参数,但被按照下文所述特殊对待。

35.4.5. SQL Functions with Variable Numbers of Arguments #

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 来做到这一点:

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

35.4.6. SQL Functions with Default Values for Arguments #

可以为部分或所有输入参数声明带默认值的函数。每当函数被调用时 实参不够多,就会插入默认值。由于实参只能从实参列表的末尾开始 被省略,一个有默认值的参数之后的所有参数也必须都有默认值。 (虽然使用命名实参记法可以让这一限制放松,但它仍被强制执行, 以便位置实参记法能合理工作。)

例如:

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

35.4.7. SQL Functions as Table Sources #

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

35.4.8. SQL Functions Returning Sets #

当一个 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

当前,返回集合的函数也可以在查询的 select 列表中被调用。对于 查询本身生成的每一行,该返回集合的函数都会被调用,并为其结果 集中的每一个元素生成一行输出。但请注意,这一能力已被弃用, 未来的发行版中可能会将其移除。下面是一个从 select 列表返回 集合的函数示例:

CREATE FUNCTION listchildren(text) RETURNS SETOF text AS $$
    SELECT name FROM nodes WHERE parent = $1
$$ LANGUAGE SQL;

SELECT * FROM nodes;
   name    | parent
-----------+--------
 Top       |
 Child1    | Top
 Child2    | Top
 Child3    | Top
 SubChild1 | Child1
 SubChild2 | Child1
(6 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 对这些参数返回空集,因此 不生成任何结果行。

注意

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

35.4.9. SQL Functions Returning 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列表中。

35.4.10. Polymorphic SQL Functions

SQL 函数可以声明为接受和返回多态类型 anyelementanyarrayanynonarrayanyenum。关于多态函数 的更详细解释,见 第 35.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)

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

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(text, VARIADIC anyarray) RETURNS text AS $$
    SELECT array_to_string($2, $1);
$$ LANGUAGE SQL;

SELECT concat('|', 1, 4, 2);
 concat 
--------
 1|4|2
(1 row)

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。