PostgreSQL 查詢語言(SQL)函數

2021-09-03 14:05 更新
37.5.1. SQL函數的參數
37.5.2. 基本類型上的SQL
37.5.3. 組合類型上的SQL函數
37.5.4. 帶有輸出參數的SQL函數
37.5.5. 帶有可變數量參數的SQL函數
37.5.6. 帶有參數默認值的SQL函數
37.5.7. SQL 函數作為表來源
37.5.8. 返回集合的SQL函數
37.5.9. 返回TABLESQL函數
37.5.10. 多態(tài)SQL函數
37.5.11. 帶有排序規(guī)則的SQL函數

SQL 函數執(zhí)行一個由任意 SQL 語句構成的列表,返回列表中最后一個查詢的結果。在簡單(非集合)的情況中,最后一個查詢的結果的第一行將被返回(記住一個多行結果的第一行不是良定義的,除非你使用ORDER BY)。如果最后一個查詢正好根本不返回行,將會返回空值。

或者,一個 SQL 函數可以通過指定函數的返回類型為SETOF sometype 被聲明為返回一個集合(也就是多個行),或者等效地聲明它為RETURNS TABLE(columns )。在這種情況下,最后一個查詢的結果的所有行會被返回。下文將給出進一步的細節(jié)。

一個 SQL 函數的主體必須是一個由分號分隔的 SQL 語句的列表。最后一個語句之后的分號是可選的。除非函數被聲明為返回void,最后一個語句必須是一個SELECT或者一個帶有RETURNING子句的INSERTUPDATE或者 DELETE。

SQL語言中的任何命令集合都能被打包在一起并且被定義成一個函數。除了SELECT查詢,命令可以包括數據修改查詢(INSERTUPDATE以及DELETE)和其他 SQL 命令(你不能在 SQL函數中使用事務控制命令,例如COMMITSAVEPOINT,以及一些工具命令,例如VACUUM)。不過,最后一個命令必須是一個SELECT或者帶有一個RETURNING子句,該命令必須返回符合函數返回類型的數據?;蛘撸绻阆胍x一個執(zhí)行動作但是不返回有用的值的函數,你可以把它定義為返回 void。例如,這個函數從emp表中移除具有負值薪水的行:

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

SELECT clean_emp();

 clean_emp
-----------

(1 row)

注意

在被執(zhí)行前,SQL 函數的整個主體都要被解析。雖然 SQL 函數可以包含修改系統(tǒng)目錄的命令(如CREATE TABLE),但這類命令的效果對于該函數中后續(xù)命令的解析分析不可見。例如,如果把CREATE TABLE foo (...); INSERT INTO foo VALUES(...);打包到一個 SQL 函數中是得不到預期效果的,因為在解析INSERT命令時 foo還不存在。在這類情況下,推薦使用PL/pgSQL而不是 SQL 函數。

CREATE FUNCTION命令的語法要求函數體被寫作一個字符串常量。使用用于字符串常量的美元引用通常最方便(見第 4.1.2.4 節(jié))。你過你選擇使用常規(guī)的單引號引用的字符串常量語法,你必須在函數體中雙寫單引號( ')和反斜線(\)(假定轉義字符串語法)(見第 4.1.2.1 節(jié))。

37.5.1. SQL函數的參數

一個 SQL 函數的參數可以在函數體中用名稱或編號引用。下面會有兩種方法的例子。

要使用一個名稱,將函數參數聲明為帶有一個名稱,然后在函數體中只寫該名稱。如果參數名稱與函數內當前 SQL 命令中的任意列名相同,列名將優(yōu)先。如果不想這樣,可以用函數本身的名稱來限定參數名,也就是function_name .argument_name (如果這會與一個被限定的列名沖突,照例還是列名贏得優(yōu)先。你可以通過為 SQL 命令中的表選擇一個不同的別名來避免這種混淆)。

在更舊的數字方法中,參數可以用語法$n 引用:$1指的是第一個輸入參數,$2指的是第二個,以此類推。不管特定的參數是否使用名稱聲明,這種方法都有效。

如果一個參數是一種組合類型,那么點號記法(如 argname .fieldname $1.fieldname )也可以被用來 訪問該參數的屬性。同樣,你可能需要用函數的名稱來限定參數的名稱以避免歧義。

SQL 函數參數只能被用做數據值而不能作為標識符。例如這是合理的:

INSERT INTO mytable VALUES ($1);

但這樣就不行:

INSERT INTO $1 VALUES (42);

注意

使用名稱來引用 SQL 函數參數的能力是在PostgreSQL 9.2 中加入的。要在老的服務器中使用的函數必須使用$n 記法。

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

注意我們?yōu)樵摵瘮档慕Y果在函數體內定義了一個列別名(名為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;

一個用戶可以這樣執(zhí)行這個函數來從賬戶 17 中借記 $100.00:

SELECT tf1(17, 100.0);

在這個例子中,我們?yōu)榈谝粋€參數選擇了名稱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 。)

