| PostgreSQL 7.4 文檔 | ||||
|---|---|---|---|---|
| Prev | Fast Backward | Chapter 33. 擴展 SQL | Fast Forward | Next |
SQL 函數執行一個任意 SQL 查詢的列表,返回列表裡最後一個查詢的結果。 它必須是一條 SELECT。在比較簡單的情況下(非集合的情況), 返回最後一條查詢結果的第一行。(請記住多行結果的"第一行"是不明確的,除非你用 ORDER BY 對結果排序。) 如果最後一個查詢碰巧不返回行,那麼返回 NULL 值。
另外,一個 SQL 函數可以聲明為返回一個集合,方法是把該函數的返回類型聲明為 SETOF sometype。 這個時候最後一條查詢結果的所有行都會被返回。更多的細節在下面講。
SQL 函數的函數體應該是一個用分號分隔的一條或多條 SQL 語句的列表。 請注意,因為 CREATE FUNCTION 命令的語法要求函數體要封閉在單引號裡面, 所以在函數體中使用的單引號(')必須逃逸, 方法是寫兩個單引號(') 或者在需要逃逸的單引號之前放一個反斜扛(\')。
SQL 函數的參數在查詢裡可以用 $n 語法引用: $1 指第一個參數,$2 指第二個參數,以此類推。 如果參數是復合類型,那麼可以用點表示法, 也就是說,$1.name,訪問參數裡的字段。
最簡單的 SQL 函數可能就是沒有參數並且返回一個 基本類型,比如 integer 的函數:
CREATE FUNCTION one() RETURNS integer AS '
SELECT 1 AS result;
' LANGUAGE SQL;
SELECT one();
one
-----
1
請注意我們在函數體裡面定義了一個字段別名,用于函數結果(名字是 result), 但是字段別名在函數外面是不可見的。因此,結果是以 one 為標簽的,而不是 result。
定義一個接受基本類型做參數的 SQL 函數幾乎一樣簡單。 在下面的例子裡,請注意我們在函數中是如何用$1 和 $2 引用參數的。
CREATE FUNCTION add_em(integer, integer) RETURNS integer AS '
SELECT $1 + $2;
' LANGUAGE SQL;
SELECT add_em(1, 2) AS answer;
answer
--------
3
下面是一個更有用的函數,我們可以用它對一個銀行帳號做扣款(借記消費 debit)動作:
CREATE FUNCTION tf1 (integer, numeric) RETURNS integer AS '
UPDATE bank
SET balance = balance - $2
WHERE acctountno = $1;
SELECT 1;'
LANGUAGE 'sql';一個用戶可以象下面這樣用這個函數給帳戶 17 扣款 $100.00:
SELECT tf1( 17,100.0);
實際上我們可能喜歡函數有一個比常量 1 更有用一些的結果。 所以更有可能的定義是
CREATE FUNCTION tf1 (integer, numeric) RETURNS numeric AS '
UPDATE bank
SET balance = balance - $2
WHERE accountno = $1;
SELECT balance FROM bank WHERE accountno = $1;
' LANGUAGE SQL;它修改餘額並返回新的餘額。
SQL 裡面的任何命令集都可以打成一個包, 定義成一個函數。除了 SELECT 命令, 這些命令可以包含數據修改(也就是說, INSERT,UPDATE, 和DELETE)。 不過,最後的命令必須是一條返回函數聲明的返回類型的 SELECT。 另外,如果你想定義那些執行動作但是不返回有用的數值的 SQL 函數, 你可以把它定義成返回 void。這時候它不能以 SELECT 為最後一條語句。比如:
CREATE FUNCTION clean_EMP () RETURNS void AS '
DELETE FROM EMP
WHERE EMP.salary <= 0;
' LANGUAGE SQL;
SELECT clean_EMP();
clean_emp
-----------
(1 row)
當我們聲明的函數用復合類型做參數時, 我們不僅要聲明我們需要哪個參數(像上面我們使用 $1和$2一樣),而且要聲明參數的字段。比如, 假設 emp 是一個包含雇員信息的表,並且因此也是該表每行的復合類型的名字。這裡就是一個函數 double_salary,它計算某人薪水翻番之後的數值:
CREATE TABLE emp (
name text,
salary integer,
age integer,
cubicle point
);
CREATE FUNCTION double_salary(emp) RETURNS integer 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
------+-------
Sam | 2400
請注意這裡使用 $1.salary 的語法選擇參數行數值的一個字段。 還要注意SELECT命令是如何使用一個表的名字表示該表的整個當前行作為復合數值。 表裡面的行也可以像下面這樣引用:
SELECT name, double_salary(emp.*) AS dream
FROM emp
WHERE emp.cubicle ~= point '(2,1)';這樣強調的是行的本意。
我們也可以寫一個返回復合類型的函數。 下面是一個返回一行 emp 函數的例子:
CREATE FUNCTION new_emp() RETURNS emp AS '
SELECT text ''None'' AS name,
1000 AS salary,
25 AS age,
point ''(2,2)'' AS cubicle'
LANGUAGE 'sql';在這個例子中我們給每個字段都賦予了一個常量, 當然我們可以用任何計算或表達式來代替這些常量。
注意定義函數的兩個重要的問題:
選擇列表的順序必須和與該復合類型相關的表中字段的順序完全一樣。 (象我們上面那樣給字段的命名和系統無關。)
你必須對表達式進行類型轉換以匹配復合類型的定義。 否則你將看到下面的錯誤信息:
ERROR: function declared to return emp returns varchar instead of text at column 1
返回一行(復合類型)的函數可以用作一個表函數,象下面描述地那樣。 我們還可以在 SQL 表達式的環境裡調用它,但是只有在你從該行中抽取一個字段或者把整個行傳遞給另外一個接受同樣復合類型的函數中才可以。
下面是一個例子,顯示了如何從一個行類型中抽取出一個屬性:
SELECT (new_emp()).name; name ------ None
我們需要一個額外的圓括弧以防止分析器誤解:
SELECT new_emp().name; ERROR: parser: parse error at or near "."
另外一個選擇是使用函數表示法進行字段抽取。解釋這些問題的簡單方法是我們通常交互使用attribute(table)和 table.attribute的表示法。
SELECT name(new_emp()); name ------ None
--
-- 下面的與這句話相同:
-- SELECT EMP.name AS youngster FROM EMP WHERE EMP.age < 30
--
SELECT name(EMP) AS youngster
FROM emp
WHERE age(EMP) < 30;
youngster
-----------
Sam
另外一個使用函數返回行結果的方法是聲明另外一個函數, 該函數接受一個行類型參數,然後把函數結果傳遞給這個第二個函數:
CREATE FUNCTION getname(emp) RETURNS text AS 'SELECT $1.name;' LANGUAGE SQL; SELECT getname(new_emp()); getname --------- None (1 row)
所有 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
(2 rows)如這個例子顯示的那樣,我們可以象對待一個普通表的字段一樣對待函數的結果字段。
請注意我們只從該函數中獲取了一行。這是因為我們沒有說 SETOF。 這個問題我們在下一節描述。
如果一個 SQL 函數聲明為返回 SETOF sometype。 這時候,該函數的最後的SELECT查詢一直執行到結束,並且它輸出的每行都當做該結果集合的一個元素返回。
這個特性通常用于把函數放在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 FUNCTION listchildren(text) RETURNS SETOF text AS
'SELECT name FROM nodes WHERE parent = $1'
LANGUAGE SQL;
SELECT * FROM nodes;
name | parent
-----------+--------
Top |
Child1 | Top
Child2 | Top
Child3 | Top
SubChild1 | Child1
SubChild2 | Child1
(6 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裡,請注意沒有出現Child2, Child3等的行。 這是因為listchildren 為這些參數返回一個空集合, 因此不生成任何結果行。
SQL 函數可以聲明為接受並返回多態的類型 anyelement 和 anyarray。參閱 Section 33.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 "anyarray"/"anyelement" type because input has type "unknown"
PostgreSQL 允許有多態的參數的函數返回一個固定類型,但是反過來不行。比如:
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 "anyarray" or "anyelement" must have at least one argument of either type.