目录

  • 一、存储过程与存储函数的定义
  • 二、创建 / 执行存储过程所需的权限
    • 1、resource权限
    • 2、create、execute procedure权限
  • 三、创建 / 执行存储过程
  • 四、变量与参数
    • 1、变量
      • 1) 标量类型
      • 2) 复合变量类型
        • a) 复合记录类型
        • b) 复合表类型(关联数组)
        • c) 变长数组类型
      • 3) 参照类型
    • 2、参数
      • 1) in
      • 2) out
      • 3) in out
      • 4) 设置参数默认值
      • 5) 系统参数
  • 附录:

一、存储过程与存储函数的定义

所谓的存储过程(Stored Procedure),即是指数据库中的用于完成某一功能的一组SQL语句,这组语句生成后就会被数据库编译并保存,与在控制台中直接调用语句比起来,存储过程有着更高的效率和安全性。
       存储函数可以看作是特殊的存储过程,存储函数被强制要求使用return语句返回函数值,而存储过程不需要。尽管就目前而言,存储过程可以借助in和out参数完成存储函数所能承担的工作,但为了兼容旧版本,Oracle仍旧保留了存储函数的相关内容[1]。本文中只讨论存储过程。

在代码中,所有被 “{ }” 包裹起来的表示必填项,所有被 “[ ]” 包裹起来的均为选填项。比如语句“create [ or replace ]”中,[ or replace ]表示用户可以选择性输入“or replace”字段,替换我们已经创建的存储过程。
在本文的演示中,我会尽可能用清晰明了的英文短语表述变量或过程的含义。我使用的环境是 PL/SQL Developer 13.0 和 Oracle 11g,相关教程各位可以在网络上找到。

二、创建 / 执行存储过程所需的权限

创建存储过程需要我们的账户至少要拥有 CREATE 和 EXECUTE 存储过程的权限。该步骤为可选步骤,使用管理员账号学习Oracle语法的同志可以跳过。下面有两种给予用户相应权限的方式 [2]

1、resource权限

开发的时候,我们可以直接授予开发人员以管理员权限(dba)或是开发者权限(resource),给予相关人员最多的权限内容,让他们能做更多的事情(当然开发完成后就得反过来,给予外部人员最少的权限以降低安全性问题发生的风险):

grant resource to [ user_name ];
-- 或者是 grant dba to [ user_name ];

2、create、execute procedure权限

从我个人看法而言,我们应该尽可能的只给予用户工作所需的最低权限,用于确保系统的安全性。比如开发人员只需要做存储过程设计,那我就只给他相关的权限:

grant create any procedure to [ user_name ];
grant execute any procedure to [ user_name ];

通过以上这种方法,用户就可以获得创建和执行存储过程的权利。在这里有个小插曲,在使用PL\SQL软件操作Oracle的时候,你还需要给用户创建会话的权限,否则可能会触发异常ORA-01045:

grant create session to [ user_name ];

三、创建 / 执行存储过程

以下是一个创建无参存储过程的语法块:

create [ or replace ] procedure procedure_name
as
begin-- PL-SQL blocks
end;
/
  • 运用此语法我们创建了一个名为“procedure_name”的存储过程。首行的 replace 表示替换,对于Oracle的存储过程而言,我们只能创建 (create)、删除 (drop) 或替换 (replace) 它,没有类似于SQL Server的修改 (Alter) 操作(但你可以在PL/SQL工具中对它进行重新编译以达到修改的目的)。
  • 第二行的 as 与SQL Server中的Declare作用相似,但as不可省略,是固定语法,之后讲到参数的时候,我们就要在as和begin之间插入参数列表。
  • beginend 表示PL-SQL语句块的开始和结束,所有需要执行的语句都写在此处。end结束后,还需跟上 “/” 表示执行上述语句块,创建这个存储过程。

明白语法之后,我们可以自己动手试一下。下面的这段代码是一个简单的例子,它创建了一个叫做 “examsp_query_cs” 的存储过程,每次调用的时候它都会在屏幕上打印出一句“Hello World”:

create or replace procedure examsp_query_cs as
begindbms_output.put_line('Hello World');
end examsp_query_cs;
/

如果是在SQLPLUS中执行该语句段,我们应该要能看见屏幕上显示“存储过程已创建”(Procedure created)的提示,这时候我们还需要再执行一个语句set serveroutput on;设置 serveroutput 的值为on可以让dbms_output输出的内容显示在屏幕上[3],之后我们再调用存储过程,就可以看到效果了。执行存储过程的方式有以下几种:

  -- 通过 exec 执行,只能在 SQLPLUS 中调用,过程名后面跟上参数列表,用逗号分隔exec proc_name [ param_1, param_2... ];-- 通过 call 执行,可以在任意场合使用,无论有没有参数都要写括号,在括号中写入参数call proc_name([ param_1, param_2... ]);-- 通过 begin - end 句段调用,在PL/SQL Developer中,建议使用此方式beginproc_name([ param_1, param_2... ]);end;

