PostgreSQL存儲過程幾年前寫過不少,可是幾年不碰又陌生了,今天給客戶寫了一個存儲過程,查了一些資料,記錄一下:php
--建立測試表
create table student (id integer, name varchar(64));
create table employees (id integer, age integer);
--table_new 須要在外部建立
create table table_new (id integer, name varchar(64), age integer);
--插入測試數據
insert into student select generate_series(1, 100), 'lili_' || cast(random()*100 as varchar(2));
insert into employees select generate_series(1, 50), random()*100;
select count(*) from student;
select count(*) from employees;
--存儲過程
create or replace function P_DWA_ERP_LEDGER_JQ_MONTH_NEW( v_mouth varchar(8), out v_retcode text, out v_retinfo text, out v_row_num integer)
AS
$BODY$
declare
begin
insert into table_new(id, name, age) select t.id, t.name, m.age from student t, employees m where t.id=m.id;
GET DIAGNOSTICS V_ROW_NUM := ROW_COUNT;
-- 執行成功後的返回信息
V_RETCODE := 'SUCCESS';
V_RETINFO := '結束';
--異常處理
EXCEPTION
WHEN OTHERS THEN
V_RETCODE := 'FAIL';
V_RETINFO := SQLERRM;
end;
$BODY$
language plpgsql;
--調用存儲過程
select * from P_DWA_ERP_LEDGER_JQ_MONTH_NEW('12');
--查看結果
select count(*) from table_new;
delete from table_new;
vacuum table_new;
create or replace function test() returns integer
AS
$BODY$
declare
v_mouth varchar(8);
v_retcode text;
v_retinfo text;
v_row_num integer;
begin
v_mouth := 12;
select * from P_DWA_ERP_LEDGER_JQ_MONTH_NEW(v_mouth) into v_retcode, v_retinfo, v_row_num;
raise notice 'P_DWA_ERP_LEDGER_JQ_MONTH_NEW result is: %, %, %, %', v_mouth, v_retcode, v_retinfo, v_row_num;
return 0;
end;
$BODY$
language plpgsql;
select test();
1、寫法示例html
http://panyongzheng.iteye.com/blog/2194815sql
PostgreSQL的存儲過程簡單入門 http://blog.csdn.net/rachel_luo/article/details/8073458
存儲過程事物 http://www.php100.com/manual/PostgreSQL8/tutorial-transactions.html
PL/pgSQL - SQL存儲過程語言 https://wiki.postgresql.org/wiki/9.1%E7%AC%AC%E4%B8%89%E5%8D%81%E4%B9%9D%E7%AB%A0
postgreSQL存儲過程寫法示例http://blog.sina.com.cn/s/blog_448574810101f64u.html
結構
PL/pgSQL是一種塊結構的語言,比較方便的是用pgAdmin III新建Function,填入一些參數就能夠了。基本上是這樣的數據庫
2、處理異常express
轉自:https://www.cnblogs.com/lottu/p/7410978.html緩存
1. 異常錯誤處理
在PL/pgSQL函數中,若是沒有異常捕獲,函數會在發生錯誤時直接退出,與其相關的事物也會隨之回滾。咱們能夠經過使用帶有EXCEPTION子句的BEGIN塊來捕獲異常並使其從中恢復。見以下聲明形式:服務器
[ <> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ... ] THEN
handler_statements
WHEN condition [ OR condition ... ] THEN
handler_statements
END;
若是沒有錯誤發生,只有BEGIN塊中的statements會被正常執行,然而一旦這些語句中有任意一條發生錯誤,其後的語句都將被跳過,直接跳轉到 EXCEPTION塊的開始處。此時系統將搜索異常條件列表,尋找匹配該異常的第一個條件,若是找到匹配,則執行相應的 handler_statements,以後再執行END的下一條語句。若是沒有找到匹配,該錯誤就會被繼續向外拋出,其結果與沒有EXCEPTION子 句徹底等同。若是此時handler_statements中的語句發生新錯誤,它將不能被該EXCEPTION子句捕獲,而是繼續向外傳播,交由其外層 的EXCEPTION子句捕獲並處理。網絡
在PostgreSQL中能夠利用RAISE語句報告信息和拋出錯誤,其聲明形式爲:app
RAISE level 'format' [, expression [, ...]];
這裏包含的級別有DEBUG(向服務器日誌寫信息)、LOG(向服務器日誌寫信息,優先級更高)、INFO、NOTICE和WARNING(把信息寫到服務器日誌以及轉發到客戶端應用,優先級逐步升高)和EXCEPTION拋出一個錯誤(一般退出當前事務)。某個優先級別的信息是報告給客戶端仍是寫到服務器日誌,仍是兩個均有,是由log_min_messages和client_min_messages這兩個系統初始化參數控制的。
在format部分中,%表示爲佔位符,其實際值僅在RAISE命令執行時由後面的變量替換,若是要在format中表示%自身,可使用%%的形式表示,見以下示例:
RAISE NOTICE 'Calling cs_create_job(%)',v_job_id;--v_job_id變量的值將替換format中的%。
RAISE EXCEPTION 'Inexistent ID --> %',user_id;dom
簡單來講:
--拋出異常
RAISE EXCEPTION '你出問題了。該修修!';
--使用SQLERRM 來顯示錯誤信息。
RAISE EXCEPTION '(%)', SQLERRM;
見以下示例:
CREATE OR REPLACE FUNCTION GETEXCEPTION(v_phone text) RETURNS void AS
$$
BEGIN
IF v_phone = 'iphone' THEN
RAISE EXCEPTION '你出問題了。該修修!';
ELSIF v_phone = 'samsung' THEN
RAISE EXCEPTION '你會爆炸,離你遠點!';
else
RETURN;
END IF;
EXCEPTION
WHEN others THEN
RAISE EXCEPTION '(%)', SQLERRM;
END
$$ LANGUAGE PLPGSQL;
參考文獻:http://www.postgres.cn/docs/9.3/plpgsql-errors-and-messages.html
3、裏面有如何調用一個有out參數的存儲過程
原創文章,轉載請務必將下面這段話置於文章開頭處(保留超連接)。
本文轉發自Jason’s Blog,原文連接 http://www.jasongj.com/2015/12/27/SQL4_存儲過程_Store Procedure/
存儲過程簡介
什麼是存儲過程
百度百科是這麼描述存儲過程的:存儲過程(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接口調用。
總結下來存儲過程有以下特性:
- 存儲於數據庫服務器
- 一次編譯後可屢次調用
- 設計良好的數據庫應用程序極可能會用到它
- 由SQL和過程語句來定義
- 應用程序經過SQL接口來調用
使用存儲過程的優點及劣勢
首先看看使用存儲過程的優點
- 減小應用與數據庫服務器的通訊開銷,從而提高總體性能。筆者在項目中使用的存儲過程,少則幾十行,多則幾百行甚至上千行(假設一行10個字節,一千行即至關於10KB),若是不使用存儲過程而直接經過應用程序將相應SQL請求發送到數據庫服務器,會增大網絡通訊開銷。相反,使用存儲過程能下降該開銷,從而提高總體性能。尤爲在一些BI系統中,一個頁面每每要使用多個存儲過程,此時存儲過程下降網絡通訊開銷的優點很是明顯
- 一次編譯屢次調用,提升性能。存儲過程存於數據庫服務器中,第一次被調用後即被編譯,以後再調用時無需再次編譯,直接執行,提升了性能
- 同一套業務邏輯可被不一樣應用程序共用,減小了應用程序的開發複雜度,同時也保證了不一樣應用程序使用的一致性
- 保護數據庫元信息。若是應用程序直接使用SQL語句查詢數據庫,會將數據庫表結構暴露給應用程序,而使用存儲過程是應用程序並不知道數據庫表結構
- 更細粒度的數據庫權限管理。直接從表讀取數據時,對應用程序只能實現表級別的權限管理,而使用存儲過程是,可在存儲過程當中將應用程序無權訪問的數據屏蔽
- 將業務實現與應用程序解耦。當業務需求更新時,只需更改存儲過程的定義,而不須要更改應用程序
- 能夠經過其它語言並可及其它系統交互。好比可使用PL/Java與Kafka交互,將存儲過程的參數Push到Kafka或者將從Kafka獲取的數據做爲存儲過程的結果返回給調用方
固然,使用存儲過程也有它的劣勢
- 不便於調試。尤爲在作性能調優時,以PostgreSQL爲例,可以使用EXPLAIN ANALYZE檢查SQL查詢計劃,從而方便的進行性能調優。而使用存儲過程時,EXPLAIN ANALYZE沒法顯示其內部查詢計劃
- 不便於移植到其它數據庫。直接使用SQL時,SQL存於應用程序中,對大部分標準SQL而言,換用其它數據庫並不影響應用程序的使用。而使用存儲過程時,因爲不一樣數據庫的存儲過程定義方式不一樣,支持的語言及語法不一樣,移植成本較高
存儲過程在PostgreSQL中的使用
PostgreSQL支持的過程語言
PostgreSQL官方支持PL/pgSQL,PL/Tcl,PL/Perl和PL/Python這幾種過程語言。同時還支持一些第三方提供的過程語言,如PL/Java,PL/PHP,PL/Py,PL/R,PL/Ruby,PL/Scheme,PL/sh。
基於SQL的存儲過程定義
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;
調用方式
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 ];
中括號部分爲可選部分
- 塊中的每個declaration和每一條statement都由一個分號終止
- 塊支持嵌套,嵌套時子塊的END後面必須跟一個分號,最外層的塊END後可不跟分號
- BEGIN後面沒必要也不能跟分號
- END後跟的label名必須和塊開始時的標籤名一致
- 全部關鍵字都不區分大小寫。標識符被隱含地轉換成小寫字符,除非被雙引號包圍
- 聲明的變量在當前塊及其子塊中有效,子塊開始前可聲明並覆蓋(只在子塊內覆蓋)外部塊的同名變量
- 變量被子塊中聲明的變量覆蓋時,子塊能夠經過外部塊的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)
本例返回的每一行記錄是複合類型,該方法也可返回基本類型的結果集,即多行一列。
使用RETURN TABLE返回多行多列
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和存儲過程。實際上PostgreSQL建立存儲與建立UDF的方式同樣,並無專用於建立存儲過程的語法,如CREATE PRECEDURE。在PostgreSQL官方文檔中也暫未找到這兩者的區別。卻是從一些資料中找對了它們的對比,以下表如示,僅供參考。
多態SQL函數
SQL函數能夠聲明爲接受多態類型(anyelement和anyarray)的參數或返回多態類型的返回值。
函數重載(Overwrite)
在PostgreSQL中,多個函數可共用同一個函數名,但它們的參數必須得不一樣。這一規則與面嚮對象語言(好比Java)中的函數重載相似。也正因如此,在PostgreSQL刪除函數時,必須指定其參數列表,如:
1
|
DROP FUNCTION get_array(anyelement, anyelement);
|
另外,在實際項目中,常常會用到CREATE OR REPLACE FUNCTION去替換已有的函數實現。若是同名函數已存在,但輸入參數列表不一樣,會建立同名的函數,也即重載。若是同名函數已存在,且輸入輸出參數列表均相同,則替換。若是已有的函數輸入參數列表相同,但輸出參數列表不一樣,則會報錯,並提示須要先DROP已有的函數定義。