pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
SQL 函数执行由任意 SQL 语句组成的一个列表,并返回列表中最后一个查询的结果。在简单(非集合)情况下,将返回最后一个查询结果的第一行。(请记住,除非使用 ORDER BY,否则多行结果中的“第一行”并没有明确定义。)如果最后一个查询完全没有返回任何行,则返回空值。
或者,一个 SQL 函数可以通过指定函数的返回类型为SETOF 被声明为返回一个集合。在这种 情况下,最后一个查询的结果的所有行会被返回。下文将给出进一步 的细节。sometype
SQL 函数的函数体必须是一个由分号分隔的 SQL 语句列表。最后一条语句后的分号是可选的。除非该函数被声明为返回 void,否则最后一条语句必须是 SELECT。
SQL 语言中的任何命令集合都可以打包在一起并 定义为函数。除了 SELECT 查询之外,这些命令 还可以包括数据修改查询(INSERT、 UPDATE 和 DELETE),以及 其他 SQL 命令。(唯一的例外是不能把 BEGIN、 COMMIT、ROLLBACK 或 SAVEPOINT 命令放进 SQL 函数。) 不过,最后一条命令必须是 SELECT,并且返回与 函数声明返回类型 相符的结果。或者,如果你想定义一个执行动作但没有有用返回值的 SQL 函数,也可以把它定义为返回 void。在这种情况下, 函数体不能以 SELECT 结尾。例如,下面这个 函数会删除 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.2 节)。如果你选择使用常规的单引号字符串常量语法,那么必须对函数体中使用的单引号(')和反斜线(\)进行转义,通常是把它们写成双份(见第 4.1.2.1 节)。
SQL 函数的参数在函数体中使用语法 $ 引用:n$1 引用第一个参数,$2 引用第二个参数,依此类推。如果 参数是组合类型,则可以用点号记法(例如 $1.name)访问该参数的属性。这些参数只能用作 数据值,而不能用作标识符。因此,举例来说,这样写是合理的:
INSERT INTO mytable VALUES ($1);
但这样写行不通:
INSERT INTO $1 VALUES (42);
最简单的 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;
它会调整余额并返回新余额。
在编写参数为组合类型的函数时,我们必须不仅指定想要哪个参数 (如上面用 $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)
第二种方式在 第 32.4.4 节 中有更完整的描述。
当你使用返回复合类型的函数时,可能只需要取其结果中的一个字段(属性)。可以使用下面这样的语法:
SELECT (new_emp()).name; name ------ None
这里需要额外的括号,以免解析器产生歧义。如果不加括号,结果会是这样:
SELECT new_emp().name;
ERROR: syntax error at or near "." at character 17
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
函数记法与属性记法之间的等价性,使得可以用组合类型上的函数 来模拟“计算字段”。 例如,利用前面定义的 double_salary(emp),我们可以写
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)
使用返回复合类型的函数的又一种方式,是把它作为表函数调用, 如 第 32.4.4 节 所述。
描述函数结果的另一种方式是用输出参数 来定义它,如下例所示:
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)
这与 第 32.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(默认)、OUT或INOUT。INOUT参数既充当输入参数(调用参数列表的一部分),又充当输出参数(结果记录类型的一部分)。
所有的 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。 这会在下一节中介绍。
当一个 SQL 函数被声明为返回SETOF 时,该函数的 最后一个 sometypeSELECT 查询会被执行完,并且它输出的每一行都会被 作为结果集的一个元素返回。
这一特性通常是在查询的 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)
当前,返回集合的函数也可以在查询的 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 中,注意 Child2、Child3 等没有对应的输出行。这是 因为 listchildren 对这些参数返回空集,因此 不生成任何结果行。
SQL 函数可以声明为接受和返回多态类型 anyelement 和 anyarray。关于多态函数 的更详细解释,见 第 32.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 "anyarray"/"anyelement" 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 "anyarray" or "anyelement" must have at least one argument of either type.
多态也可以和带输出参数的函数一起使用。例如:
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)
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。