PostgreSQL存儲過程<轉>

原創文章,轉載請務必將下面這段話置於文章開頭處(保留超連接)。
本文轉發自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

  總結下來存儲過程有以下特性:緩存

  • 存儲於數據庫服務器
  • 一次編譯後可屢次調用
  • 設計良好的數據庫應用程序極可能會用到它
  • 由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。app

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

 調用方式 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 ];

中括號部分爲可選部分 

  • 塊中的每個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官方文檔中也暫未找到這兩者的區別。卻是從一些資料中找對了它們的對比,以下表如示,僅供參考。
UDF VS. Stored Precedure

多態SQL函數

  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)

     

函數重載(Overwrite)

  在PostgreSQL中,多個函數可共用同一個函數名,但它們的參數必須得不一樣。這一規則與面嚮對象語言(好比Java)中的函數重載相似。也正因如此,在PostgreSQL刪除函數時,必須指定其參數列表,如:

1
DROP FUNCTION get_array(anyelement, anyelement);

 

  另外,在實際項目中,常常會用到CREATE OR REPLACE FUNCTION去替換已有的函數實現。若是同名函數已存在,但輸入參數列表不一樣,會建立同名的函數,也即重載。若是同名函數已存在,且輸入輸出參數列表均相同,則替換。若是已有的函數輸入參數列表相同,但輸出參數列表不一樣,則會報錯,並提示須要先DROP已有的函數定義。

相關文章
相關標籤/搜索