热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PostgreSQL存储过程<转>

原创文章,转载请务必将下面这段话置于文章开头处(保留超链接)。本文转发自Jason’sBlog,原文链接http:www.jasongj.com20151227SQL4_存

原创文章,转载请务必将下面这段话置于文章开头处(保留超链接)。
本文转发自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是一个块结构语言。函数定义的所有文本都必须是一个块。一个块用下面的方法定义:

[ <]
[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已有的函数定义。


推荐阅读
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 网址:https:vue.docschina.orgv2guideforms.html表单input绑定基础用法可以通过使用v-model指令,在 ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
author-avatar
手机用户2602932623
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有