37.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 節(jié))。

有時候實時構建一個組合參數很方便。這可以用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;

在這個例子中,我們?yōu)槊恳粋€屬性指定了一個常量值,但是可以用任何計算來替換這些常量。

有關定義函數有兩件重要的事情:

  • 查詢中的選擇列表順序必須與列在復合類型中出現的順序完全相同。(正如我們上面所做的那樣,命名列與系統(tǒng)無關。)

  • 我們必須確保每個表達式的類型都可以轉換為復合類型的相應列的類型。 否則我們會得到這樣的錯誤:

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

    與基本類型的情況一樣,系統(tǒng)不會自動插入顯式轉換,只會插入隱式或賦值轉換。

定義同樣的函數的一種不同的方法是:

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)

第二種方式在本篇文章中的第 37.5.7 節(jié)中有更完全的描述。

當你使用一個返回組合類型的函數時,你可能只想要其結果中的一個域(屬性)。 你可以這樣做:

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 節(jié)中所說,字段記法和函數記法是等效的。

另一種使用返回組合類型的函數的方法是把結果傳遞給另一個接收正確行類型作為輸入的函數:

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

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

37.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)

這和本文中的第 37.5.2 節(jié)中展示的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)

這里實際發(fā)生的是我們?yōu)樵摵瘮档慕Y果創(chuàng)建了一個匿名的組合類型。上述例子具有與下面相同的最終結果

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;

但是不必單獨定義組合類型常常很方便。注意輸出參數的名稱并非只是裝飾,而且決定了匿名組合類型的列名(如果你為一個輸出參數忽略了名稱,系統(tǒng)將自行選擇一個名稱)。

在從 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(默認)、OUT、INOUT或者VARIADIC。一個INOUT參數既作為一個輸入參數(調用參數列表的一部分)又作為一個輸出參數(結果記錄類型的一部分)。VARIADIC參數是輸入參數,但被按照后文所述特殊對待。

37.5.5. 帶有可變數量參數的SQL函數

只要可選的參數都是相同的數據類型,SQL函數可以被聲明為接受可變數量的參數??蛇x的參數將被作為一個數組傳遞給該函數。聲明該函數時要把最后一個參數標記為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]);    -- 不起作用

但是你實際無法這樣寫 — 或者說至少它將無法匹配這個函數定義。一個被標記為VARIADIC的參數匹配其元素類型的一次或者多次出現,而不是它自身類型的出現。

有時候能夠傳遞一個已經構造好的數組給 variadic 函數是有用的,特別是當 一個 variadic 函數想要把它的數組參數傳遞給另一個函數時這會特別方便。此外,這是在一個允許不可信用戶創(chuàng)建對象的方案中調用一個variadic函數的唯一安全的方式,見第 10.3 節(jié)。你可以通過在調用中指定VARIADIC來做到這一點:

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

這會阻止該函數的 variadic 參數擴展成它的元素結構,從而允許數組參 數值正常匹配。VARIADIC只能被附著在函數調用的最后一 個實參上。

在調用中指定VARIADIC也是將空數組傳遞給 variadic 函數 的唯一方式,例如:

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

