2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

36.5. 查詢語言 (SQL) 函式 #

SQL 函式執行任意數量的 SQL 語句,並返回列表中最後一個查詢的結果。在簡單(非集合)的情況下,將返回最後一個查詢結果的第一行。(請注意,除非使用 ORDER BY,否則多行結果的“第一行”並不明確定義)。如果最後一個查詢恰好沒有返回任何行,則將返回 NULL 值。

或者,可以透過將函式的返回型別指定為 SETOF sometype,或透過將其宣告為 RETURNS TABLE(columns) 來宣告 SQL 函式返回集合(即多行)。在這種情況下,將返回最後一個查詢結果的所有行。下面將提供更多詳細資訊。

SQL 函式的主體必須是由分號分隔的 SQL 語句列表。最後一個語句後的分號是可選的。除非函式被宣告為返回 void,否則最後一個語句必須是 SELECT,或帶有 RETURNING 子句的 INSERTUPDATEDELETEMERGE

可以打包任何一系列命令SQL語言並將其定義為函式。除了 SELECT 查詢之外,命令還可以包括資料修改查詢(INSERTUPDATEDELETEMERGE)以及其他 SQL 命令。(您不能在SQL函式中使用事務控制命令,例如 COMMITSAVEPOINT,以及一些實用命令,例如 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)

您也可以將其寫為儲存過程,從而避免返回型別問題。例如

CREATE PROCEDURE clean_emp() AS '
    DELETE FROM emp
        WHERE salary < 0;
' LANGUAGE SQL;

CALL clean_emp();

在簡單的示例中,返回 void 的函式與儲存過程之間的區別主要在於風格。但是,儲存過程提供了函式中不可用的其他功能,例如事務控制。此外,儲存過程是 SQL 標準,而返回 void 是 PostgreSQL 的擴充套件。

CREATE FUNCTION 命令的語法要求函式體必須寫成字串常量。通常最方便的方法是使用美元引用(請參閱 第 4.1.2.4 節)作為字串常量。如果您選擇使用常規的單引號字串常量語法,則必須在函式體中將單引號(')和反斜槓(\)(假設使用的是跳脫字元串語法)加倍(請參閱 第 4.1.2.1 節)。

36.5.1. 引數SQL函式 #

可以使用名稱或編號在函式體中引用 SQL 函式的引數。下面提供了這兩種方法的示例。

要使用名稱,請將函式引數宣告為具有名稱,然後僅在函式體中寫該名稱。如果引數名稱與函式中的當前 SQL 命令中的任何列名稱相同,則列名稱將優先。要覆蓋此行為,請使用函式本身的名稱限定引數名稱,即 function_name.argument_name。(如果這與限定的列名稱衝突,則列名稱仍然優先。您可以透過為 SQL 命令中的表選擇不同的別名來避免歧義。)

在舊的數字方法中,使用語法 $n 引用引數:$1 指第一個輸入引數,$2 指第二個,依此類推。這將在特定引數是否已命名的情況下工作。

如果引數是複合型別,則可以使用點表示法,例如 argname.fieldname$1.fieldname 來訪問引數的屬性。同樣,您可能需要用函式名稱限定引數名稱,以使帶引數名稱的形式不產生歧義。

SQL 函式引數只能用作資料值,不能用作識別符號。因此,例如這是合理的

INSERT INTO mytable VALUES ($1);

但這將不起作用

INSERT INTO $1 VALUES (42);

注意

PostgreSQL 9.2 中添加了使用名稱引用 SQL 函式引數的功能。要用於舊伺服器的函式必須使用 $n 符號。

36.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;

使用者可以執行此函式,以 100.00 美元的價格借記賬戶 17,如下所示

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函式中的最後一個 SELECTRETURNING 子句沒有返回與函式宣告的結果型別完全相同的值,PostgreSQL 將自動將該值轉換為所需型別(如果可以透過隱式或賦值轉換完成)。否則,您必須編寫顯式轉換。例如,假設我們希望前面的 add_em 函式返回型別 float8 而不是。寫

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

就足夠了,因為 integer 總和可以隱式轉換為 float8。(有關轉換的更多資訊,請參閱 第 10 章CREATE CAST)。

36.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)';

但這是一種不推薦的使用方式,因為它很容易混淆。(有關錶行複合值的這兩種表示法的詳細資訊,請參閱 第 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;

在此示例中,我們為每個屬性指定了一個常量值,但任何計算都可以替換這些常量。

請注意定義函式時的兩個重要事項

  • 查詢中的 SELECT 列表順序必須與列在複合型別中出現的順序完全相同。(如上所示,為列命名對系統無關緊要。)

  • 我們必須確保每個表示式的型別都可以轉換為複合型別的相應列的型別。否則,我們將收到類似以下的錯誤

    
    ERROR:  return type mismatch in function declared to return emp
    DETAIL:  Final statement returns text instead of point at column 4.
    
    

    與基本型別情況一樣,系統不會自動插入顯式轉換,只會插入隱式或賦值轉換。

定義相同函式的另一種方法是

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)

