一、什么是存储过程
所谓存储过程,就是一段存储在数据库中实现某种功能的程序,其中包含一条或多条SQL语句,但是它的定义方式和PL/SQL中的块、包等有所区别。存储过程可以通俗地理解为存储在数据库服务器中的封装了一段或多段SQL语句的PL/SQL代码块。在数据库中有一些系统默认的存储过程,可以直接通过存储过程的名称进行调用。另外,存储过程还可以在其他编程语言中调用,例如:Java,C#,VB等。
二、存储过程的作用
储过程的编写相对比较复杂,但很多单位或个人都在使用它,显然这不是因为存储过程编写简单,而是因为它有着一系列的优点:
- 简化复杂的操作。存储过程可以把需要执行的多条SQL语句封装到一个独立单元中,用户只需调用这个单元就能达到目的。这样就实现了一人编写多人调用,同时缩短了程序的平均开发周期,为公司节省了成本。
- 增加数据独立性。与视图的效果类似,利用存储过程可以把数据库基础数据和程序(或用户)隔离开来,当基础数据的结构发生变化时,可以只修改存储过程,这样对程序来说基础数据的变化是不可见的,也就不需要修改程序代码了。
- 提高安全性。使用存储过程可以有效降低错误出现的几率。如果想不使用存储过程实现某项操作可能需要执行多条单独的SQL语句,而过多的执行步骤很可能造成更高的出错几率。不仅如此,在实际工作中开发人员的水平参差不齐,由高水平的人编写存储过程,水平较低的人员直接调用,这样就能避免很多不必要的错误发生。此外,对存储过程也可以进行权限设置。
- 提高性能。完成一项复杂的功能可能需要多条SQL语句,同时SQL每次执行都需要编译,而存储过程可以包含多条SQL语句,在创建完成后只需要编译一次就可以直接调用,从这方面来看存储过程可以提高性能。如果程序语言要实现某项比较复杂的功能,它会多次连接数据库,在使用存储过程的情况下,程序只需连接一次数据库就能达到目的。
1.1,Oracle存储过程简介:
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,
减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
优点:
允许模块化程序设计,就是说只需要创建一次过程,以后在程序中就可以调用该过程任意次。
允许更快执行,如果某操作需要执行大量SQL语句或重复执行,存储过程比SQL语句执行的要快。
减少网络流量,例如一个需要数百行的SQL代码的操作有一条执行语句完成,不需要在网络中发送数百行代码。
更好的安全机制,对于没有权限执行存储过程的用户,也可授权他们执行存储过程。
三、Oracle的存储过程基本写法
1.2,创建存储过程的语法:
create [or replace] procedure 存储过程名(param1 in type,param2 out type)
as
变量1 类型(值范围);
变量2 类型(值范围);
begin
select count(*) into 变量1 from 表A where列名=param1;
if (判断条件) then
select 列名 into 变量2 from 表A where列名=param1;
dbms_output.Put_line('打印信息');
elsif (判断条件) then
dbms_output.Put_line('打印信息');
else
raise 异常名(NO_DATA_FOUND);
end if;
exception
when others then
rollback;
end;
参数的几种类型:
in 是参数的默认模式,这种模式就是在程序运行的时候已经具有值,在程序体中值不会改变。
out 模式定义的参数只能在过程体内部赋值,表示该参数可以将某个值传递回调用他的过程
in out 表示高参数可以向该过程中传递值,也可以将某个值传出去
1.3,示范一些存储过程[下面一些存储过程的操作根据自己数据库中的内容进行内容显示,只要显示内容就正确,报错除外- -,还有存储过程尽量不要粘贴代码,很容易报错]:
1.3.1,不带参数的存储过程:
CREATE OR REPLACE PROCEDURE MYDEMO02
AS
name VARCHAR(10);
age NUMBER(10);
BEGIN
name := 'xiaoming';--:=则是对属性进行赋值
age := 18;
dbms_output.put_line ( 'name=' || name || ', age=' || age );--这条是输出语句
END;
--存储过程调用(下面只是调用存储过程语法)
BEGIN
MYDEMO02();
END;
1.3.2,带参数的存储过程:
CREATE OR REPLACE procedure MYDEMO03(name in varchar,age in int)
AS
BEGIN
dbms_output.put_line('name='||name||', age='||age);
END;
--存储过程调用
BEGIN
MYDEMO03('姜煜',18);
END;
1.3.3,出现异常的输出存储过程:
CREATE OR REPLACE PROCEDURE MYDEMO04
AS
age INT;
BEGIN
age:=10/0;
dbms_output.put_line(age);
EXCEPTION when others then --处理异常
dbms_output.put_line('error');
END;
--调用存储过程
BEGIN
MYDEMO04;
END;
Oracle常见的三大异常分类
预定义异常:由PL/SQL定义的异常。由于它们已在standard包中预定义了,因此,这些预定义异常可以直接在程序中使用,而不必再定义部分声明。
非预定义异常:用于处理预定义异常所不能处理的Oracle错误。
自定义异常:用户自定义的异常,需要在定义部分声明后才能在可执行部分使用。用户自定义异常对应的错误不一定是Oracle错误,例如它可能是一个数据错误。
1.3.4,获取当前时间和总人数:
CREATE OR REPLACE PROCEDURE TEST_COUNT01
IS
v_total int;
v_date varchar(20);
BEGIN
select count(*) into v_total from EMP_TEST WHERE ENAME ='燕小六'; --into是赋值的关键字
select to_char(sysdate,'yyyy-mm-dd')into v_date FROM EMP_TEST WHERE ENAME ='郭芙蓉';
DBMS_OUTPUT.put_line('总人数:'||v_total);
DBMS_OUTPUT.put_line('当前日期'||v_date);
END;
--调用存储过程
BEGIN
TEST_COUNT01();
END;
1.3.5,带输入参数和输出参数的存储过程:
CREATE OR REPLACE PROCEDURE TEST_COUNT04(v_id in int,v_name out varchar2)
IS
BEGIN
SELECT ENAME into v_name FROM EMP_TEST WHERE EMPNO = v_id;
dbms_output.put_line(v_name);
EXCEPTION
when no_data_found then dbms_output.put_line('no_data_found');
END;
--调用存储过程
DECLARE
v_name varchar(200);
BEGIN
TEST_COUNT04('1002',v_name);
END;
1.3.6,查询存储过程以及其他:
CREATE OR REPLACE PROCEDURE job_day04(de in varchar,name out varchar,App_Code out varchar,error_Msg out varchar)
AS
BEGIN
SELECT ENAME into name FROM EMP_TEST WHERE ENAME=de;
EXCEPTION WHEN others THEN
error_Msg:='未找到数据';
END;
--调用存储过程
DECLARE
de varchar(10);
ab varchar(10);
appcode varchar(20);
ermg varchar(20);
BEGIN
de:= '张三丰';
JOB_DAY04(de,ab,appcode,ermg);
dbms_output.put_line(ermg);
END;
1.3.7,向数据库中添加数据的存储过程
CREATE OR REPLACE PROCEDURE job_day05(do1 in varchar,dn1 in varchar,eo1 in number,en1 in varchar,App_Code out varchar,error_Msg out varchar)
AS
BEGIN
INSERT INTO STUDENT(NAME,CLASS)VALUES(do1,dn1);
INSERT INTO COMPANY(EMPID,NAME,DEPARNAME)VALUES(eo1,en1,do1);
COMMIT;
EXCEPTION WHEN OTHERS THEN
App_Code:=-1;
error_Msg:='插入失败';
END;
--调用存储过程
DECLARE
do1 varchar(10);
dn1 varchar(10);
eo1 number(20);
App_Code varchar(20);
error_Msg varchar(20);
BEGIN
do1:= '张三丰';
dn1:='新桥';
eo1:=1001;
JOB_DAY04(do1,dn1,App_Code,error_Msg);
dbms_output.put_line(ermg);
END;
三、游标的使用:
游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。
游标有两种类型:显式游标和隐式游标。
在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,
系统都会使用一个隐式游标。
但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。 游标一旦打开,数据就从数据库中传送到游标变量中,然后应用程序再从游标变量中分解出需要的数据,并进行处理。
在我们进行insert、update、delete和select value into variable 的操作中,使用的是隐式游标
隐式游标的属性 返回值类型意义
SQL%ROWCOUNT 整型 代表DML语句成功执行的数据行数
SQL%FOUND 布尔型 值为TRUE代表插入、删除、更新或单行查询操作成功
SQL%NOTFOUND 布尔型 与SQL%FOUND属性返回值相反
SQL%ISOPEN 布尔型 DML执行过程中为真,结束后为假
2.1,修改雇员薪资:
CREATE OR REPLACE PROCEDURE job_day06(epo in number)
AS
BEGIN
UPDATE EMPS SET SAL=(SAL+100) WHERE empno = epo;
IF SQL%FOUND --SQL%FOUND是隐式游标 作用:判断SQL语句是否成功执行,当有作用行时则成功执行为true,否则为false。 6 THEN
DBMS_OUTPUT.PUT_LINE('成功修改雇员工资!');
commit;
else
DBMS_OUTPUT.PUT_LINE('修改雇员工资失败!');
END IF;
END;
--调用存储过程
declare
e_number number;
begin
e_number:=1001;
job_day06(e_number);
end;
2.2,查询编号为1001信息
CREATE OR REPLACE PROCEDURE job_day07
IS
BEGIN
DECLARE
cursor emp_sor is select name,sal FROM EMPS WHERE EMPNO = '1001'; --声明游标
cname EMPS.NAME%type; --%type 作用: 声明的变量ename与EMPS表的NAME列类型一样
csal EMPS.SAL%type;
BEGIN
open emp_sor; --打开游标
loop
-- 取游标值给变量
FETCH emp_sor into cname,csal;
dbms_output.put_line('name:'||cname);
exit when emp_sor%notfound;
end loop;
close emp_sor; --关闭游标
end;
end;
--调用存储过程
BEGIN
job_day07();
END;
【示例3】使用游标处理数据
要求把PRODUCTINFO表中数据根据不同的产品类型把数据分类输出到屏幕。
CREATE PROCEDURE PRODUCT_CUR_PRC
AS
cur_ctgy productinfo.category%TYPE; --存放产品类型编码
cur_ctgyname categoryinfo.categoryname%TYPE; --存放产品类型名称
cur_prtifo productinfo%ROWTYPE; --存放表productinfo的行记录
CURSOR cur_category
IS
SELECT CATEGORY FROM PRODUCTINFO GROUP BY CATEGORY;
BEGIN
OPEN cur_category;
LOOP
FETCH cur_category INTO cur_ctgy;
EXIT WHEN cur_category%NOTFOUND;
SELECT CATEGORYINFO.CATEGORYNAME INTO cur_ctgyname
FROM CATEGORYINFO
WHERE CATEGORYID = cur_ctgy; --根据类型编码得到产品类型名称
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('--------------------------------------');
DBMS_OUTPUT.PUT_LINE(cur_ctgyname || ':');
END IF;
FOR my_prdinfo_rec IN
(
SELECT * FROM PRODUCTINFO WHERE CATEGORY = cur_ctgy
)
LOOP
DBMS_OUTPUT.PUT_LINE(
'产品名称: ' || my_prdinfo_rec.PRODUCTNAME
|| '产品价格: ' || my_prdinfo_rec.PRODUCTPRICE
|| '产品数量: ' || my_prdinfo_rec.QUANTITY
);
END LOOP;
END LOOP;
CLOSE cur_category;
END ;
/
【代码解析】
■ 第1行表示创建存储过程,名称是PRODUCT_CUR_PRC。
■ 第3~5行表示变量名称以及变量类型,脚本的具体含义可参考游标部分的讲解。其中cur_ctgy存放产品类型编码,cur_ctgyname存放产品类型名称,cur_prtifo存放表productinfo的行记录。
■ 第7~9行表示创建一个游标,表示从PRODUCTINFO表中查询已有的产品类型。
■ 第12~15行表示打开游标cur_category并进入循环流提取数据,当数据提取完毕会退出循环。
■ 第16~18行表示在CATEGORYINFO表中,根据产品类型编码得到产品类型名称,并把得到的结果赋值给变量cur_ctgyname。
■ 第20~23行利用隐式游标的%FOUND属性判断第16~18行的查询是否有结果,如果有则把产品类型名称输出到屏幕。
■ 第25~35行表示利用隐式游标获取某类型的所有产品,并把产品信息分类输出到屏幕上。
总结:
存储过程通俗的理解就是就是一个执行过程,调用的时候给他所需要的需求就会对数据库进行操作,相当于我们自己手写Sql,只不过有了存储过程
只要调用一下传给他参数他就会帮我们写,比较方便,灵活的运用存储过程会让我们开发很方便
JDBC调用存储过程:四种分类详解及实例(Oracle)
【准备工作】
创建一个测试表TMP_MICHAEL ,并插入数据,SQL如下:
create table TMP_MICHAEL
(
USER_ID VARCHAR2(20),
USER_NAME VARCHAR2(10),
SALARY NUMBER(8,2),
OTHER_INFO VARCHAR2(100)
)
insert into TMP_MICHAEL (USER_ID, USER_NAME, SALARY, OTHER_INFO)
values ('michael', 'Michael', 5000, 'http://sjsky.iteye.com');
insert into TMP_MICHAEL (USER_ID, USER_NAME, SALARY, OTHER_INFO)
values ('zhangsan', '张三', 10000, null);
insert into TMP_MICHAEL (USER_ID, USER_NAME, SALARY, OTHER_INFO)
values ('aoi_sola', '苍井空', 99999.99, 'twitter account');
insert into TMP_MICHAEL (USER_ID, USER_NAME, SALARY, OTHER_INFO)
values ('李四', '李四', 2500, null);
[一]、只有输入IN参数,没有输出OUT参数
存储过程 TEST_MICHAEL_NOOUT 的相关SQL:
CREATE OR REPLACE PROCEDURE TEST_MICHAEL_NOOUT(P_USERID IN VARCHAR2,
P_USERNAME IN VARCHAR2,
P_SALARY IN NUMBER,
P_OTHERINFO IN VARCHAR2) IS
BEGIN
INSERT INTO TMP_MICHAEL
(USER_ID, USER_NAME, SALARY, OTHER_INFO)
VALUES
(P_USERID, P_USERNAME, P_SALARY, P_OTHERINFO);
END TEST_MICHAEL_NOOUT;
调用代码如下:
Java代码
/**
* 测试调用存储过程:无返回值
* @blog http://sjsky.iteye.com
* @author Michael
* @throws Exception
*/
public static void testProcNoOut() throws Exception {
System.out.println("------- start 测试调用存储过程:无返回值");
Connection conn = null;
CallableStatement callStmt = null;
try {
Class.forName(DB_DRIVER);
conn = DriverManager.getConnection(DB_CONNECTION, DB_NAME, DB_PWd);
// 存储过程 TEST_MICHAEL_NOOUT 其实是向数据库插入一条数据
callStmt = conn.prepareCall("{call TEST_MICHAEL_NOOUT(?,?,?,?)}");
// 参数index从1开始,依次 1,2,3...
callStmt.setString(1, "jdbc");
callStmt.setString(2, "JDBC");
callStmt.setDouble(3, 8000.00);
callStmt.setString(4, "http://sjsky.iteye.com");
callStmt.execute();
System.out.println("------- Test End.");
} catch (Exception e) {
e.printStackTrace(System.out);
} finally {
if (null != callStmt) {
callStmt.close();
}
if (null != conn) {
conn.close();
}
}
}
运行后查询数据库内容,已经成功插入数据,截图如下:
[二]、既有输入IN参数,也有输出OUT参数,输出是简单值(非列表)
存储过程 TEST_MICHAEL 的SQL如下:
Sql代码
CREATE OR REPLACE PROCEDURE TEST_MICHAEL(P_USERID IN VARCHAR2,
P_SALARY IN NUMBER,
P_COUNT OUT NUMBER) IS
V_SALARY NUMBER := P_SALARY;
BEGIN
IF V_SALARY IS NULL THEN
V_SALARY := 0;
END IF;
IF P_USERID IS NULL THEN
SELECT COUNT(*)
INTO P_COUNT
FROM TMP_MICHAEL T
WHERE T.SALARY >= V_SALARY;
ELSE
SELECT COUNT(*)
INTO P_COUNT
FROM TMP_MICHAEL T
WHERE T.SALARY >= V_SALARY
AND T.USER_ID LIKE '%' || P_USERID || '%';
END IF;
DBMS_OUTPUT.PUT_LINE('v_count=:' || P_COUNT);
END TEST_MICHAEL;
调用程序如下:
Java代码
/**
* 测试调用存储过程:返回值是简单值非列表
* @blog http://sjsky.iteye.com
* @author Michael
* @throws Exception
*/
public static void testProcOutSimple() throws Exception {
System.out.println("------- start 测试调用存储过程:返回值是简单值非列表");
Connection conn = null;
CallableStatement stmt = null;
try {
Class.forName(DB_DRIVER);
conn = DriverManager.getConnection(DB_CONNECTION, DB_NAME, DB_PWd);
stmt = conn.prepareCall("{call TEST_MICHAEL(?,?,?)}");
stmt.setString(1, "");
stmt.setDouble(2, 3000);
// out 注册的index 和取值时要对应
stmt.registerOutParameter(3, Types.INTEGER);
stmt.execute();
// getXxx(index)中的index 需要和上面registerOutParameter的index对应
int i = stmt.getInt(3);
System.out.println("符号条件的查询结果 count := " + i);
System.out.println("------- Test End.");
} catch (Exception e) {
e.printStackTrace(System.out);
} finally {
if (null != stmt) {
stmt.close();
}
if (null != conn) {
conn.close();
}
}
}
测试程序就是查询薪水3000以上人员的数量 ,运行结果如下:
------- start 测试调用存储过程:返回值是简单值非列表
符号条件的查询结果 count := 4
------- Test End.
[三]、既有输入IN参数,也有输出OUT参数,输出是列表
首先需要创建PACKAGE TEST_PKG_CURSOR 的SQL如下:
Sql代码
CREATE OR REPLACE PACKAGE TEST_PKG_CURSOR IS
-- Author : MICHAEL http://sjsky.iteye.com
TYPE TEST_CURSOR IS REF CURSOR;
END TEST_PKG_CURSOR;
再创建存储过程 TEST_P_OUTRS 的SQL如下:
Sql代码
CREATE OR REPLACE PROCEDURE TEST_P_OUTRS(P_SALARY IN NUMBER,
P_OUTRS OUT TEST_PKG_CURSOR.TEST_CURSOR) IS
V_SALARY NUMBER := P_SALARY;
BEGIN
IF P_SALARY IS NULL THEN
V_SALARY := 0;
END IF;
OPEN P_OUTRS FOR
SELECT * FROM TMP_MICHAEL T WHERE T.SALARY > V_SALARY;
END TEST_P_OUTRS;
调用存储过程的代码如下:
Java代码
/**
* 测试调用存储过程:有返回值且返回值为列表的
* @blog http://sjsky.iteye.com
* @author Michael
* @throws Exception
*/
public static void testProcOutRs() throws Exception {
System.out.println("------- start 测试调用存储过程:有返回值且返回值为列表的");
Connection conn = null;
CallableStatement stmt = null;
ResultSet rs = null;
try {
Class.forName(DB_DRIVER);
conn = DriverManager.getConnection(DB_CONNECTION, DB_NAME, DB_PWd);
stmt = conn.prepareCall("{call TEST_P_OUTRS(?,?)}");
stmt.setDouble(1, 3000);
stmt.registerOutParameter(2, OracleTypes.CURSOR);
stmt.execute();
// getXxx(index)中的index 需要和上面registerOutParameter的index对应
rs = (ResultSet) stmt.getObject(2);
// 获取列名及类型
int colunmCount = rs.getMetaData().getColumnCount();
String[] colNameArr = new String[colunmCount];
String[] colTypeArr = new String[colunmCount];
for (int i = 0; i < colunmCount; i++) {
colNameArr[i] = rs.getMetaData().getColumnName(i + 1);
colTypeArr[i] = rs.getMetaData().getColumnTypeName(i + 1);
System.out.print(colNameArr[i] + "(" + colTypeArr[i] + ")"
+ " | ");
}
System.out.println();
while (rs.next()) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < colunmCount; i++) {
sb.append(rs.getString(i + 1) + " | ");
}
System.out.println(sb);
}
System.out.println("------- Test Proc Out is ResultSet end. ");
} catch (Exception e) {
e.printStackTrace(System.out);
} finally {
if (null != rs) {
rs.close();
}
if (null != stmt) {
stmt.close();
}
if (null != conn) {
conn.close();
}
}
}
运行结果如下:
------- start 测试调用存储过程:有返回值且返回值为列表的
USER_ID(VARCHAR2) | USER_NAME(VARCHAR2) | SALARY(NUMBER) | OTHER_INFO(VARCHAR2) |
michael | Michael | 5000 | null |
zhangsan | 张三 | 10000 | null |
aoi_sola | 苍井空 | 99999.99 | null |
jdbc | JDBC | 8000 | http://sjsky.iteye.com |
------- Test Proc Out is ResultSet end.
[四]、输入输出参数是同一个(IN OUT)
创建存储过程TEST_P_INOUT 的SQL如下:
Sql代码
-
CREATE OR REPLACE PROCEDURE TEST_P_INOUT(P_USERID IN VARCHAR2, P_NUM IN OUT NUMBER) IS V_COUNT NUMBER; V_SALARY NUMBER := P_NUM; BEGIN IF V_SALARY IS NULL THEN V_SALARY := 0; END IF; SELECT COUNT(*) INTO V_COUNT FROM TMP_MICHAEL WHERE USER_ID LIKE '%' || P_USERID || '%' AND SALARY >= V_SALARY; P_NUM := V_COUNT; END TEST_P_INOUT;
调用存储过程的代码:
Java代码
/**
* 测试调用存储过程: INOUT同一个参数:
* @blog http://sjsky.iteye.com
* @author Michael
* @throws Exception
*/
public static void testProcInOut() throws Exception {
System.out.println("------- start 测试调用存储过程:INOUT同一个参数");
Connection conn = null;
CallableStatement stmt = null;
try {
Class.forName(DB_DRIVER);
conn = DriverManager.getConnection(DB_CONNECTION, DB_NAME, DB_PWd);
stmt = conn.prepareCall("{call TEST_P_INOUT(?,?)}");
stmt.setString(1, "michael");
stmt.setDouble(2, 3000);
// 注意此次注册out 的index 和上面的in 参数index 相同
stmt.registerOutParameter(2, Types.INTEGER);
stmt.execute();
// getXxx(index)中的index 需要和上面registerOutParameter的index对应
int count = stmt.getInt(2);
System.out.println("符号条件的查询结果 count := " + count);
System.out.println("------- Test End.");
} catch (Exception e) {
e.printStackTrace(System.out);
} finally {
if (null != stmt) {
stmt.close();
}
if (null != conn) {
conn.close();
}
}
}
运行结果如下:
------- start 测试调用存储过程:INOUT同一个参数
符号条件的查询结果 count := 1
------- Test End.
到处基本介绍已经结束了,希望能给大家有所帮助。
本文连接:http://sjsky.iteye.com/blog/1246657
二、Oracle实现数据不存在则插入,数据存在则更新(merge)
MERGE INTO copy_emp c
USING employees e
ON (c.employee_id=e.employee_id)
WHEN MATCHED THEN
UPDATE SET
c.first_name=e.first_name,
c.last_name=e.last_name,
c.department_id=e.department_id
WHEN NOT MATCHED THEN
INSERT VALUES(e.employee_id,e.first_name,e.last_name,
e.email,e.phone_number,e.hire_date,e.job_id,
e.salary,e.commission_pct,e.manager_id,
e.departmetn_id);