簡單地寫成SELECT mleast()是沒有作用的,因為一個 variadic 參數必須匹配至少一個實參(如果想允許這類調用,你可以定義第二個沒有 參數且也叫mleast的函數)。

從一個 variadic 參數產生的數組元素參數會被當做自己不具有名稱。這 意味著不能使用命名參數調用 variadic 函數(第 4.3 節(jié)),除非你指定了 VARIADIC。例如下面的調用是可以工作的:

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

但這些就不行:

SELECT mleast(arr => 10);
SELECT mleast(arr => ARRAY[10, -1, 5, 4.4]);

37.5.6. 帶有參數默認值的SQL函數

函數可以被聲明為對一些或者所有輸入參數具有默認值。只要調用函數時 沒有給出足夠多的實參,就會插入默認值來彌補缺失的實參。由于參數只 能從實參列表的尾部開始被省略,在一個有默認值的參數之后的所有參數 都不得不也具有默認值(盡管使用命名參數記法可以允許放松這種限制, 這種限制仍然會被強制以便位置參數記法能工作)。不管你是否使用它,這種能力都要求在某些用戶不信任其他用戶的數據中調用函數時做一些預防措施,見第 10.3 節(jié)。

例如:

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();  -- 因為第一個參數沒有默認值,所以會失敗
ERROR:  function foo() does not exist

=符號也可以用來替代關鍵詞 DEFAULT。

37.5.7. 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)

正如例子所示,我們可以把函數結果的列當作常規(guī)表的列來使用。

注意我們只從函數得到了一行。這是因為我們沒有使用SETOF。 這會在下一節(jié)中介紹。

37.5.8. 返回集合的SQL函數

當一個 SQL 函數被聲明為返回SETOF sometype 時,該函數的 最后一個查詢會被執(zhí)行完,并且它輸出的每一行都會被 作為結果集的一個元素返回。

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。

通過多次調用集合返回函數來構建查詢的結果非常有用,每次調用的參數 來自于一個表或者子查詢的連續(xù)行。做這種事情最好的方法是使用 第 7.2.1.5 節(jié)中描述的LATERAL關鍵 詞。這里是一個使用集合返回函數枚舉樹結構中元素的例子:

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

這會是完全一樣的,除了在這個特別的例子中,規(guī)劃器會選擇把g放在嵌套循環(huán)連接的外側,因為gtab沒有實際的橫向依賴。那會導致一種不同的輸出行順序。選擇列表中的集合返回函數總是會被計算,就好像它們在FROM子句剩余部分的嵌套循環(huán)連接的內側一樣,因此在考慮來自 FROM子句的下一行之前,這些函數會運行到完成。

如果在查詢的選擇列表中有不止一個集合返回函數,則行為類似于把那些函數放到一個單一的LATERAL ROWS FROM( ... ) FROM子句項中的行為。對于來自底層查詢的每一行,都有一個用到每個函數首個結果的輸出行,然后是一個使用每個函數第二個結果的輸出行,以此類推。如果某些集合返回函數產生的輸出比其他函數少,會用空值代替缺失的數據,因此為一個底層行形成的總行數等于產生最多輸出的集合返回函數的輸出行數。因此集合返回函數會 步調一致地運行直到它們的輸出被耗盡,然后用下一個底層行繼續(xù)執(zhí)行。

集合返回函數可以被嵌套在一個選擇列表中,不過在FROM子句項中不允許這樣做。在這種情況下,嵌套的每一層會被單獨對待,就像它是一個單獨的LATERAL ROWS FROM( ... )項一樣。例如,在

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

中,集合返回函數srf2、srf3srf5將為tab的每一行步調一致地運行,然后會對較低層的函數產生的每一行以步調一致的形式應用srf1srf4。

CASECOALESCE這樣的條件計算結構中,不能使用集合返回函數。例如,考慮

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

看起來這個語句應該產生滿足x > 0的輸入行的五次重復,以及不滿足的行的一次重復。但實際上,由于在CASE表達時被計算前,generate_series(1, 5)會被運行在一個隱式的LATERAL FROM項中,它會為每個輸入行產生五次重復。為了減少混亂,這類情況會產生一個解析時錯誤。