第二種方法將在 第 36.5.8 節 中更全面地介紹。

當使用返回複合型別的函式時,您可能只想要其結果中的一個欄位(屬性)。您可以使用類似以下的語法來實現

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

第 8.16.5 節 中所述,欄位表示法和函式表示法是等效的。

使用返回複合型別的函式的另一種方法是將結果傳遞給接受正確行型別作為輸入的另一個函式

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

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

36.5.4. SQL帶輸出引數的函式 #

描述函式結果的另一種方法是使用 輸出引數 定義函式,如下例所示

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)

這與 第 36.5.2 節 中顯示的 add_em 版本沒有本質區別。輸出引數的真正價值在於它們提供了一種方便的方法來定義返回多個列的函式。例如,

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 僅考慮輸入引數來定義函式的呼叫簽名。這意味著在刪除函式等目的引用函式時,只有輸入引數才相關。我們可以使用以下任一方式刪除上述函式

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

引數可以標記為 IN (預設)、OUTINOUTVARIADICINOUT 引數既是輸入引數(呼叫引數列表的一部分),也是輸出引數(結果記錄型別的一部分)。VARIADIC 引數是輸入引數,但按如下方式特殊處理。

36.5.5. SQL帶輸出引數的儲存過程 #

儲存過程也支援輸出引數,但它們的工作方式與函式略有不同。在 CALL 命令中,輸出引數必須包含在引數列表中。例如,前面的銀行賬戶借記例程可以這樣編寫

CREATE PROCEDURE tp1 (accountno integer, debit numeric, OUT new_balance numeric) AS $$
    UPDATE bank
        SET balance = balance - debit
        WHERE accountno = tp1.accountno
    RETURNING balance;
$$ LANGUAGE SQL;

要呼叫此儲存過程,必須包含與 OUT 引數匹配的引數。習慣上寫 NULL

CALL tp1(17, 100.0, NULL);

如果編寫其他內容,則它必須是一個表示式,該表示式可以隱式強制轉換為引數的宣告型別,就像輸入引數一樣。但請注意,這樣的表示式將不會被求值。

PL/pgSQL 呼叫儲存過程時,而不是編寫 NULL,必須編寫一個變數來接收儲存過程的輸出。有關詳細資訊,請參閱 第 41.6.3 節

36.5.6. 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 的引數匹配其元素型別的一次或多次出現,而不是匹配其自身型別。

有時,能夠將已構建的陣列傳遞給可變函式很有用;當一個可變函式想要將其陣列引數傳遞給另一個函式時,這特別方便。此外,這是在允許不受信任使用者建立物件的模式中查詢可變函式的唯一安全方法;請參閱 第 10.3 節。您可以透過在呼叫中指定 VARIADIC 來執行此操作

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

這可以防止函式的可變引數展開為其元素型別,從而允許陣列引數值正常匹配。VARIADIC 只能附加到函式呼叫的最後一個實際引數。

在呼叫中指定 VARIADIC 也是將空陣列傳遞給可變函式的唯一方法,例如

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

簡單地寫 SELECT mleast() 不起作用,因為可變引數必須匹配至少一個實際引數。(如果您想允許此類呼叫,可以定義另一個名為 mleast 的函式,但沒有引數。)

從可變引數生成的陣列元素引數被視為沒有自己的名稱。這意味著除了指定 VARIADIC 之外,無法使用命名引數 (第 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]);

36.5.7. SQL帶有引數預設值的函式 #

函式可以宣告一些或所有輸入引數具有預設值。當函式被呼叫時,如果實際引數不足,將插入預設值。由於只能從實際引數列表的末尾省略引數,因此在具有預設值的引數之後的任何引數也必須具有預設值。(儘管使用命名引數表示法可以放寬此限制,但仍會強制執行此限制,以便位置引數表示法可以正常工作。)無論您是否使用它,此功能都會在呼叫資料庫中某些使用者不信任其他使用者的函式時產生預防措施;請參閱 第 10.3 節

例如

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

36.5.8. SQL表源函式 #

所有 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。下一節將對此進行描述。

36.5.9. 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

透過呼叫集返回函式(其引數來自表或子查詢的連續行)來構造查詢結果,這通常很有用。首選方法是使用 LATERAL 關鍵字,該關鍵字在 第 7.2.1.5 節 中進行了描述。以下是一個使用集返回函式來列舉樹結構元素的示例

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)

此示例不做任何我們無法透過簡單連線完成的事情,但在更復雜的計算中,將一些工作放入函式中的選項可能非常方便。

