第三十八章. 扩展的 SQL: 函数

内容
查询语言 (SQL) 函数
过程语言函数
内部函数
编译 (C)语言函数
函数重载
正如我们想象的那样,定义新类型的一部分工作是定义描述(该类型)特征的函数.因此,我们可能只定义一个新函数而不定义新类型,反过来却不行.所以,我们先描述如何给 Postgres 增加新函数,然后再描述如何增加新类型.

PostgresSQL 提供三种类型的函数:

每种函数都可以以一个基本类型或一个复合类型或是两者的某种组合作为参数.另外,每种函数都可以返回一个基本类型或一个复合类型值.定义 SQL 函数更容易些,所以我们将从这里开始.本章的例子还可以在 funcs.sqlfuncs.c里找到.

查询语言(SQL)函数

SQL 函数执行一个任意 SQL 查询的列表,返回列表里最后一个查询的结果。SQL 函数通常返回集。如果它们的返回类型没有声明为 setof,那么将返回最后一条查询结果的任意元素。

跟在 AS 后面的 SQL 函数体应该是一个用空白字符分隔和用引号括起来的查询列表。要注意在查询里面的引号必须用前面带两个反斜杠的方法转意。

SQL 函数的参数在查询里可以用 $n 语法引用:$1 指第一个参数,$2 指第二个参数,以此类推。如果参数是复合类型,那么可以用表示法(例如。"$1.emp")访问参数里的字段或者激活函数。

例子

看看下面这个简单的 SQL 函数的例子,它将用于对一个银行帐号做扣款(借记消费 debit)动作:
create function TP1 (int4, float8) returns int4
    as 'update BANK set balance = BANK.balance - $2
        where BANK.acctountno = $1
        select(x = 1)'
    language 'sql';
一个用户可以象下面这样用这个函数给帐户 17 扣款 $100.00:
select (x = TP1( 17,100.0));
下面的更有意思的例子接受一个类型为 EMP 的参数,并且检索多个结果:
 
select function hobbies (EMP) returns set of HOBBIES(译注:开头的 select 应为 create)
    as 'select (HOBBIES.all) from HOBBIES
        where $1.name = HOBBIES.person'
    language 'sql';

基本类型的 SQL 函数

最简单的 SQL 函数可能是不带参数,只是返回一个基本类型如 int4 的函数:
 
CREATE FUNCTION one() RETURNS int4
    AS 'SELECT 1 as RESULT' LANGUAGE 'sql';

SELECT one() AS answer;

     +-------+
     |answer |
     +-------+
     |1      |
     +-------+
注意我们给函数定义了目标列(名称为 RESULT),但是激活函数的查询语句的目标列覆盖了函数的目标列. 因此,结果的标记是 answer 而不是 one 的.

定义以基本类型为参数的 SQL 函数几乎一样简单,注意我们在函数内如何用$1和$2使用参数:

CREATE FUNCTION add_em(int4, int4) RETURNS int4
    AS 'SELECT $1 + $2;' LANGUAGE 'sql';

SELECT add_em(1, 2) AS answer;

     +-------+
     |answer |
     +-------+
     |3      |
     +-------+

复合类型的 SQL 函数

当我们声明的函数用复合类型(如 EMP)做参数时,我们不仅要声明我们需要哪个参数(像上面我们使用$1和$2一样),而且要声明参数的字段.比如,把 double_salary 做为计算你薪水翻番之后的数值的函数:
CREATE FUNCTION double_salary(EMP) RETURNS int4
    AS 'SELECT $1.salary * 2 AS salary;' LANGUAGE 'sql';

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

     +-----+-------+
     |name | dream |
     +-----+-------+
     |Sam  | 2400  |
     +-----+-------+
注意这里 $1.salary 语法的使用.在开始说明返回复合类型的函数之前,我们必须先介绍用于映射字段的函数表示法.我们可以用一个简单的方法解释这些:我们可以互换地使用 attribute(class) 和 class.attribute 两种表达方式:
--
-- 这是与下面语句一样的:
--  SELECT EMP.name AS youngster FROM EMP WHERE EMP.age < 30
--
SELECT name(EMP) AS youngster
    FROM EMP
    WHERE age(EMP) < 30;

     +----------+
     |youngster |
     +----------+
     |Sam       |
     +----------+
不过,我们呆会儿就会看到不总是这种情况.这种函数表示法在我们希望使用的函数返回单条记录时是很重要的.此时我们在函数里面一个字段一个字段地组装整条记录.下面是返回单条 EMP 记录的函数例子:
CREATE FUNCTION new_emp() RETURNS EMP
    AS 'SELECT \'None\'::text AS name,
        1000 AS salary,
        25 AS age,
        \'(2,2)\'::point AS cubicle'
    LANGUAGE 'sql';
在这个例子中我们把每个字段赋予了一个常量,当然我们可以用任何计算或表达式来代替这些常量.定义这样的函数可能需要一点点技巧.下面是一些比较重要的注意事项:
    目标列表的顺序必须和你用 CREATE TABLE (或者你执行的 .* 查询里的)语句创建时的字段顺序一样.

    你必须很仔细的进行类型转换 (使用 ::).否则你将看到下面的错误信息:

             WARN::function declared to return type EMP does not retrieve (EMP.*)
    当我们调用了一个返回整条记录的函数时,我们不能检索整条记录.我们要么是从该记录中映射出一个字段,要么是把整条记录传递给另外一个函数.
    SELECT name(new_emp()) AS nobody;
    
            +-------+
            |nobody |
            +-------+
            |None   |
            +-------+
    通常我们必须把函数的返回值映射到字段里,使用这种函数语法是因为分析器在分析函数调用时,无法正确分析与函数相联的'点'的语法.
    SELECT new_emp().name AS nobody;
    WARN:parser: syntax error at or near "."
在 SQL 查询语言里的任何命令的集合都可以打包在一起,定义为函数.这些命令可以包含更新动作(例如,INSERTUPDATE,和 DELETE),就象可以使用 SELECT 查询一样.不过,最后的命令必须是一条返回类型与所声明的函数返回类型的一致的 SELECT 语句。
CREATE FUNCTION clean_EMP () RETURNS int4
    AS 'DELETE FROM EMP WHERE EMP.salary <= 0;
SELECT 1 AS ignore_this'
    LANGUAGE 'sql';

SELECT clean_EMP();

     +--+
     |x |
     +--+
     |1 |
     +--+