38.5. 查询语言(SQL)函数

SQL 函数执行 SQL 语句的任意列表,并返回列表中最后一个查询的结果。在简单(未设置)的情况下,将返回上一个查询结果的第一行。 (请记住,除非使用ORDER BY,否则多行结果的“第一行”定义不明确.)如果最后一个查询恰好不返回任何行,则将返回空值。

或者,可以通过将函数的返回类型指定为SETOF sometype或等效地通过将其声明为RETURNS TABLE(columns)来声明 SQL 函数返回一个集(即多行)。在这种情况下,将返回上一个查询结果的所有行。详细信息如下。

SQL 函数的主体必须是用分号分隔的 SQL 语句列表。最后一条语句后的分号是可选的。除非声明该函数返回void,否则最后一条语句必须是SELECT或具有RETURNING子句的INSERTUPDATEDELETE

SQL 语言中的任何命令集合都可以打包在一起并定义为一个函数。除了SELECT查询,这些命令还可以包括数据修改查询(INSERTUPDATEDELETE)以及其他 SQL 命令。 (在 SQL 函数中不能使用事务控制命令,例如COMMITSAVEPOINT和某些 Util 命令例如VACUUM.)但是,最终命令必须是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)

Note

SQL 函数的整个主体在执行任何函数之前都会被分析。虽然 SQL 函数可以包含更改系统目录的命令(例如CREATE TABLE),但是在对函数中的后续命令进行语法分析时,这些命令的效果将不可见。因此,例如,如果将CREATE TABLE foo (...); INSERT INTO foo VALUES(...);打包到单个 SQL 函数中,它将无法按预期工作,因为在解析INSERT命令时foo尚不存在。在这种情况下,建议使用 PL/pgSQL 代替 SQL 函数。

CREATE FUNCTION命令的语法要求将函数主体编写为字符串常量。通常,对字符串常量使用美元引号(请参见Section 4.1.2.4)最方便。如果选择使用常规的单引号字符串常量语法,则必须在函数主体中将单引号(')和反斜杠(\)(假定转义字符串语法)加倍(请参见Section 4.1.2.1)。

38 .5.1. SQL 函数的参数

可以使用名称或数字在函数主体中引用 SQL 函数的参数。这两种方法的示例如下所示。

要使用名称,请将函数参数声明为具有名称,然后只需将该名称写在函数主体中即可。如果参数名称与函数中当前 SQL 命令中的任何列名称相同,则列名称优先。要覆盖此参数,请使用函数本身的名称(即function_name.argument_name)限定参数名称。 (如果这将与限定的列名冲突,则列名再次获胜.您可以通过在 SQL 命令中为表选择其他别名来避免歧义.)

在较旧的数字方法中,使用语法$n引用参数:$1引用第一个 Importing 参数,$2引用第二个 Importing 参数,依此类推。无论是否使用名称声明了特定参数,这都将起作用。

如果参数是复合类型,则点符号(例如argname.fieldname$1.fieldname)可用于访问参数的属性。同样,您可能需要用函数名来限定参数名,以使带有参数名的表格变得清晰。

SQL 函数参数只能用作数据值,不能用作标识符。因此,例如,这是合理的:

INSERT INTO mytable VALUES ($1);

但这不起作用:

INSERT INTO $1 VALUES (42);

Note

PostgreSQL 9.2 中增加了使用名称来引用 SQL 函数参数的功能。在较旧的服务器中使用的功能必须使用$n表示法。

38 .5.2. 基本类型的 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 函数几乎一样容易:

CREATE FUNCTION add_em(x integer, y integer) RETURNS integer AS $$
    SELECT x + y;
$$ LANGUAGE SQL;

SELECT add_em(1, 2) AS answer;

 answer
--------
      3

另外,我们可以省去参数的名称并使用数字:

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

用户可以按以下方式执行此功能以从帐户 17 中扣除$ 100.00:

SELECT tf1(17, 100.0);

在此示例中,我们为第一个参数选择了名称accountno,但这与bank表中的列的名称相同。在UPDATE命令中,accountno引用列bank.accountno,因此必须使用tf1.accountno来引用自变量。我们当然可以通过为参数使用其他名称来避免这种情况。

在实践中,人们可能希望函数比常量 1 更有用的结果,因此更可能的定义是:

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

调整余额并返回新余额。使用RETURNING可以在一个命令中完成同一件事:

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

SQL 函数必须完全返回其声明的结果类型。这可能需要插入显式转换。例如,假设我们希望以前的add_em函数返回类型float8。这行不通:

CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
    SELECT $1 + $2;
$$ LANGUAGE SQL;

即使在其他情况下 PostgreSQL 也愿意插入一个隐式转换将integer转换为float8。我们需要写成

CREATE FUNCTION add_em(integer, integer) RETURNS float8 AS $$
    SELECT ($1 + $2)::float8;
$$ LANGUAGE SQL;

38 .5.3. 复合类型上的 SQL 函数

当使用复合类型的参数编写函数时,我们不仅必须指定所需的参数,还必须指定该参数的所需属性(字段)。例如,假设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命令如何使用* table_name * .*选择表的整个当前行作为复合值。可以仅使用表名来引用表行,如下所示:

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

但是不建议使用此方法,因为很容易混淆。 (有关表行的组合值的这两种表示法的详细信息,请参见Section 8.16.5。)

有时,即时构造复合参数值很方便。这可以使用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;

在此示例中,我们为每个属性指定了一个常量值,但是任何计算都可以代替这些常量。

请注意有关定义函数的两个重要事项:

  • 查询中的选择列表 Sequences 必须与列在与组合类型相关联的表中出现的 Sequences 完全相同。 (正如我们上面所做的那样,命名列与系统无关.)

  • 我们必须确保每个表达式的类型都与复合类型的相应列匹配,并在必要时插入强制类型转换。否则,我们将收到如下错误:

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)