下图是我们执行存储过程的结果

关于Oracle的表命名规范可以参考下面这篇文章,尽管严格来说,表命名并没有太多的强制要求,但规范的命名习惯有利于我们日后的维护:Oracle命名规范。
dbms_output有很多的用途,想知道其细节的话可查阅本文末的相关链接。

四、变量与参数

在设计存储过程的时候,我们必然会用到变量与参数,它们可以扩展代码的灵活性,让我们做到更多事情。在Oracle中,参数与变量有着截然不同的语法。
       开讲之前有个小细节我想和大家提一下,我相信诸位在查找相关资料的时候一定有看到“as”和“is”这两种不同的写法,严格来说在存储过程中二者没有什么显著的差别,它们是同义词,但使用as的情况居多。值得注意的是,在创建视图的时候我们只能用as,而在声明游标的时候只能用is。[4]

1、变量

首先让我们看看声明变量的语法[5]

create [ or replace ] procedure procedure_name
as
[ var_1 var_type (var_size); ]
begin-- PL-SQL blocks
end;
/

在这里,var_1表示变量名,var_type表示变量的类型,var_size表示取值范围(变量大小),当我们要声明一个变量的时候,这三个元素缺一不可。Oracle的变量命名遵循系统命名规则,在此我们不做赘述,但变量类型则有多种不同的分类:标量类型复合变量类型参照类型大型数据对象

1) 标量类型

标量类型既包括了系统中的标准数据类型,诸如varcharnumber等;亦包括了一些比较少用的类型,比如BINARY_INTEGERboolean等。这些类型使用广泛、声明简单,是变量类型中的基础。下面这个例子会创建一个名为“proc_findGirl”的存储过程,它会从“Employee”表中找到一个ID为6的雇员:

create or replace procedure proc_findGirl
asgirl_id number(4);girl_name varchar(20);girl_sex varchar(10);girl_salary number;
beginselect emp_id, emp_name, emp_sex, emp_salary into girl_id, girl_name, girl_sex, girl_salaryfrom employeewhere emp_id = '6';dbms_output.put_line('name: ' || girl_name || ' id: ' || girl_id || ' sex: ' || girl_sex);
end proc_findGirl;


       这个例子仅仅只是用来演示变量效果的,实际情况中我们肯定不会干这种在存储过程中只塞一个select语句的蠢事。上图即是执行效果,在存储过程中调用select语句必须使用变量接收查询结果,否则会出现异常。
       还有一种变量类型叫做 “%TYPE[6] ,你可以把它看做是一种动态数据类型,它由一个已经定义了的变量调用,并返回该变量的类型。比如说:

  v_msg varchar(20);v_msg_back v_msg%TYPE;-- 在这里,v_msg 和 v_msg_back 的类型都是 varchar(20)

这很OOP,尤其在我们使用参数的时候,我们很难确定输入的参数类型;或者是通过表格给变量赋值的时候,如果字段类型变了,我们还得跟着修改所有的过程。用一个%TYPE就可以解决这些问题,提高了代码的复用率和可靠性。在接下来的例子中,我们还会看到更多使用%TYPE的情况出现。
       与之相似的还有%ROWTYPE,顾名思义,它能保存一个表格中所有列的类型,你可以直接将它看做是一条行记录:

create or replace procedure proc_findGirl
asgirl employee%ROWTYPE;
beginselect emp_id, emp_name, emp_sex, emp_salary into girlfrom employeewhere emp_id = '6';dbms_output.put_line('name: ' || girl.emp_name || ' id: ' || girl.emp_id || ' sex: ' || girl.emp_sex);
end proc_findGirl;
-- 效果与前者一致

2) 复合变量类型

复合变量类型要比标量类型更加复杂,在这里我只做一些简单的解释。它包含以下几种类型:

a) 复合记录类型

就我个人而言我觉得它无论是看起来还是用起来都很像java里的结构体。在这里,我们会声明一种名为record类型的变量,该变量内含有多个标量类型的变量,随后我们声明该record类型的实例化“对象”[7]

  type record_type_name is record (var_name var_type(var_size)[, var_name var_type(var_size)]);var_record_type record_type_name;

