原創文章,轉載請務必將下面這段話置於文章開頭處(保留超連接)。
本文轉發自Jason’s Blog,原文連接 http://www.jasongj.com/2015/12/27/SQL4_存儲過程_Store Procedure/sql
百度百科是這麼描述存儲過程的:存儲過程(Stored Procedure)是在大型數據庫系統中,一組爲了完成特定功能的SQL語句集,存儲在數據庫中,首次編譯後再次調用不須要再次編譯,用戶經過指定存儲過程的名字並給出參數(若是有)來執行它。它是數據庫中的一個重要對象,任何一個設計良好的數據庫應用程序都應該用到存儲過程。
維基百科是這樣定義的:A stored procedure (also termed proc, storp, sproc, StoPro, StoredProc, StoreProc, sp, or SP) is a subroutine available to applications that access a relational database management system (RDMS). Such procedures are stored in the database data dictionary。數據庫
PostgreSQL對存儲過程的描述是:存儲過程和用戶自定義函數(UDF)是SQL和過程語句的集合,它存儲於數據庫服務器並能被SQL接口調用。express
總結下來存儲過程有以下特性:緩存
首先看看使用存儲過程的優點服務器
固然,使用存儲過程也有它的劣勢網絡
PostgreSQL官方支持PL/pgSQL,PL/Tcl,PL/Perl和PL/Python這幾種過程語言。同時還支持一些第三方提供的過程語言,如PL/Java,PL/PHP,PL/Py,PL/R,PL/Ruby,PL/Scheme,PL/sh。app
CREATE OR REPLACE FUNCTION add(a INTEGER, b NUMERIC) RETURNS NUMERIC AS $$ SELECT a+b; $$ LANGUAGE SQL;
調用方法函數
SELECT add(1,2); add ----- 3 (1 row) SELECT * FROM add(1,2); add ----- 3 (1 row)
上面這種方式參數列表只包含函數輸入參數,不包含輸出參數。下面這個例子將同時包含輸入參數和輸出參數性能
CREATE OR REPLACE FUNCTION plus_and_minus (IN a INTEGER, IN b NUMERIC, OUT c NUMERIC, OUT d NUMERIC) AS $$ SELECT a+b, a-b; $$ LANGUAGE SQL;
調用方式 spa
SELECT plus_and_minus(3,2); add_and_minute ---------------- (5,1) (1 row) SELECT * FROM plus_and_minus(3,2); c | d ---+--- 5 | 1 (1 row)
該例中,IN表明輸入參數,OUT表明輸出參數。這個帶輸出參數的函數和以前的add
函數並沒有本質區別。事實上,輸出參數的最大價值在於它爲函數提供了返回多個字段的途徑。
在函數定義中,能夠寫多個SQL語句,不必定是SELECT語句,能夠是其它任意合法的SQL。但最後一條SQL必須是SELECT語句,而且該SQL的結果將做爲該函數的輸出結果。
CREATE OR REPLACE FUNCTION plus_and_minus (IN a INTEGER, IN b NUMERIC, OUT c NUMERIC, OUT d NUMERIC) AS $$ SELECT a+b, a-b; INSERT INTO test VALUES('test1'); SELECT a-b, a+b; $$ LANGUAGE SQL;
其效果以下
SELECT * FROM plus_and_minus(5,3); c | d ---+--- 2 | 8 (1 row) SELECT * FROM test; a ------- test1 (1 row)
基於PL/PgSQL的存儲過程定義
PL/pgSQL是一個塊結構語言。函數定義的全部文本都必須是一個塊。一個塊用下面的方法定義:
[ <<label>> ] [DECLARE declarations] BEGIN statements END [ label ];
中括號部分爲可選部分
聲明一個變量的語法以下:
name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ];
使用PL/PgSQL語言的函數定義以下:
CREATE FUNCTION somefunc() RETURNS integer AS $$ DECLARE quantity integer := 30; BEGIN -- Prints 30 RAISE NOTICE 'Quantity here is %', quantity; quantity := 50; -- Create a subblock DECLARE quantity integer := 80; BEGIN -- Prints 80 RAISE NOTICE 'Quantity here is %', quantity; -- Prints 50 RAISE NOTICE 'Outer quantity here is %', outerblock.quantity; END; -- Prints 50 RAISE NOTICE 'Quantity here is %', quantity; RETURN quantity; END; $$ LANGUAGE plpgsql;
聲明函數參數
若是隻指定輸入參數類型,不指定參數名,則函數體裏通常用$1,$n這樣的標識符來使用參數。
CREATE OR REPLACE FUNCTION discount(NUMERIC) RETURNS NUMERIC AS $$ BEGIN RETURN $1 * 0.8; END; $$ LANGUAGE PLPGSQL;
但該方法可讀性很差,此時能夠爲$n參數聲明別名,而後能夠在函數體內經過別名指向該參數值。
CREATE OR REPLACE FUNCTION discount(NUMERIC) RETURNS NUMERIC AS $$ DECLARE total ALIAS FOR $1; BEGIN RETURN total * 0.8; END; $$ LANGUAGE PLPGSQL;
筆者認爲上述方法仍然不夠直觀,也不夠完美。幸虧PostgreSQL提供另一種更爲直接的方法來聲明函數參數,即在聲明參數類型時同時聲明相應的參數名。
CREATE OR REPLACE FUNCTION discount(total NUMERIC) RETURNS NUMERIC AS $$ BEGIN RETURN total * 0.8; END; $$ LANGUAGE PLPGSQL;
返回多行或多列
PostgreSQL除了支持自帶的類型外,還支持用戶建立自定義類型。在這裏能夠自定義一個複合類型,並在函數中返回一個該複合類型的值,從而實現返回一行多列。
CREATE TYPE compfoo AS (col1 INTEGER, col2 TEXT); CREATE OR REPLACE FUNCTION getCompFoo (in_col1 INTEGER, in_col2 TEXT) RETURNS compfoo AS $$ DECLARE result compfoo; BEGIN result.col1 := in_col1 * 2; result.col2 := in_col2 || '_result'; RETURN result; END; $$ LANGUAGE PLPGSQL; SELECT * FROM getCompFoo(1,'1'); col1 | col2 ------+---------- 2 | 1_result (1 row)
使用輸出參數名返回一行多列
在聲明函數時,除指定輸入參數名及類型外,還可同時聲明輸出參數類型及參數名。此時函數能夠輸出一行多列。
CREATE OR REPLACE FUNCTION get2Col (IN in_col1 INTEGER,IN in_col2 TEXT, OUT out_col1 INTEGER, OUT out_col2 TEXT) AS $$ BEGIN out_col1 := in_col1 * 2; out_col2 := in_col2 || '_result'; END; $$ LANGUAGE PLPGSQL; SELECT * FROM get2Col(1,'1'); out_col1 | out_col2 ----------+---------- 2 | 1_result (1 row)
實際項目中,存儲過程常常須要返回多行記錄,能夠經過SETOF實現。使用SETOF返回多行記錄
CREATE TYPE compfoo AS (col1 INTEGER, col2 TEXT); CREATE OR REPLACE FUNCTION getSet(rows INTEGER) RETURNS SETOF compfoo AS $$ BEGIN RETURN QUERY SELECT i * 2, i || '_text' FROM generate_series(1, rows, 1) as t(i); END; $$ LANGUAGE PLPGSQL; SELECT col1, col2 FROM getSet(2); col1 | col2 ------+-------- 2 | 1_text 4 | 2_text (2 rows)
本例返回的每一行記錄是複合類型,該方法也可返回基本類型的結果集,即多行一列。
CREATE OR REPLACE FUNCTION getTable(rows INTEGER) RETURNS TABLE(col1 INTEGER, col2 TEXT) AS $$ BEGIN RETURN QUERY SELECT i * 2, i || '_text' FROM generate_series(1, rows, 1) as t(i); END; $$ LANGUAGE PLPGSQL; SELECT col1, col2 FROM getTable(2); col1 | col2 ------+-------- 2 | 1_text 4 | 2_text (2 rows)
使用EXECUTE語句執行動態命令 此時從函數中讀取字段就和從表或視圖中取字段同樣,能夠看此種類型的函數當作是帶參數的表或者視圖。
有時在PL/pgSQL函數中須要生成動態命令,這個命令將包括他們每次執行時使用不一樣的表或者字符。EXECUTE語句用法以下:
EXECUTE command-string [ INTO [STRICT] target] [USING expression [, ...]];
此時PL/plSQL將再也不緩存該命令的執行計劃。相反,在該語句每次被執行的時候,命令都會編譯一次。這也讓該語句得到了對各類不一樣的字段甚至表進行操做的能力。
command-string包含了要執行的命令,它可使用參數值,在命令中經過引用如$1,$2等來引用參數值。這些符號的值是指USING字句的值。這種方法對於在命令字符串中使用參數是最好的:它能避免運行時數值從文原本回轉換,而且不容易產生SQL注入,並且它不須要引用或者轉義。
CREATE TABLE testExecute AS SELECT i || '' AS a, i AS b FROM generate_series(1, 10, 1) AS t(i); CREATE OR REPLACE FUNCTION execute(filter TEXT) RETURNS TABLE (a TEXT, b INTEGER) AS $$ BEGIN RETURN QUERY EXECUTE 'SELECT * FROM testExecute where a = $1' USING filter; END; $$ LANGUAGE PLPGSQL; SELECT * FROM execute('3'); a | b ---+--- 3 | 3 (1 row) SELECT * FROM execute('3'' or ''c''=''c'); a | b ---+--- (0 rows)
固然,也可使用字符串拼接的方式在command-string中使用參數,但會有SQL注入的風險。
CREATE TABLE testExecute AS SELECT i || '' AS a, i AS b FROM generate_series(1, 10, 1) AS t(i); CREATE OR REPLACE FUNCTION execute(filter TEXT) RETURNS TABLE (a TEXT, b INTEGER) AS $$ BEGIN RETURN QUERY EXECUTE 'SELECT * FROM testExecute where b = ''' || filter || ''''; END; $$ LANGUAGE PLPGSQL; SELECT * FROM execute(3); a | b ---+--- 3 | 3 (1 row) SELECT * FROM execute('3'' or ''c''=''c'); a | b ----+---- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | 10 (10 rows)
從該例中能夠看出使用字符串拼接的方式在command-string中使用參數會引入SQL注入攻擊的風險,而使用USING的方式則能有效避免這一風險。
本文中並未區分PostgreSQL中的UDF和存儲過程。實際上PostgreSQL建立存儲與建立UDF的方式同樣,並無專用於建立存儲過程的語法,如CREATE PRECEDURE。在PostgreSQL官方文檔中也暫未找到這兩者的區別。卻是從一些資料中找對了它們的對比,以下表如示,僅供參考。
SQL函數能夠聲明爲接受多態類型(anyelement和anyarray)的參數或返回多態類型的返回值。
函數參數和返回值均爲多態類型。其調用方式和調用其它類型的SQL函數徹底相同,只是在傳遞字符串類型的參數時,須要顯示轉換到目標類型,不然將會被視爲unknown類型。
CREATE OR REPLACE FUNCTION get_array(anyelement, anyelement) RETURNS anyarray AS $$ SELECT ARRAY[$1, $2]; $$ LANGUAGE SQL; SELECT get_array(1,2), get_array('a'::text,'b'::text); get_array | get_array -----------+----------- {1,2} | {a,b} (1 row)
函數參數爲多態類型,而返回值爲基本類型
CREATE OR REPLACE FUNCTION is_greater(anyelement, anyelement) RETURNS BOOLEAN AS $$ SELECT $1 > $2; $$ LANGUAGE SQL; SELECT is_greater(7.0, 4.5); is_greater ------------ t (1 row) SELECT is_greater(2, 4); is_greater ------------ f (1 row)
輸入輸出參數均爲多態類型。這種狀況與第一種狀況同樣。
CREATE OR REPLACE FUNCTION get_array (IN anyelement, IN anyelement, OUT anyelement, OUT anyarray) AS $$ SELECT $1, ARRAY[$1, $2]; $$ LANGUAGE SQL; SELECT get_array(4,5), get_array('c'::text, 'd'::text); get_array | get_array -------------+------------- (4,"{4,5}") | (c,"{c,d}") (1 row)
在PostgreSQL中,多個函數可共用同一個函數名,但它們的參數必須得不一樣。這一規則與面嚮對象語言(好比Java)中的函數重載相似。也正因如此,在PostgreSQL刪除函數時,必須指定其參數列表,如:
1 |
DROP FUNCTION get_array(anyelement, anyelement); |
另外,在實際項目中,常常會用到CREATE OR REPLACE FUNCTION去替換已有的函數實現。若是同名函數已存在,但輸入參數列表不一樣,會建立同名的函數,也即重載。若是同名函數已存在,且輸入輸出參數列表均相同,則替換。若是已有的函數輸入參數列表相同,但輸出參數列表不一樣,則會報錯,並提示須要先DROP已有的函數定義。