Section 38.5.7中对第二种方法进行了更全面的描述。

使用返回复合类型的函数时,可能只需要其结果中的一个字段(属性)。您可以使用以下语法来做到这一点:

SELECT (new_emp()).name;

 name
------
 None

需要额外的括号以防止解析器变得混乱。如果您在没有他们的情况下尝试这样做,则会得到以下信息:

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

另一种选择是使用功能符号来提取属性:

SELECT name(new_emp());

 name
------
 None

Section 8.16.5中所述,字段符号和功能符号是等效的。

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

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

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

38 .5.4. 带输出参数的 SQL 函数

描述函数结果的另一种方法是使用* output parameters *定义它,如本例所示:

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

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

这与Section 38.5.2中显示的add_em版本没有本质上的区别。输出参数的 true 价值在于,它们提供了一种方便的方法来定义返回几列的函数。例如,

CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
AS 'SELECT x + y, x * y'
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 仅考虑 Importing 参数来定义函数的调用签名。这也意味着,在出于删除等目的引用函数时,仅 Importing 参数很重要。我们可以使用以下任一方法删除上述功能

DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int);
DROP FUNCTION sum_n_product (int, int);

参数可以标记为IN(默认值),OUTINOUTVARIADICINOUT参数既用作 Importing 参数(属于调用参数列表的一部分),又用作输出参数(属于结果记录类型的一部分)。 VARIADIC参数是 Importing 参数,但将按以下说明进行特殊处理。

38 .5.5. 参数数目可变的 SQL 函数

可以声明 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的参数匹配其元素类型而不是其自身类型的一个或多个出现。

有时,将已经构造的数组传递给可变函数很有用;当一个可变参数函数希望将其数组参数传递给另一个函数时,这特别方便。同样,这是调用模式中发现的可变参数函数的唯一安全方法,该模式允许不受信任的用户创建对象。参见Section 10.3。您可以通过在呼叫中指定VARIADIC来做到这一点:

SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]);

这样可以防止将函数的可变参数扩展为其元素类型,从而使数组参数值正常匹配。 VARIADIC只能附加到函数调用的最后一个实际参数上。

在调用中指定VARIADIC也是将空数组传递给可变参数函数的唯一方法,例如:

SELECT mleast(VARIADIC ARRAY[]::numeric[]);

简单地写SELECT mleast()是行不通的,因为可变参数必须至少匹配一个实际参数。 (如果要允许这样的调用,则可以定义第二个也名为mleast的函数,不带参数)。

从可变参数生成的数组元素参数被视为没有自己的名称。这意味着除非指定了VARIADIC,否则无法使用命名参数(Section 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]);