注意

如果函數的最后一個命令是帶有RETURNINGINSERT、UPDATE或者 DELETE,該命令將總是會被執(zhí)行完,即使函數沒有用 SETOF定義或者調用查詢不要求取出所有結果行也是如此。 RETURNING子句產生的多余的行會被悄無聲息地丟掉,但是 在命令的目標表上的修改仍然會發(fā)生(而且在從該函數返回前就會全部完成)。

注意

PostgreSQL 10之前,把多個集合返回函數放在同一個選擇列表中的行為并不容易察覺,除非它們總是產生同等的行數。否則,你得到的輸出行數將會是各集合返回函數產生的行數的最小公倍數。此外,嵌套的集合返回函數不會按照上述的方式工作。相反,一個集合返回函數只能有最多一個集合返回參數,集合返回函數的每一次嵌套會被獨立運行。此外,條件執(zhí)行(CASE等中的集合返回函數)以前是被允許的,但是會讓事情更加復雜。在編寫需要在較老的 PostgreSQL版本中工作的查詢時,推薦使用LATERAL語法,因為這種語法能夠在不同的版本間提供一致的結果。如果有一個依賴于集合返回函數的條件執(zhí)行,那么可能可以通過把條件測試移到一個自定義集合返回函數中來修正該問題。例如,

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

37.5.9. 返回TABLESQL函數

還有另一種方法可以把函數聲明為返回一個集合,即使用 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;

不允許把顯式的OUT或者INOUT參數用于 RETURNS TABLE記法 — 必須把所有輸出列放在 TABLE列表中。

37.5.10. 多態(tài)SQL函數

SQL函數可以聲明為接受和返回第 37.2.5 節(jié)中描述的多態(tài)類型。 這是一個多態(tài)函數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,您必須提供兩個數據類型完全相同的參數; 系統(tǒng)不會嘗試解決任何類型差異。因此,例如,這并不工作:

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

另一種方法是使用common多態(tài)類型系列,它允許系統(tǒng)嘗試識別合適的公共類型:

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)

因為當所有輸入都是未知類型時,通用類型解析規(guī)則默認選擇類型text,這也適用:

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

允許具有多態(tài)參數和固定的返回類型,但是反過來不行。例如:

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.

多態(tài)化可以用在具有輸出參數的函數上。例如:

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)

多態(tài)化也可以用在 variadic 函數上。例如:

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)

37.5.11. 帶有排序規(guī)則的SQL函數

當一個 SQL 函數具有一個或者更多可排序數據類型的參數時,按照 第 23.2 節(jié)中所述,對每一次函數調用都會根據分 配給實參的排序規(guī)則為其確定一個排序規(guī)則。如果成功地確定(即在 參數之間沒有隱式排序規(guī)則的沖突),那么所有的可排序參數都被認 為隱式地具有該排序規(guī)則。這將會影響函數中對排序敏感的操作的行 為。例如,使用上述的anyleast函數時,

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

的結果將依賴于數據庫的默認排序規(guī)則。在C區(qū)域中, 結果將是ABC,但是在很多其他區(qū)域中它將是 abc。可以在任意參數上增加一個COLLATE 子句來強制要使用的排序規(guī)則,例如:

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

此外,如果你希望一個函數用一個特定的排序規(guī)則工作而不管用什么排序規(guī)則 調用它,可根據需要在函數定義中插入COLLATE子句。 這種版本的anyleast將總是使用en_US區(qū)域來比 較字符串:

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

但是注意如果應用到不可排序數據類型上,這將會拋出一個錯誤。

如果在實參之間無法確定共同的排序規(guī)則,那么 SQL 函數會把它的參數 當作擁有其數據類型的默認排序規(guī)則(通常是數據庫的默認排序規(guī)則, 但是域類型的參數可能會不同)。

可排序參數的行為可以被想成是多態(tài)的一種受限形式,只對于文本數據 類型有效。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號