Oracle PL/SQL基础知识
过程(存储过程)
过程用于执行特定的操作,当建立过程时,既可以指定输入参数(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();
}
}
}
包
包用于在逻辑上组合过程和函数等对象,它由包的规范和包体两部分组成。
1、使用create package命令来创建包:
建包基本语法:
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;
/
2、建立包体可以使用create package body 命令
建立包体基本语法:
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)
标量(scalar)
在编写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;
/
%type类型
为了降低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;
/
复合变量(composite)
用于存放多个值的变量。常用的包括:1、pl/sql记录;2、pl/sql表
复合类型–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表
相当于高级语言中的数组,但是需要注意的是在高级语言中数组的下标不能为负数,而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》