12.2. 查询语言(SQL)函数 #
SQL
函数执行一个任意的
SQL
语句列表,返回列表中最后一个查询的结果,该查询必须是一个
SELECT。在简单(非集合)的情况下,将返回最后一个查询结果的第一行。(请记住,除非使用
ORDER BY,多行结果的“第一行”并没有良好定义。)如果最后一个查询碰巧不返回任何行,则返回
NULL。
或者,也可以把
SQL
函数声明为返回集合,方法是把函数的返回类型指定为
SETOF sometype。此时将返回最后一个查询结果的所有行。更多细节见下文。
SQL
函数体应是一个或多个用分号分隔的
SQL
语句的列表。注意,由于
CREATE FUNCTION
命令的语法要求函数体用单引号括起,因此函数体中使用的单引号(')必须转义:在需要引号的地方写两个单引号('')或一个反斜杠(\')。
SQL
函数的参数可以在函数体中使用语法
$
引用:$1
指第一个参数,$2
指第二个参数,依此类推。如果参数是复合类型,则可以使用“点记法”(例如
n$1.emp)访问该参数的属性。
12.2.1. 示例
为说明一个简单的 SQL 函数,考虑下面这个可以用来给银行账户扣款的例子:
CREATE FUNCTION tp1 (integer, numeric) RETURNS integer AS '
UPDATE bank
SET balance = balance - $2
WHERE accountno = $1;
SELECT 1;
' LANGUAGE SQL;用户可以执行如下命令给账户 17 扣款 $100.00:
SELECT tp1(17, 100.0);
实践中人们可能希望函数返回比常量 “1” 更有用的结果,所以更可能的定义是
CREATE FUNCTION tp1 (integer, numeric) RETURNS numeric AS '
UPDATE bank
SET balance = balance - $2
WHERE accountno = $1;
SELECT balance FROM bank WHERE accountno = $1;
' LANGUAGE SQL;它调整余额并返回新余额。
SQL
语言中任何命令的集合都可以打包到一起并定义为函数。这些命令既可以是数据修改(即
INSERT、UPDATE
和
DELETE),也可以是
SELECT
查询。但最后一条命令必须是一个
SELECT,它返回函数返回类型所指定的内容。
CREATE FUNCTION clean_EMP () RETURNS integer AS '
DELETE FROM EMP
WHERE EMP.salary <= 0;
SELECT 1 AS ignore_this;
' LANGUAGE SQL;
SELECT clean_EMP();
x --- 1
12.2.2. 基本类型上的 SQL 函数
最简单的 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
12.2.3. 复合类型上的 SQL 函数
指定带复合类型参数的函数时,我们不仅要指明想要哪个参数(如上面用
$1 和
$2),还要指明该参数的属性。例如,设
EMP
是一个包含员工数据的表,因而也是该表每行的复合类型的名称。下面的函数
double_salary
计算你的薪资翻倍后的值:
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
命令如何用表名把该表的整个当前行表示为一个复合值。
也可以构建返回复合类型的函数。(不过,如下文所见,对这种函数的使用存在一些不幸的限制。)下面是一个返回单个 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
在 PostgreSQL 的当前版本中,对返回复合类型的函数的使用存在一些不便的限制。简而言之,调用一个返回行的函数时,我们无法检索整个行。我们必须要么从行中投影出单个属性,要么把整行传递给另一个函数。(试图显示整个行值只会得到一个无意义的数字。)例如,
SELECT name(new_emp());
name ------ None
这个示例使用了投影属性的函数记法。简单的解释是:attribute(table)
和 table.attribute
这两种记法通常可以互换使用:
--
-- 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
一般而言,我们必须使用函数语法投影函数返回值的属性,原因就是解析器还不理解与函数调用组合使用点记法做投影。
SELECT new_emp().name AS nobody; ERROR: parser: parse error at or near "."
使用返回行的函数结果的另一种方式是声明一个接受行类型参数的第二个函数,并把函数的结果传给它:
CREATE FUNCTION getname(emp) RETURNS text AS 'SELECT $1.name;' LANGUAGE SQL;
SELECT getname(new_emp()); getname --------- None (1 row)
12.2.4. 返回集合的 SQL 函数
如前所述,SQL
函数可以声明为返回
SETOF 。此时该函数的最后一个
sometypeSELECT
查询会被执行完,并且它输出的每一行都会作为结果集的一个元素返回。
返回集合的函数只能在
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
对这些输入返回空集,因此不会生成输出行。