该语法声明了一个叫做 “record_type_name” 的记录类型,里面含有复数个变量(单个变量没有声明成记录的必要)。随后,我们声明了一个名为 “var_record_type” 的 “record_type_name” 类型的变量。下面这个例子就是一种应用,如我们前面所说,使用%TYPE可以给我们很大的帮助:

-- 利用记录类型可以将返回值整合成一个有实际意义的对象
create or replace procedure proc_findGirl
astype emp_record_type is record (  r_name   employee.emp_name%TYPE , r_salary employee.emp_salary%TYPE);  employee_record emp_record_type;
beginselect emp_name, emp_salary into employee_record from employeewhere emp_id = '7';dbms_output.put_line('name: ' || employee_record.r_name || ' salary: ' || employee_record.r_salary);
end proc_findGirl;

上述例子使用

b) 复合表类型(关联数组)

索引表(关联数组)是一种更为复杂的记录类型,尽管在声明的时候我们会用到 “is table of” ,但本质上来讲它更接近数组,索引表通过指定类型的索引确定其元素所在位置。下面是声明索引表的语法:

  type table_type_name is table of type_nameindex by index_type;var_table table_type_name;

在这里,table_type_name 即是我们所声明的索引表的名字;type_name 是索引号的类型,它可以是标量类型,也可以是我们自己声明的记录类型,声明索引号的时,除非使用的是有固定大小或有默认大小的类型(如numberBINARY_INTEGER),否则我们必须声明其大小(如varchar2(20))。下面是一个示例,我们声明了一个叫做 v_table_emp 的索引表,其索引号类型为BINARY_INTEGER,我们将查到的一条记录保存到了表中下标(索引号)为0的位置上:

create or replace procedure proc_findGirl
astype emp_record_type is record (  r_name employee.emp_name%TYPE , r_salary employee.emp_salary%TYPE);  type table_employee_record is table of emp_record_typeindex by binary_integer;v_table_emp table_employee_record;
beginselect emp_name, emp_salary into v_table_emp(0)from employeewhere emp_id = '1';dbms_output.put_line('name: ' || v_table_emp(0).r_name || ' salary: ' || v_table_emp(0).r_salary);
end proc_findGirl;

在Oracle中,索引表的下标可以是负数,所以在上面这个例子中你也可以把信息保存在-1这个位置上。索引表保存的实际上是数据的逻辑地址,这与数组的保存原理相似。

c) 变长数组类型

变长数组(varray,即variable array)是一种可以自行设置长度的数组,它能保存一系列相同类型的元素。声明变长数组的语法如下:

  type var_name is varray(var_size) of type_name[(type_size)] [NOT NULL];

其中,var_name 是变长数组名;var_size是数组的最大长度,和索引表不同的是,数组的下标从1开始,没有负数;type_name 是类型名,和索引表一样,没有固定大小和默认大小的类型需要声明大小(type_size)。如果不允许元素取空,你可以在末尾加上 “NOT NULL” 字段[8]。下面这个例子演示了数组的赋值和通过下标输出元素值:

DECLAREtype exam_array_type is varray(4) of varchar(5);exam_array exam_array_type;
BEGINexam_array := exam_array_type('Tom', 'Sam', 'Lily', 'Jonas');dbms_output.put_line('[1] ' || exam_array(1));dbms_output.put_line('[2] ' || exam_array(2));dbms_output.put_line('[3] ' || exam_array(3));dbms_output.put_line('[4] ' || exam_array(4));
END;

输出结果如下:

除了直接使用下标读取之外,我们也可以通过循环遍历数组输出:

DECLAREtype exam_array_type is varray(4) of varchar(5);exam_array exam_array_type;
BEGINexam_array := exam_array_type('Tom', 'Sam', 'Lily', 'Jonas');dbms_output.put_line('loop start..');-- 下面是一个简单的for循环,关于循环的内容我将会 “循环分支控制” 中介绍for i in 1..exam_array.count loopdbms_output.put_line('[' || i || '] ' || exam_array(i));end loop;dbms_output.put_line('loop end..');
END;

3) 参照类型

参照类型主要是用在游标上,我会在 “游标” 里具体介绍这部分内容。

2、参数

参数是编写存储过程中的另一个重要组成元素。在前面的演示中,我们经常能看见这个方法被调用:dbms_output.put_line(),细心的同志们在PL/SQL Developer中敲出来的时候就会注意到,put_line其实也是一个存储过程,dbms_output是它所属的包:

       这就意味着,我们先前在括号中输入的其实就是参数,在这里我们不去研究这个过程的内部细节,我们先看看参数分别有哪些类型。

1) in

对于一个参数而言,如果它被标记成 “in” ,则意味着它是一个输入参数,在调用存储过程的时候,我们必须输入符合要求的参数,否则系统就会抛出异常:

