pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
SQL 函数执行由任意 SQL 语句组成的一个列表,并返回列表中最后一个查询的结果。在简单(非集合)情况下,将返回最后一个查询结果的第一行。(请记住,除非使用 ORDER BY,否则多行结果中的“第一行”并没有明确定义。)如果最后一个查询完全没有返回任何行,则返回空值。
或者,一个 SQL 函数可以通过指定函数的返回类型为SETOF 被声明为返回一个集合。在这种 情况下,最后一个查询的结果的所有行会被返回。下文将给出进一步 的细节。sometype
SQL 函数体应是一个或多个用分号分隔的 SQL 语句的列表。注意,由于 CREATE FUNCTION 命令的语法要求函数体用单引号括起,因此函数体中使用的单引号(')必须转义:在需要引号的地方写两个单引号('')或一个反斜杠(\')。
SQL 函数的参数可以在函数体中用语法 $ 引用:n$1 指第一个参数,$2 指第二个参数,依此类推。如果参数是复合类型,则可以使用点记法(例如 $1.name)访问 参数的属性。
最简单的 SQL 函数没有参数,只是返回一个基本类型,例如 integer:
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);
实践中人们可能希望函数返回比常量更有用的结果,所以更可能的定义是
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;
它调整余额并返回新余额。
SQL 语言中任何命令的集合都可以打包到一起并定义为函数。 除 SELECT 查询外,这些命令还可以包括数据修改(即 INSERT、UPDATE 和 DELETE)。但最后一条命令必须是一个 SELECT,它返回函数返回类型所指定的内容。或者,如果你想定义一个执行操作但没有有用值可返回的 SQL 函数,可以把它定义为返回 void。此时函数体不能以 SELECT 结尾。例如:
CREATE FUNCTION clean_emp() RETURNS void AS '
DELETE FROM emp
WHERE salary <= 0;
' LANGUAGE SQL;
SELECT clean_emp();
clean_emp
-----------
(1 row)
指定带复合类型参数的函数时,我们不仅要指明想要哪个参数(如上面用 $1 和 $2),还要指明该参数的属性。例如,设 emp 是一个包含员工数据的表,因而也是该表每行的复合类型的名称。下面的函数 double_salary 计算某人薪资翻倍后的值:
CREATE TABLE emp (
name text,
salary integer,
age integer,
cubicle point
);
CREATE FUNCTION double_salary(emp) RETURNS integer 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
------+-------
Sam | 2400
注意使用语法 $1.salary 选择参数行值的一个字段。还要注意调用它的 SELECT 命令如何用表名把该表的整个当前行表示为一个复合值。表行也可以这样引用:
SELECT name, double_salary(emp.*) AS dream
FROM emp
WHERE emp.cubicle ~= point '(2,1)';
这强调了它的行本质。
也可以构建返回复合类型的函数。下面是一个返回单个 emp 行的函数示例:
CREATE FUNCTION new_emp() RETURNS emp AS '
SELECT text ''None'' AS name,
1000 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
返回行(复合类型)的函数可以用作表函数,如下文所述。它也可以在 SQL 表达式的上下文中调用,但仅当从行中提取单个属性,或把整行传递给另一个接受相同复合类型的函数时。
这是从行类型中提取一个属性的示例:
SELECT (new_emp()).name; name ------ None
我们需要额外的圆括号来避免解析器混淆:
SELECT new_emp().name; ERROR: syntax error at or near "." at character 17
另一种选择是使用函数记法提取属性。简单的解释是: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
使用返回行的函数结果的另一种方式是声明一个接受行类型参数的第二个函数,并把第一个函数的结果传给它:
CREATE FUNCTION getname(emp) RETURNS text AS '
SELECT $1.name;
' LANGUAGE SQL;
SELECT getname(new_emp());
getname
---------
None
(1 row)
所有的 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
(2 rows)
如示例所示,我们可以像使用普通表的列一样使用函数结果的各列。
注意我们只从函数得到了一行。这是因为我们没有使用 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)
目前,返回集合的函数也可以在查询的选择列表中调用。对查询自身生成的每一行,都会调用一次返回集合的函数,并为函数结果集的每个元素生成一个输出行。但要注意,此能力已被弃用,在未来的版本中可能被移除。下面是一个从选择列表返回集合的函数示例:
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。关于多态函数更详细的解释见 第 33.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.
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。