原创文章,转载请务必将下面这段话置于文章开头处(保留超连接)。
本文转发自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已有的函数定义。