create or replace procedure procedure_name
( param_name IN param_type [ , param_name IN param_type ] )
as
begin-- PL-SQL blocks
end;

在这段代码中我们定义了一个类型为 param_type 的输入参数 param_name ,这很好理解,直接通过参数名就可以调用参数了。但是请注意:

  • 输入参数不能被赋值,给输入参数赋值会引发异常
  • 输入参数只能用于给存储过程传递外部的值,不能执行其他功能
  • 在输入IN参数的类型的时候,不需要给参数指定范围
    (比如 param IN varchar2(20),这就是种错误的写法,应写成 param IN varchar2

2) out

OUT参数即是输出参数,被标记为OUT的参数在程序段执行完毕后会将该参数的最终值赋给对应的“实参变量”[9]。注意:

  • 输出参数可以被 “:=” 符号赋值
  • 在存储过程中调用输出参数的时候会忽略传递进来的值
  • 只有变量可以被传递进程序段中做输出参数的
  • 和IN参数一样,在输入OUT参数的类型的时候,不需要指定范围
-- 创建存储过程,定义输入输出变量
create or replace procedure proc_query_by_id (query_id in employee.emp_id%type, query_name out employee.emp_name%type
)
as
beginselect emp_name into query_namefrom employeewhere emp_id = query_id;
end proc_query_by_id;
/-- 定义变量接收输出参数值并输出
declare emp_name varchar2(20);
beginproc_query_by_id(1, emp_name);dbms_output.put_line(emp_name);
end;
/

3) in out

将IN参数与OUT参数合二为一的产物,可以赋值也可以返回值,但必须得是实参变量调用。

-- 创建存储过程,使传入的数字型参数加1
create or replace procedure proc_add ( param in out number
)
as
beginparam := param + 1;
end;
/-- 执行存储过程并输出执行前后的结果
declare num number := 1;
begindbms_output.put_line('num = ' || num);proc_add(num);dbms_output.put_line('num = ' || num);
end;
/

4) 设置参数默认值

编写存储过程的时候,我们可以通过 “default” 字段声明参数的初始值。注意:

  • 只有IN参数可以声明初始值,OUT参数和IN OUT参数均不可声明
  • 需要声明初始值的参数最好放在参数列表的最后

在这里我要解释下第二点,对于Oracle而言,你可以给任何位置上的IN参数添加初始值,但能不能用就是另外一回事儿了。举个例子,下方是一段执行程序,它调用了 proc_add 这个过程,使传入的参数加1并将计算结果传递出来:

declare num number := 5;
begindbms_output.put_line('num = ' || num);proc_add(num);dbms_output.put_line('num = ' || num);
end;

如果我们的存储过程是像下面这样写的,那这段代码就没问题:

-- Plan A
create or replace procedure proc_add( param_out out number, param_in in number default 0
)
as
beginparam_out := param_in + 1;
end;

但如果写成这样,系统在执行的时候就会抛出异常ORA-06550(参数个数或类型出错):

-- Plan B
create or replace procedure proc_add( param_in in number default 0, param_out out number
)
as
beginparam_out := param_in + 1;
end;

这两段代码都可以通过编译,因为从语法上来讲它们没有任何问题,但是在执行的时候,二者的差异就出来了:在语句 proc_add(num); 中,我只调用了一个参数,它必定对应着参数列表中的第一个。所以对于A而言,这个参数是OUT型参数 param_out ,没有给定参数的IN型参数 param_in 被赋予默认值0,输出的计算结果就是1;而对于B而言,这个参数对应的是 param_in ,有了指定的值之后默认值就数去了作用,没有被赋值的 param_out 找不到指定参数,抛出了异常。
       所以给参数赋默认值的最好考虑下调用情况,尽管安全性堪忧,但或许可以借助default的灵活性实现一些有趣的事情。

5) 系统参数

在Oracle中有许多已经定义好了的系统参数,对这块有兴趣的同志可以看看这篇文章:Oracle参数查看方法小结

附录:

文中的示例和部分解释参考了网上搜索到的文章,我衷心感谢这些优秀的创作者们所做的贡献,没有他们的资料我将寸步难行。各位如果感兴趣,可以点击下方的链接查看他们的文章:
[1] Oracle存储过程与存储函数-入门
[2] Oracle中connect,resource角色权限
[3] Oracle系统包——dbms_output用法
[4] Oracle中存储过程和函数中IS和AS的区别
[5] (转)Oracle存储过程详解(一)
[6] Oracle存储过程----变量的介绍及使用(PL/SQL)
[7] Oracle参数变量类型
[8] ORACLE中RECORD、VARRAY、TABLE的使用详解
[9] Oracle存储过程in、out、in out 模式参数

