过程用于执行特定的操作,当建立过程时,既可以指定输入参数(in),也可以指定输出参数(out)。通过在过程中使用输入参数,可以将数据传递到执行部分;通过使用输出参数,可以将执行部分的数据传递到应用环境。可以使用create procedure命令来建立过程。
调用过程有两种方法:
exec 过程名(参数值,..)
call 过程名(参数值,..)
创建存储过程基本语法:
create or replace procedure 过程名(变量 in 变量类型,..,变量 out 变量类型) is
//定义变量
begin
//执行语句;
end;
/
特别说明:or replace在创建存储过程中可带也可不带。带or replace是指在存储过程名字相同时将其覆盖。不带则无法覆盖。在使用or replace时要小心,建议不使用or replace对原存储过程进行覆盖。
过程创建举例:
--输入员工编号,新工资,修改员工的工资 create or replace procedure p4(v_in_emp in number,v_in_new_sal in number) is v_ename varchar2(30); begin select ename into v_ename from emp2 where empno = v_in_emp; update emp2 set sal = v_in_new_sal where empno = v_in_emp; exception when no_data_found then dbms_output.put_line('员工编号不存在'); end; /
特别说明:当编写过程出现错误时,查看具体错误信息。输入show error;
Java调用过程 /** * 调用过程 */ package com.oracle.db; import java.sql.*; public class db4 { // 调用p4过程 public static void main(String[] args) { Connection ct = null; CallableStatement cs = null; try { // 加载驱动 Class.forName("oracle.jdbc.driver.OracleDriver"); // 得到连接 ct = DriverManager.getConnection( "jdbc:oracle:thin:@127.0.0.1:1521:Switch", "scott", "123456"); // 创建CallableStatement cs = ct.prepareCall("{call p4(?,?)}"); // 给?赋值 cs.setInt(1, 7369); cs.setFloat(2, 1000.5f); // 执行 cs.execute(); } catch (Exception e) { e.printStackTrace(); } finally { try { // 关闭资源 if (cs != null) { cs.close(); } if (ct != null) { ct.close(); } } catch (Exception e2) { e2.printStackTrace(); } } } }
扩展SQLHelper类,使之实现无返回参数的存储过程的方法
package com.oracle.db; import java.io.FileInputStream; import java.sql.*; import java.util.Properties; public class SQLHelper { // 定义变量 private static Connection ct = null; private static PreparedStatement ps = null; private static ResultSet rs = null; private static CallableStatement cs = null; // 连接数据库的用户名,密码,url,驱动 // 说明:在实际开发中,我们往往把这些变量写到一个外部文件中 // 当程序启动时,我们读入这些配置信息。java.util.Properites private static String username; private static String password; private static String driver; private static String url; // 使用静态块加载驱动(驱动只需要加载一次) static { // 使用Properties类,来读取配置文件 Properties pp = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream("dbinfo.properties"); // 让pp与dbinfo.properties文件关联起来 pp.load(fis); // 获取dbinfo.properties文件内信息 username = (String) pp.getProperty("username"); password = (String) pp.getProperty("password"); driver = (String) pp.getProperty("driver"); url = (String) pp.getProperty("url"); // 获得驱动 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fis != null) { fis.close(); } } catch (Exception e) { e.printStackTrace(); } fis = null; } } // 调用存储过程的方法 public static void executeProcedure(String sql, String[] parameters) { try { ct = DriverManager.getConnection(url,username,password); cs = ct.prepareCall(sql); if (parameters != null && !"".equals(parameters)) { for(int i = 0; i < parameters.length; i++){ cs.setString(i + 1, parameters[i]); } } //执行 cs.execute(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } finally { close(rs, cs, ct); } } // 统一的cud操作 public static void executeUpdate(String sql, String[] parameters) { try { ct = DriverManager.getConnection(url, username, password); ps = ct.prepareStatement(sql); if (parameters != null) { for (int i = 0; i < parameters.length; i++) { ps.setString(i + 1, parameters[i]); } } // 执行 ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } finally { close(rs, ps, ct); } } // 写一个方法,完成查询任务 // sql表示要执行的sql语句 // sql select * from emp where ename=? public static ResultSet executeQuery(String sql, String[] parameters) { try { // 根据实际情况我们对sql语句?赋值 // 得到连接 ct = DriverManager.getConnection(url, username, password); // 创建ps对象,得到sql语句对象 ps = ct.prepareStatement(sql); // 如果parameters不为null,才赋值 if (parameters != null) { for (int i = 0; i < parameters.length; i++) { ps.setString(i + 1, parameters[i]); } } rs = ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); // 抛出运行异常 throw new RuntimeException(e.getMessage()); } finally { // close(rs,ps,ct); } return rs; } // 把关闭资源写成函数 public static void close(ResultSet rs, Statement ps, Connection ct) { // 关闭资源 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } rs = null; } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } ps = null; } if (ct != null) { try { ct.close(); } catch (SQLException e) { e.printStackTrace(); } ct = null; } } public static Connection getCt() { return ct; } public static PreparedStatement getPs() { return ps; } }
dbinfo.propeties中的属性
driver = oracle.jdbc.driver.OracleDriver
url = jdbc:oracle:thin:@127.0.0.1:1521:Switch
username = scott
password =123456
dbinfo.propeties中的属性 driver = oracle.jdbc.driver.OracleDriver url = jdbc:oracle:thin:@127.0.0.1:1521:Switch username = scott password =123456
函数用于返回特定的数据,当建立函数时,在函数头部必须包含return子句,而在函数体内必须包含return语句返回的数据。我们可以使用create function来建立函数。
create function 函数名(参数1,...)
return 数据类型 is
定义变量;
begin
执行语句;
end;
/
select 函数名(参数,...) from dual;
实例:
--用函数接收用户名,返回用户年薪 create or replace function f1(v_in_ename varchar2) return number is v_annual_sal number; begin select (sal+nvl(comm,0))*13 into v_annual_sal from emp2 where ename = v_in_ename; return v_annual_sal; end; /
1、函数必须有返回值,而过程可以没有;
2、函数和过程在java中调用的方式不一样;
java中调用oracle函数可以在select语句中直接调用,如:select 自定义的函数名(参数) from 表;
过程则是使用CallableStatement完成调用。
/** * 使用SQLHelper调用函数 */ package com.oracle.db; import java.sql.*; public class db6 { public static void main(String[] args) { String sql = "select f1('SMITH') from dual"; ResultSet rs = SQLHelper.executeQuery(sql, null); try { if(rs.next()){ System.out.println(rs.getDouble(1)); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
包用于在逻辑上组合过程和函数等对象,它由包的规范和包体两部分组成。
建包基本语法:
create [or replace] package 包名 is
procedure 过程名(变量名 变量类型,...);
function 函数名(变量名 变量类型,...) return 数据类型;
end;
/
包的规范只包含了过程和函数的说明,但是没有过程和函数的实现代码。包体用于实现包规范的过程和函数。
实例:
--声明一个包 create or replace package pack1 is --声明一个过程用于输入员工姓名,新工资,修改员工的工资 procedure p1(v_in_ename in varchar2,v_in_newsal number); --声明一个函数用于接收用户名,返回用户年薪 function f1(v_in_ename in varchar2) return number; end; /
建立包体基本语法:
create or replace package body 包名 is
procedure 过程名(变量名 变量类型,...) is
--声明变量;
begin
--执行语句;
exception
when 异常名 then
--异常处理;
end;
function 函数名(变量名 变量类型,...)
return 数据类型 is
--声明变量;
begin
--执行语句;
end;
end;
/
实例:
--建立包体 create or replace package body pack1 is --实现一个过程用于输入员工姓名,新工资,修改员工的工资 procedure p1(v_in_ename in varchar2,v_in_newsal number) is begin update emp2 set sal = v_in_newsal where ename = v_in_ename; end; --实现一个函数用于接收用户名,返回用户年薪 function f1(v_in_ename in varchar2) return number is v_annual_sal number; begin select (sal+nvl(comm,0))*13 into v_annual_sal from emp2 where ename = v_in_ename; return v_annual_sal; end; end; /
细节说明:
1、包体中要实现的函数或过程,应当在包规范中声明;
2、在调用包中的某个函数或过程的时候,需要使用对应的方法才可以调用。
3、如何调用包的过程或函数
当调用包的过程或是函数时,在过程和函数前需要带有包名,如果要访问其它方案的包,还需要在包名前加方案名。
调用基本方法:
exec 方案名.包名.过程名(参数,...);
call 方案名.包名.函数名(参数,...);
也可以直接用select 方案名.包名.函数名(参数,...) from dual;
/** * 使用SQLHelper调用包中函数 */ package com.oracle.db; import java.sql.*; public class db7 { public static void main(String[] args) { String sql = "select scott.pack1.f1('SMITH') from dual"; ResultSet rs = SQLHelper.executeQuery(sql, null); try { if(rs.next()){ System.out.println(rs.getDouble(1)); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
触发器是指隐含的执行的存储过程。当定义触发器时,必须要指定触发的事件和触发的操作,常用的触发事件包括insert/update/delete语句,而触发操作实际就是一个pl/sql块。可以使用create trigger 来建立触发器。
PS:触发器也是用来维护表的完整性的,触发器将在之后单独写成一篇博客。
在编写pl/sql程序时,可以定义变量和常量;在pl/sql程序中包括有:
1、标量类型(scalar)
2、复合类型(composite)
3、参照类型(reference)
4、lob(large object)
在编写pl/sql块时,如果要使用变量,需在定义部分定义变量。
pl/sql中定义变量和常量的语法如下:
identifier [constant] datatype [not null] [:=|default expr]
说明:
identifier:名称
constant:指定常量。需要指定它的初始值,且其值是不能改变的。
datatype:数据类型
not null:指定变量值不能为null
:=给变量或是常量指定初始值
default:用于指定初始值
expr:指定初始值的pl/sql表达式,可是文本值、其它变量、函数等。
标量定义的案例:
1、定义一个变长字符串
v_ename varchar2(10);
2、定义一个小数范围-9999.99~9999.99
v_sal number(6,2);
3、定义一个小数并给一个初始值为5.4 :=是pl/sql的赋值号
v_sal2 number(6,2):=5.4
4、定义一个日期类型的数据
v_hiredate date;
5、定义一个布尔变量,不能为空,初始值为false
v_valid boolean not null default false;
特别说明:pl/sql在定义一个变量的时候,如果要赋初值,则需要使用:=,如果只是=则是用于判断两个值是否相等。
实例:
--编写过程,以输入的员工号,显示员工的姓名、工资、个人所得税(税率为0.03) create or replace procedure p5(v_in_empno in number) is v_ename varchar2(30); v_sal number; v_tax_rate number(3,2) := 0.03; v_tax number; begin select ename,sal into v_ename,v_sal from emp2 where empno = v_in_empno; v_tax := v_sal * v_tax_rate; dbms_output.put_line('员工的姓名为:' || v_ename || ' 员工的工资为:' || v_sal || ' 员工的个人所得税为: ' || v_tax); end; /
为了降低pl/sql程序的维护工作量,可以使用%type属性定义变量,这样它会按照数据库列来确定你定义的变量的类型和长度。
%type类型使用的基本语法:
标识符名 表名.列名%type;
--优化 create or replace procedure p6(v_in_empno in number) is v_ename emp2.ename%type; --使用emp2中ename的类型定义 v_sal emp2.sal%type; --使用emp2中sal的类型定义 v_tax_rate emp2.sal%type := 0.03; --使用emp2中sal的类型定义 v_tax emp2.sal%type; --使用emp2中sal的类型定义 begin select ename,sal into v_ename,v_sal from emp2 where empno = v_in_empno; v_tax := v_sal * v_tax_rate; dbms_output.put_line('员工的姓名为:' || v_ename || ' 员工的工资为:' || v_sal || ' 员工的个人所得税为: ' || v_tax); end; /
用于存放多个值的变量。常用的包括:1、pl/sql记录;2、pl/sql表
类似与高级语言中的结构体,需要注意的是,当引用pl/sql记录成员时,必须要加记录变量作为前缀(记录变量.记录成员)如下:
复合变量定义基本语法:
type 自定义的pl/sql记录名 is record(
变量名 变量类型,
变量名 变量类型
);
复合变量基本使用语法:
变量名 自定义的pl/sql记录名;
--复合类型 --编写过程,接收员工编号,显示该员工的名字、薪水、工作岗位(用pl/sql记录实现) create or replace procedure p7(v_in_empno in emp2.ename%type) is --定义记录类型 type emps_record is record ( v_ename emp2.ename%type, v_sal emp2.sal%type, v_job emp2.job%type ); --定义emps_record记录类型的变量 emps emps_record; begin select ename,sal,job into emps.v_ename,emps.v_sal,emps.v_job from emp2 where empno = v_in_empno; dbms_output.put_line('员工的姓名为:' || emps.v_ename || ' 员工的工资为:' || emps.v_sal || ' 员工的工作为: ' || emps.v_job); end; /
相当于高级语言中的数组,但是需要注意的是在高级语言中数组的下标不能为负数,而pl/sql是可以为负数的,并且表元素的下标没有限制。(可以理解为是oracle下的数组)实例如下:
复合类型pl/sql表的基本语法:
type 自定义的pl/sql表名 is table of 对应表.列名%type
index by binary_integer;
declare type table_type is table of emp2.ename%type index by binary_integer; stable table_type;--定义一个变量 begin select ename into stable(-1) from emp where empno=7788; dbms_output.put_line('员工名:'||stable(-1)); end;
说明:
table_type 是pl/sql表类型
emp.ename%type 指定了表的元素的类型和长度
stable 为pl/sql表变量
stable(0) 则表示下标为0的元素
参照变量是指用于存放数值指针的变量,通过使用参照变量,可以使得应用程序共享相同对象,从而降低占用的空间。在编写pl/sql程序时,可以使用游标变量(ref cursor)和对象类型变量(ref obj_type)两种参照变量类型。
通过游标可以取得返回结果集(这个结果集,往往是select语句的结果)的任何一行数据,从而提供共享的效率。
游标(ref cursor)使用
定义游标基本语法:
type 自定义游标名 is ref cursor;
变量名 自定义游标名;
打开游标基本语法:
open 游标变量 for select 语句;
取出当前游标指向的行基本语法:
fetch 游标变量 into 其它变量;
判断游标是否指向记录最后基本语法:
游标变量%notfound
使用游标时,当定义游标时不需要指定相应的select语句,但是当使用游标时,(open时)需要指定select语句,这样一个游标就写一个select语句结合了。
--参照变量 --编写过程,输入部门号,显示该部门所有员工的姓名和工资 create or replace procedure p8(v_in_deptno in dept2.deptno%type) is --定义游标变量类型 type emp_cursor is ref cursor; --定义游标变量 v_emp_cursor emp_cursor; v_ename emp2.ename%type; v_sal emp2.sal%type; begin --打开游标 open v_emp_cursor for select ename,sal from emp2 where deptno = v_in_deptno; --用循环语句取出游标中指向的每行数据 loop --取出每行数据 fetch v_emp_cursor into v_ename,v_sal; --判断游标退出 exit when v_emp_cursor%notfound; --输出每行数据 dbms_output.put_line('员工的姓名为:' || v_ename || ' 员工的工资为:' || v_sal); end loop; --关闭游标 close v_emp_cursor; end; /
--参照变量 --编写过程,输入部门号,显示该部门所有员工的姓名和工资并且如果某个员工的工资低于2000则增加100 create or replace procedure p9(v_in_deptno in dept2.deptno%type) is --定义游标变量类型 type emp_cursor is ref cursor; --定义游标变量 v_emp_cursor emp_cursor; v_ename emp2.ename%type; v_sal emp2.sal%type; v_empno emp2.empno%type; begin --打开游标 open v_emp_cursor for select ename,sal,empno from emp2 where deptno = v_in_deptno; --用循环语句取出游标中指向的每行数据 loop --取出每行数据 fetch v_emp_cursor into v_ename,v_sal,v_empno; --判断游标退出 exit when v_emp_cursor%notfound; --如果某个员工的工资低于2000则增加100 if(v_sal < 2000) then update emp2 set sal = sal + 100 where empno = v_empno; end if; --输出每行数据 dbms_output.put_line('员工的姓名为:' || v_ename || ' 员工的工资为:' || v_sal); end loop; --关闭游标 close v_emp_cursor; end; /
----------参考《韩顺平玩转Oracle》