38 .5.6. 具有参数默认值的 SQL 函数

可以使用某些或所有 Importing 参数的默认值声明函数。只要调用的函数的实际参数不足,就会插入默认值。由于只能从实际参数列表的末尾省略参数,因此具有默认值的参数之后的所有参数也必须具有默认值。 (尽管使用命名参数表示法可以放宽此限制,但仍会强制执行此设置,以使位置参数表示法合理地起作用.)无论您是否使用它,此功能在某些用户调用数据库中的函数时都需要采取预防措施不信任其他用户;参见Section 10.3

For example:

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

38 .5.7. SQL 函数作为表源

所有 SQL 函数都可以在查询的FROM子句中使用,但是对于返回复合类型的函数特别有用。如果将函数定义为返回基本类型,则 table 函数将生成一个单列表。如果将函数定义为返回复合类型,则表函数将为复合类型的每个属性生成一列。

这是一个例子:

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。下一节将对此进行描述。

38 .5.8. SQL 函数返回集

当 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

通过多次调用 set-returning 函数构造查询结果通常很有用,每次调用的参数都来自表或子查询的连续行。首选的方法是使用Section 7.2.1.5中描述的LATERAL关键字。这是一个使用 set-returning 函数枚举树结构元素的示例:

SELECT * FROM nodes;
   name    | parent
-----------+--------
 Top       |
 Child1    | Top
 Child2    | Top
 Child3    | Top
 SubChild1 | Child1
 SubChild2 | Child1
(6 rows)

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

SELECT * FROM listchildren('Top');
 listchildren
--------------
 Child1
 Child2
 Child3
(3 rows)

SELECT name, child FROM nodes, LATERAL listchildren(name) AS child;
  name  |   child
--------+-----------
 Top    | Child1
 Top    | Child2
 Top    | Child3
 Child1 | SubChild1
 Child1 | SubChild2
(5 rows)

这个例子并没有做我们用简单的连接所无法完成的任何事情,但是在更复杂的计算中,将某些工作放到一个函数中的选择可能非常方便。

返回集的函数也可以在查询的选择列表中调用。对于查询本身生成的每一行,都将调用 set-returning 函数,并为函数结果集中的每个元素生成一个输出行。前面的示例也可以通过以下查询完成:

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返回了这些参数的空集,所以没有生成结果行。这与使用LATERAL语法时从内部联接到函数结果的行为相同。

PostgreSQL 对查询的选择列表中的返回集合函数的行为几乎完全等同于将返回集合函数写在LATERAL FROM子句中。例如,

SELECT x, generate_series(1,5) AS g FROM tab;

几乎等于

SELECT x, g FROM tab, LATERAL generate_series(1,5) AS g;

完全相同,只是在此特定示例中,计划者可以选择将g放在 nestloop 联接的外部,因为gtab没有实际的横向依赖性。这将导致不同的输出行 Sequences。选择列表中的返回集合的函数始终会像在嵌套循环内部一样与FROM子句的其余部分一起进行求值,以便在FROM子句的下一行成为考虑过的。

如果查询的选择列表中有多个返回集合的函数,则其行为类似于将函数放入单个LATERAL ROWS FROM( ... ) FROM子句中所得到的结果。对于基础查询的每一行,都有一个使用每个函数的第一个结果的输出行,然后是使用第二个结果的输出行,依此类推。如果某些集合返回函数产生的输出少于其他函数,则将空值替换为丢失的数据,以便为一个基础行发出的总行数与产生最多输出的集合返回函数的行数相同。因此,返回设置的函数“按步进行”,直到它们全部用尽,然后从下一个下一行 continue 执行。

返回设置的函数可以嵌套在选择列表中,尽管FROM子句项中不允许这样做。在这种情况下,嵌套的每个级别都被单独对待,就好像它是单独的LATERAL ROWS FROM( ... )项一样。例如,在

SELECT srf1(srf2(x), srf3(y)), srf4(srf5(z)) FROM tab;

对于tab的每一行,将以锁步方式运行返回集合的函数srf2srf3srf5,然后将srf1srf4以锁步方式应用于由较低函数产生的每一行。

返回集函数不能在条件评估构造中使用,例如CASECOALESCE。例如,考虑

