支援的版本: 目前 (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,否則多列結果的第一列沒有明確定義。)如果最後一個查詢碰巧沒有傳回任何列,則將傳回空值。

或者,可以宣告 SQL 函數傳回集合(即多列),方法是將函數的傳回型別指定為 SETOF sometype,或等效地宣告為 RETURNS TABLE(columns)。在這種情況下,將傳回最後一個查詢結果的所有列。更多詳細資訊如下所示。

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 的擴充功能。

注意

SQL 函數的整個主體會在執行任何部分之前進行剖析。雖然 SQL 函數可以包含更改系統目錄的命令(例如 CREATE TABLE),但這些命令的效果在函數中稍後命令的剖析分析期間將不可見。因此,例如,如果將 CREATE TABLE foo (...); INSERT INTO foo VALUES(...); 打包到單個 SQL 函數中,則無法按預期工作,因為在剖析 INSERT 命令時,foo 尚不存在。建議在這種情況下使用 PL/pgSQL 而不是 SQL 函數。

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);

注意

使用名稱參照 SQL 函式引數的功能是在 PostgreSQL 9.2 中新增的。要在較舊的伺服器中使用的函式必須使用 $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;

使用者可以執行此函式,從帳戶 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;

如果最終的 SELECTRETURNING 子句在SQL函式中沒有完全傳回函式宣告的結果類型,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;

在本例中,我們使用常數值指定了每個屬性,但任何計算都可以取代這些常數。

請注意有關定義函式的兩個重要事項:

  • 查詢中的選取清單順序必須與欄位出現在複合類型中的順序完全相同。(命名欄位,就像我們上面所做的那樣,與系統無關。)

  • 我們必須確保每個運算式的類型都可以轉換為複合類型的相應欄位的類型。否則,我們會收到像這樣的錯誤:

    
    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,它只傳回正確複合類型的一個欄位。在這種情況下,這並不是真的更好,但在某些情況下這是一個方便的替代方案 — 例如,如果我們需要透過呼叫另一個傳回所需複合值的函式來計算結果。另一個例子是,如果我們嘗試編寫一個傳回複合類型上的域(domain)的函式,而不是純複合類型,那麼始終需要將其編寫為傳回單個欄位,因為沒有辦法強制轉換整個列結果。

我們可以透過在數值運算式中使用它來直接呼叫此函數

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 的函數,且不帶任何參數。)

從可變參數產生的陣列元素參數被視為沒有任何自己的名稱。 這表示無法使用具名引數(第 4.3 節)呼叫可變參數函數,除非您指定 VARIADIC。 例如,這將起作用

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)

這個範例沒有做任何我們無法用簡單的 join 完成的事情,但在更複雜的計算中,將一些工作放入函數中的選項可能非常方便。

傳回集合的函數也可以在查詢的 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 語法時,從函數結果的內部 join 中獲得的行為相同。

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 放在巢狀迴圈聯結的外部,因為 g 沒有對 tab 的實際 lateral 相依性。這將導致不同的輸出列順序。select 列表中傳回集合的函數始終被評估為如同它們位於與 FROM 子句的其餘部分進行的巢狀迴圈聯結的內部,以便在考慮 FROM 子句的下一列之前,先完整執行函數。

如果在查詢的 select 列表中有多個傳回集合的函數,則行為類似於將函數放入單個 LATERAL ROWS FROM( ... ) FROM 子句項目的情況。對於來自底層查詢的每一列,都有一個輸出列使用每個函數的第一個結果,然後是一個輸出列使用第二個結果,依此類推。如果某些傳回集合的函數產生的輸出比其他函數少,則會用空值替換遺失的資料,以便為一個底層列發出的總列數與產生最多輸出的傳回集合的函數相同。因此,傳回集合的函數以鎖步的方式執行,直到它們全部耗盡,然後繼續執行下一個底層列。

傳回集合的函數可以在 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 項目中執行,因此它會產生每個輸入列的五個重複項。為了減少混淆,這種情況會產生一個剖析時期的錯誤。

注意

如果函數的最後一個指令是 INSERTUPDATEDELETEMERGE 並帶有 RETURNING,則無論該函數是否使用 SETOF 宣告,或者呼叫查詢是否提取所有結果列,該指令都將始終完整執行。RETURNING 子句產生的任何額外列都會被靜默丟棄,但被命令的表格修改仍然會發生(並且會在從函數傳回之前全部完成)。

注意

PostgreSQL 10 之前的版本中,在同一個 select 列表中放置多個集合回傳函數的行為並不明智,除非它們總是產生相同數量的列。否則,您得到的輸出列數將等於集合回傳函數所產生列數的最小公倍數。此外,巢狀的集合回傳函數無法如上述方式運作;相反地,一個集合回傳函數最多只能有一個集合回傳的參數,並且每個巢狀的集合回傳函數都會獨立執行。此外,條件式執行(CASE 等中的集合回傳函數)先前是被允許的,這使得事情變得更加複雜。建議在使用舊版本的 PostgreSQL 時,使用 LATERAL 語法來編寫查詢,因為這樣可以在不同版本之間提供一致的結果。如果您有一個查詢依賴於集合回傳函數的條件式執行,您可以透過將條件測試移到自訂的集合回傳函數中來修正它。例如:

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

另一種方法是使用 common 多型類型系列,這允許系統嘗試識別適合的通用類型:

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 locale 中,結果將是 ABC,但在許多其他 locale 中,它將是 abc。可以透過將 COLLATE 子句添加到任何參數來強制使用校對規則,例如:

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

或者,如果您希望函數以特定的校對規則運作,而不管它被如何呼叫,請在函數定義中根據需要在插入 COLLATE 子句。此版本的 anyleast 將始終使用 en_US locale 來比較字串:

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 函數會將其參數視為具有其資料類型的預設校對規則(通常是資料庫的預設校對規則,但對於網域類型的參數可能有所不同)。

可校對參數的行為可以被認為是多型的一種有限形式,僅適用於文字資料類型。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用 此表單 來報告文件問題。