集返回函式也可以在查詢的 select 列表中呼叫。對於查詢本身生成的每一行,都將呼叫集返回函式,併為函式結果集中的每個元素生成一個輸出行。上面的示例也可以透過以下查詢完成

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 在查詢 select 列表中的集返回函式的行為幾乎與該集返回函式在 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 放在巢狀迴圈連線的外部,因為 gtab 沒有實際的橫向依賴。這將導致不同的輸出行順序。Select 列表中的集返回函式始終作為巢狀迴圈連線的內部進行評估,以便在考慮 FROM 子句的下一行之前,先將函式執行完畢。

如果查詢的 select 列表中有多個集返回函式,其行為與將這些函式放入單個 LATERAL ROWS FROM( ... ) FROM 子句項中的行為類似。對於底層查詢的每一行,將有一個輸出行使用每個函式的第一個結果,然後一個輸出行使用第二個結果,依此類推。如果一些集返回函式產生的行少於其他函式,則會用 NULL 值替換缺失的資料,以便為每個底層行發出的總行數與產生最多輸出的集返回函式相同。因此,集返回函式執行“鎖定”,直到它們全部耗盡,然後執行繼續處理下一個底層行。

集返回函式可以巢狀在 select 列表中,儘管在 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 的輸入行產生五次重複,為不滿足此條件的輸入行產生一次重複;但實際上,因為 generate_series(1, 5) 將在 CASE 表示式求值之前在隱式的 LATERAL FROM 項中執行,它將為每個輸入行產生五次重複。為了減少混淆,此類情況將在解析時產生錯誤。

注意

如果函式的最後一個命令是帶有 RETURNINGINSERTUPDATEDELETEMERGE,則即使函式未宣告為 SETOF 或呼叫查詢未獲取所有結果行,該命令也將始終執行直到完成。由 RETURNING 子句產生的任何額外行都將被靜默丟棄,但命令的表修改仍然會發生(並且在從函式返回之前都會完成)。

注意

PostgreSQL 10 之前,在同一個 select 列表中放置多個集返回函式,除非它們總是產生相等數量的行,否則行為並不十分合理。否則,您得到的結果行數是集返回函式產生的行數的最小公倍數。此外,巢狀的集返回函式不像上面描述的那樣工作;相反,一個集返回函式最多可以有一個集返回引數,並且每個集返回函式的巢狀都是獨立執行的。此外,之前允許條件執行(CASE 等中的集返回函式),這使得事情更加複雜。在編寫需要與舊版 PostgreSQL 相容的查詢時,建議使用 LATERAL 語法,因為這將提供跨不同版本的Consistent的結果。如果您有一個依賴於集返回函式的條件執行的查詢,您可以透過將條件測試移到一個自定義的集返回函式來修復它。例如,

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

可以變成

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 中以相同的方式工作。

36.5.10. SQL返回 TABLE 的函式 #

還有一種宣告函式返回集合的方法,即使用語法 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;

不允許將顯式 OUTINOUT 引數與 RETURNS TABLE 表示法一起使用——您必須將所有輸出列放在 TABLE 列表中。

36.5.11. 多型SQL函式 #

SQL函式可以宣告為接受和返回 第 36.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 polymorphic type because input has type unknown

使用上面宣告的 make_array,您必須提供兩個相同資料型別的引數;系統不會嘗試解析任何型別差異。因此,例如,這不起作用

SELECT make_array(1, 2.5) AS numericarray;
ERROR:  function make_array(integer, numeric) does not exist

另一種方法是使用“通用”多型型別系列,它允許系統嘗試識別合適的通用型別

CREATE FUNCTION make_array2(anycompatible, anycompatible)
RETURNS anycompatiblearray AS $$
    SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;

SELECT make_array2(1, 2.5) AS numericarray;
 numericarray
--------------
 {1,2.5}
(1 row)

由於通用型別解析規則在所有輸入型別未知時預設為選擇 text 型別,因此這也有效

SELECT make_array2('a', 'b') AS textarray;
 textarray
-----------
 {a,b}
(1 row)

允許多型引數具有固定返回型別,但反之則不然。例如

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 result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.

多型性可以與帶有輸出引數的函式一起使用。例如

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)

36.5.12. SQL帶排序規則的函式 #

當 SQL 函式有一個或多個可排序資料型別的引數時,會根據分配給實際引數的排序規則為每個函式呼叫確定排序規則,如 第 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 函式會將引數視為具有其資料型別的預設排序規則(通常是資料庫的預設排序規則,但對於域型別引數可能不同)。

可排序引數的行為可以被認為是多型性的一種有限形式,僅適用於文字資料型別。

提交更正

如果您在文件中發現任何不正確、與您對特定功能的實際使用體驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。