Oracle 存储过程详解(上)相关推荐

  1. ORACLE存储过程详解----我看过最详细的存储过程

    1.定义 所谓存储过程(Stored Procedure),就是一组用于完成特定数据库功能的SQL语句集,该SQL语句集经过编译后存储在数据库系统中.在使用时候,用户通过指定已经定义的存储过程名字并给 ...

  2. java调用oracle存储过程_java调用oracle存储过程详解

    之前给大家介绍了java代码调用存储过程,下面要给大家介绍的就是java当中调用oracle存储过程,一起来看看吧. 首先来看一下项目结构: 在数据库创建存储过程的脚本,假如,使用的是本地的oracl ...

  3. oracle存储过程详解--游标 实现增、删、改、查的

    注:以下是转来的内容,但是设计PLSQL代码,原文有问题,所以PLSQL代码我都验证修改了.测试需要在scott/tiger下进行,对于没有的表,需要自行创建,表复制的语句为 CREATE TABLE ...

  4. Oracle 存储过程详解(procedure)

    文章目录 1 概述 2 语法 2.1 创建 2.2 调用 2.3 修改.删除 3 Debug 1 概述 1. 存储过程是什么:事先经过编译并存储在数据库中的一套 sql 语句2. 存储过程的优点(1) ...

  5. 在oracle中使用存储过程,如何在ORACLE中使用JAVA存储过程(详解)

    如何在Oracle中使用Java存储过程 (详解) 一.如何缔造java存储过程? 通常有三种步骤来缔造java存储过程. 1.使用oracle的sql语句来缔造: e.g.使用create or r ...

  6. Oracle ASM 详解 收藏

    Oracle ASM 详解 ASM:Automatic Storage Management, 是Oracle 主推的一种面向Oracle的存储解决方案, ASM 和 RDBMS 非常相似,ASM 也 ...

  7. MySQL存储过程详解 mysql 存储过程

    mysql存储过程详解 1.      存储过程简介   我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的S ...

  8. mysql存储过程详解[转]

    mysql存储过程详解[转] 1.      存储过程简介   我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功 ...

  9. oracle分区表编程,Oracle分区表详解

    当前位置:我的异常网» 编程 » Oracle分区表详解 Oracle分区表详解 www.myexceptions.net  网友分享于:2013-10-28  浏览:25次 Oracle分区表详解 ...

最新文章

  1. Android之Android触摸事件传递机制
  2. 小码哥iOS学习笔记第八天: block的底层结构
  3. [转载] C#面向对象设计模式纵横谈——10. Decorator装饰模式
  4. 详谈为何不要使用Windows的notepad编写shell
  5. java 工程新建ivy文件_Hadoop学习之路(八)在eclispe上搭建Hadoop开发环境
  6. 力扣 数组中的第K个最大元素
  7. 【转】C#+csgl库进行OpenGL编程
  8. ajax live search,AJAX Live Search
  9. HCNP学习笔记之OSPF协议原理及配置9-基础知识特殊区域
  10. securecrt批量登录linux,SecureCRT批量配置使用会话key
  11. 在Centos7上配置docker运行DotNetCore项目
  12. mysql exporter怎么配置_prometheus mysqld_exporter监控mysql-5.7
  13. 不知为不知--信息论和最大熵原则
  14. 【Scratch案例实操】Scratch小狗散步 scratch编程案例教学 scratch创意编程 少儿编程教案
  15. MySQL源码调试入门
  16. paypal android sdk,Android Paypal SDK错误:商家不接受此类付款
  17. 英文地址翻译原则:先小后大。如**号**路**区,
  18. java 地图坐标转换_百度地图坐标和高德地图坐标转换代码 Java实现
  19. grunt从入门到自定义项目模板
  20. 14.2 Numpy实现逆傅里叶变换

热门文章

  1. 最强大脑《多米诺效应》
  2. android可拖拽九宫格,微信小程序实现九宫格图片拖拽
  3. 自由宣言-- I Have a Dream 马丁 路德 金
  4. python查询员工信息表
  5. 《娱乐至死》读书笔记(part3)--无知是可以补救的,但如果我们把无知当成知识,我们该怎么做呢?
  6. 浅谈Java类加载:ClassLoader
  7. 7.26 5 优化浪漫 恋爱中的经济学
  8. 基于 SPI 协议的0.96 寸OLED显示
  9. SSL 证书签发详细攻略
  10. 从零开始带你称为MySQL实战优化高手(儒猿技术窝)