SELECT x, CASE WHEN x > 0 THEN generate_series(1, 5) ELSE 0 END FROM tab;

看起来这应该产生五个重复的具有x > 0的 Importing 行,以及一个不具有_的 Importing 行的重复。但实际上,因为在评估CASE表达式之前,将在隐式LATERAL FROM项中运行generate_series(1, 5),所以它将对每个 Importing 行产生 5 次重复。为了减少混乱,此类情况会产生解析时错误。

Note

如果函数的最后一个命令是INSERTUPDATEDELETERETURNING,则即使该函数未使用SETOF声明或调用查询未获取所有结果行,该命令也将始终执行完毕。 RETURNING子句产生的任何多余的行都将被静默删除,但是命令表修改仍会发生(并且在从函数返回之前全部完成)。

Note

在 PostgreSQL 10 之前,除非将总是返回相同数量的行,否则在同一个选择列表中放置多个返回集合的函数的行为就不是很明智。否则,您得到的输出行数等于 set-returning 函数产生的行数的最小公倍数。同样,嵌套的集合返回函数不能如上所述工作。相反,一个返回集合的函数最多可以有一个返回集合的参数,并且每个返回集合的嵌套都独立运行。另外,以前允许条件执行(CASE等内部的集合返回函数),这使事情更加复杂。在编写需要在旧 PostgreSQL 版本中运行的查询时,建议使用LATERAL语法,因为这将在不同版本之间提供一致的结果。如果查询依赖于有条件执行集合返回函数,则可以通过将条件测试移到自定义的有集合返回函数中来对其进行修复。例如,

SELECT x, CASE WHEN y > 0 THEN generate_series(1, z) ELSE 5 END FROM tab;

could become

CREATE FUNCTION case_generate_series(cond bool, start int, fin int, els int)
RETURNS SETOF int AS $$
BEGIN
IF cond THEN
RETURN QUERY SELECT generate_series(start, fin);
ELSE
RETURN QUERY SELECT els;
END IF;
END$$ LANGUAGE plpgsql;

SELECT x, case_generate_series(y > 0, 1, z, 5) FROM tab;

此公式在所有版本的 PostgreSQL 中都将相同。

38 .5.9. SQL 函数返回表

还有一种将函数声明为返回集合的方法,即使用语法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;

不允许使用带有RETURNS TABLE表示法的显式OUTINOUT参数-您必须将所有输出列都放在TABLE列表中。

38 .5.10. 多态 SQL 函数

可以声明 SQL 函数以接受并返回多态类型anyelementanyarrayanynonarrayanyenumanyrange。有关多态函数的更详细说明,请参见Section 38.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。如果参数只是字符串 Literals,则必须这样做,因为否则它将被视为类型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_values(text, VARIADIC anyarray) RETURNS text AS $$
    SELECT array_to_string($2, $1);
$$ LANGUAGE SQL;

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

38 .5.11. 带排序规则的 SQL 函数

当 SQL 函数具有一个或多个可排序数据类型的参数时,将根据分配给实际参数的排序规则为每个函数调用标识一个排序规则,如Section 23.2中所述。如果成功识别了排序规则(即,参数之间没有隐式排序规则的冲突),则所有可排序参数都被视为隐式具有该排序规则。这将影响函数中对排序规则敏感的操作的行为。例如,使用上述anyleast函数,

SELECT anyleast('abc'::text, 'ABC');

将取决于数据库的默认排序规则。在C语言环境中,结果将是ABC,但在其他许多语言环境中,结果将是abc。可以通过在所有参数中添加COLLATE子句来强制使用排序规则

SELECT anyleast('abc'::text, 'ABC' COLLATE "C");

另外,如果您希望函数使用特定的排序规则进行操作而不管其调用什么内容,请在函数定义中根据需要插入COLLATE子句。此版本的anyleast将始终使用en_US语言环境来比较字符串:

CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
    SELECT min($1[i] COLLATE "en_US") FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;

但是请注意,如果将其应用于不可整理的数据类型,则将引发错误。

如果无法在实际参数之间识别出通用的排序规则,则 SQL 函数会将其参数视为具有其数据类型的默认排序规则(通常是数据库的默认排序规则,但对于域类型的参数可能会有所不同)。

可将可排序参数的行为视为多态性的一种受限形式,仅适用于文本数据类型。