什么是存储过程(Stored Procedure)?
一段存储在数据库的“子程序”,下面对这两个部分进行分开的阐释。
子程序(Subprogram): 一段可以被调用的子程序(subprogram)要么是一个过程(Procedrue),要么是一个函数(function).过程(Procerue)和函数(function)的区别之一是:函数总是只返回一个单个的值, 而过程并不是。(摘自Oracle Database concepts guide)数据库的存储过程通常是指一系列的SQL语句构成的”SQL代码块”, 和普通的SQL语句的不同点是, 代码块中包含了原生SQL所没有的元素, 例如cursor, variable,if, else, loop。“存储在数据库” SQL语句都是需要通过数据库相应组件编译后才能执行,最常见的例子是通过JDBC, 或ODBC创建数据区连接,发送SQL语句给数据库执行,并在程序中获得数据库返回的结果。存储过程则是把经常会被重复使用的SQL语句逻辑块封装起来,编译好,存储在数据库服务器端(这里的客户端是需要连接数据库的应用程序,但该应用程序本身也可能是一个Web服务器)。 这样当存储过程再次被调用时,就无须编译了。 而调用的过程也无须发送SQL语句,只需要发送一个存储过程的标识, 数据库就可以找到相应的存储过程予以调用。储存过程:CREATE OR REPLACE PROCEDURE 存储过程名ISBEGINNULL;END;行1:CREATE OR REPLACE PROCEDURE 是一个SQL语句通知Oracle数据库去创建一个叫做"存储过程名"存储过程, 如果存在就覆盖它;行2:IS关键词表明后面将跟随一个PL/SQL体。行3:BEGIN关键词表明PL/SQL体的开始。行4:NULL PL/SQL语句表明什么事都不做,这句不能删去,因为PL/SQL体中至少需要有一句;行5:END关键词表明PL/SQL体的结束存储过程创建语法一:create or replace procedure 存储过程名(param1 in type,param2 out type)as变量1 类型(值范围); –vs_msg VARCHAR2(4000);变量2 类型(值范围);BeginSelect 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(‘打印信息’);ElseRaise 异常名(NO_DATA_FOUND); End if;ExceptionWhen others thenRollback;End;注意事项:1, 存储过程参数不带取值范围,in表示传入,out表示输出类型可以使用任意Oracle中的合法类型。2, 变量带取值范围,后面接分号3, 在判断语句前最好先用count(*)函数判断是否存在该条操作记录4, 用select 。。。into。。。给变量赋值5, 在代码中抛异常用 raise 异常名存储过程创建语法二:CREATE OR REPLACE PROCEDURE存储过程名(–定义参数is_ym IN CHAR(6) ,the_count OUT NUMBER,)AS–定义变量vs_msg VARCHAR2(4000); –错误信息变量vs_ym_beg CHAR(6); –起始月份vs_ym_end CHAR(6); –终止月份vs_ym_sn_beg CHAR(6); –同期起始月份vs_ym_sn_end CHAR(6); –同期终止月份–定义游标(简单的说就是一个可以遍历的结果集)CURSOR cur_1 ISSELECT 。。。FROM 。。。WHERE 。。。GROUP BY 。。。;BEGIN–用输入参数给变量赋初值,用到了Oralce的SUBSTR,TO_CHAR,ADD_MONTHS,TO_DATE 等很常用的函数。vs_ym_beg := SUBSTR(is_ym,1,6);vs_ym_end := SUBSTR(is_ym,7,6);vs_ym_sn_beg := TO_CHAR(ADD_MONTHS(TO_DATE(vs_ym_beg,’yyyymm’), -12),’yyyymm’);vs_ym_sn_end := TO_CHAR(ADD_MONTHS(TO_DATE(vs_ym_end,’yyyymm’), -12),’yyyymm’);–先删除表中特定条件的数据。DELETE FROM 表名 WHERE ym = is_ym;–然后用内置的DBMS_OUTPUT对象的put_line方法打印出影响的记录行数,其中用到一个系统变量SQL%rowcountDBMS_OUTPUT.put_line(‘del上月记录=’||SQL%rowcount||’条‘);INSERT INTO表名(area_code,ym,CMCODE,rmb_amt,usd_amt)SELECT area_code,is_ym,CMCODE,SUM(rmb_amt)/10000,SUM(usd_amt)/10000FROM BGD_AREA_CM_M_BASE_TWHERE ym >= vs_ym_begAND ym <= vs_ym_endGROUP BY area_code,CMCODE;DBMS_OUTPUT.put_line(‘ins当月记录=’||SQL%rowcount||’条‘);–遍历游标处理后更新到表。遍历游标有几种方法,用for语句是其中比较直观的一种。FOR rec IN cur_1 LOOPUPDATE 表名 SET rmb_amt_sn = rec.rmb_amt_sn,usd_amt_sn = rec.usd_amt_snWHERE area_code = rec.area_codeAND CMCODE = rec.CMCODEAND ym = is_ym;END LOOP;COMMIT;–错误处理部分。OTHERS表示除了声明外的任意错误。SQLERRM是系统内置变量保存了当前错误的详细信息。EXCEPTIONWHEN OTHERS THENvs_msg := ’ERROR IN xxxxxxxxxxx_p(‘||is_ym||’):’||SUBSTR(SQLERRM,1,500);ROLLBACK;–把当前错误记录进日志表。INSERT INTO LOG_INFO(proc_name,error_info,op_date)VALUES(‘xxxxxxxxxxx_p’,vs_msg,SYSDATE);COMMIT;RETURN;END;oracle存储过程语法1 、判断语句:if 比较式 then begin end; end if;create or replace procedure test(x in number) isbeginif x >0 thenbeginx := 0 - x;end;end if;if x = 0 thenbeginx: = 1;end;end if;end test;2 、For 循环For … in … LOOP– 执行语句end LOOP;(1) 循环遍历游标create or replace procedure test() asCursor cursor is select name from student; name varchar(20);beginfor name in cursor LOOPbegindbms_output.putline(name);end;end LOOP;end test;(2) 循环遍历数组create or replace procedure test(varArray in myPackage.TestArray) as–( 输入参数varArray 是自定义的数组类型,定义方式见标题6)i number;begini := 1; – 存储过程数组是起始位置是从1 开始的,与java 、C 、C 等语言不同。因为在Oracle 中本是没有数组的概念的,数组其实就是一张– 表(Table), 每个数组元素就是表中的一个记录,所以遍历数组时就相当于从表中的第一条记录开始遍历for i in 1..varArray.count LOOPdbms_output.putline(‘The No.’|| i || ’record in varArray is:’||varArray(i));end LOOP;end test;3 、While 循环while 条件语句 LOOPbeginend;end LOOP;E.gcreate or replace procedure test(i in number) asbeginwhile i < 10 LOOPbegini:= i 1;end;end LOOP;end test;4 、数组首先明确一个概念:Oracle 中本是没有数组的概念的,数组其实就是一张表(Table), 每个数组元素就是表中的一个记录。使用数组时,用户可以使用Oracle 已经定义好的数组类型,或可根据自己的需要定义数组类型。(1) 使用Oracle 自带的数组类型x array; – 使用时需要需要进行初始化e.g:create or replace procedure test(y out array) isx array;beginx := new array();y := x;end test;(2) 自定义的数组类型 ( 自定义数据类型时,建议通过创建Package 的方式实现,以便于管理)create or replace package myPackage isPublic type declarations type info is record( name varchar(20), y number);type TestArray is table of info index by binary_integer;– 此处声明了一个TestArray 的类型数据,其实其为一张存储Info 数据类型的Table 而已,及TestArray 就是一张表,有两个字段,一个是name ,一个是y 。需要注意的是此处使用了Index by binary_integer 编制该Table 的索引项,也可以不写,直接写成:type TestArray istable of info ,如果不写的话使用数组时就需要进行初始化:varArray myPackage.TestArray; varArray := new myPackage.TestArray();end TestArray;
实例:
1 CREATE OR REPLACE PROCEDURE CSSS_PROC_SHEET_TO_HIS IS 2 ------- 查询待归档工单 3 CURSOR mycusor IS SELECT A.TASK_PARAMS 4 FROM COMMON_TASK_TD A 5 WHERE A.TASK_TYPE = 'FLOW_END' 6 AND A.VERSION = 0 7 AND A.IS_FINISHED = 'N' 8 -- and A.Task_Params like 'CSS201807%' 9 AND ROWNUM <10000;10 /*CURSOR mycusor IS SELECT t.sheet_id TASK_PARAMS FROM pg_j_sheet t WHERE t.deal_flag IN (1,2,4,5,6,9) AND ROWNUM<501;*/11 err_num NUMBER; /*申明变量*/12 err_msg varchar2(4000); /*申明变量*/13 seq NUMBER;/*申明变量*/14 CURSOR excusor IS SELECT A.EXPERT_SHEET_ID15 FROM EX_EXPERT_DEAL_SHEET A16 WHERE A.EXPERT_SHEET_STATUS > 117 AND ROWNUM < 20;18 19 BEGIN20 FOR MYROW IN mycusor LOOP /*循环遍历游标*/21 BEGIN22 ----------- 参数日志表23 INSERT INTO csss_his.pg_j_paralog SELECT * FROM pg_j_paralog a WHERE a.serial_id IN (SELECT24 b.serial_id FROM PG_J_BUSILOG b WHERE b.SHEET_ID = MYROW.TASK_PARAMS);25 --INSERT INTO csss_his.pg_j_paralog SELECT * FROM pg_j_paralog a WHERE a.serial_id=serow.serial_id;26 DELETE FROM pg_j_paralog a WHERE a.serial_id IN (SELECT27 b.serial_id FROM PG_J_BUSILOG b WHERE b.SHEET_ID = MYROW.TASK_PARAMS);28 --------- 业务日志表29 INSERT INTO csss_his.pg_j_busilog SELECT * FROM pg_j_busilog a WHERE a.sheet_id=MYROW.TASK_PARAMS;30 DELETE FROM pg_j_busilog a WHERE a.sheet_id=MYROW.TASK_PARAMS;31 --------- 接口日志表32 --INSERT INTO csss_his.cm_j_interfacelog SELECT * FROM cm_j_interfacelog a WHERE a.sheet_id=MYROW.TASK_PARAMS;33 --DELETE FROM cm_j_interfacelog a WHERE a.sheet_id=MYROW.TASK_PARAMS;34 --------- 表附件表35 INSERT INTO csss_his.pg_j_table_file SELECT * FROM pg_j_table_file a WHERE a.sheet_id=MYROW.TASK_PARAMS;36 DELETE FROM pg_j_table_file a WHERE a.sheet_id=MYROW.TASK_PARAMS;37 FOR exrow IN excusor LOOP38 BEGIN39 -------- 专席工单表40 INSERT INTO csss_his.EX_EXPERT_DEAL_SHEET SELECT * FROM EX_EXPERT_DEAL_SHEET a WHERE a.EXPERT_SHEET_ID=exrow.expert_sheet_id;41 DELETE FROM EX_EXPERT_DEAL_SHEET a WHERE a.expert_sheet_id=exrow.expert_sheet_id;42 END;43 END LOOP;44 --------- 客户信息表45 INSERT INTO csss_his.pg_j_custiinfo SELECT * FROM pg_j_custiinfo a WHERE a.sheet_id=MYROW.TASK_PARAMS;46 DELETE FROM pg_j_custiinfo a WHERE a.sheet_id=MYROW.TASK_PARAMS;47 ------工单日志48 INSERT INTO csss_his.pg_j_sheet_log SELECT * FROM pg_j_sheet_log a WHERE a.sheet_id=MYROW.TASK_PARAMS;49 DELETE FROM pg_j_sheet_log a WHERE a.sheet_id=MYROW.TASK_PARAMS;50 --------- 预处理工单表51 INSERT INTO csss_his.pg_j_sheet SELECT * FROM pg_j_sheet a WHERE a.sheet_id=MYROW.TASK_PARAMS;52 --------- 未统一编码工单表(新增备注2018.07.24)53 INSERT INTO csss_his.pg_j_sheet_unicode54 SELECT a.*55 FROM pg_j_sheet a, pg_c_phenomena_v_desc b56 WHERE a.sheet_id = MYROW.TASK_PARAMS57 and a.ycl_obligate7_flag is null58 and a.phe_type_id = b.PHE_TYPE_ID59 and b.COM_TYPE is not null60 and a.deal_flag != 9;61 ----存放一个月数据62 INSERT INTO pg_j_sheet_month SELECT * FROM pg_j_sheet a WHERE a.sheet_id=MYROW.TASK_PARAMS;63 DELETE FROM pg_j_sheet a WHERE a.sheet_id=MYROW.TASK_PARAMS;64 DELETE FROM COMMON_TASK_TD WHERE TASK_PARAMS=MYROW.TASK_PARAMS;65 dbms_output.put_line('info--------------');66 COMMIT;67 EXCEPTION68 WHEN OTHERS THEN69 err_num:= SQLCODE;70 err_msg:= SUBSTR(SQLERRM, 1, 3000);71 select MY_SEQENCE.NEXTVAL into seq from dual;72 insert into PUB_ERROR_LOG values(seq,sysdate,err_msg||':::'||MYROW.TASK_PARAMS,err_num);73 goto v_continue;74 <>75 NULL;76 END;77 END LOOP;78 END CSSS_PROC_SHEET_TO_HIS;
什么是游标?
①从表中检索出结果集,从中每次指向一条记录进行交互的机制。
②关系数据库中的操作是在完整的行集合上执行的。
由 SELECT 语句返回的行集合包括满足该语句的 WHERE 子句所列条件的所有行。由该语句返回完整的行集合叫做结果集。 应用程序,尤其是互动和在线应用程序,把完整的结果集作为一个单元处理并不总是有效的。这些应用程序需要一种机制来一次处理一行或连续的几行。而游标是对提供这一机制的结果集的扩展。
游标是通过游标库来实现的。游标库是常常作为数据库系统或数据访问 API 的一部分而得以实现的软件,
用来管理从数据源返回的数据的属性(结果集)。这些属性包括并发管理、在结果集中的位置、返回的行数,
以及是否能够在结果集中向前和/或向后移动(可滚动性)。
游标跟踪结果集中的位置,并允许对结果集逐行执行多个操作,在这个过程中可能返回至原始表,也可能不返回至原始表。
换句话说,游标从概念上讲基于数据库的表返回结果集。由于它指示结果集中的当前位置 ,就像计算机屏幕上的光标指示当前位置一样,“游标”由此得名。
2,游标有什么作用?
①指定结果集中特定行的位置。 ②基于当前的结果集位置检索一行或连续的几行。 ③在结果集的当前位置修改行中的数据。 ④对其他用户所做的数据更改定义不同的敏感性级别。 ⑤可以以编程的方式访问数据库。3,为什么避免使用游标?
①在创建游标时,最需要考虑的事情是,“是否有办法避免使用游标?” 因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写; 如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作。4,Oracle游标的类型?
①静态游标:结果集已经确实(静态定义)的游标。分为隐式和显示游标。 ⑴隐式游标:所有DML语句为隐式游标,通过隐式游标属性可以获取SQL语句信息。 ⑵显示游标:用户显示声明的游标,即指定结果集。当查询返回结果超过一行时,就需要一个显式游标。 ②REF游标:动态关联结果集的临时对象。游标的用法
-- 声明游标;CURSOR cursor_name IS select_statement--For 循环游标--(1)定义游标--(2)定义游标变量--(3)使用for循环来使用这个游标declare --类型定义 cursor c_job is select empno,ename,job,sal from emp where job='MANAGER'; --定义一个游标变量v_cinfo c_emp%ROWTYPE ,该类型为游标c_emp中的一行数据类型 c_row c_job%rowtype;begin for c_row in c_job loop dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal); end loop;end; --Fetch游标--使用的时候必须要明确的打开和关闭declare --类型定义 cursor c_job is select empno,ename,job,sal from emp where job='MANAGER'; --定义一个游标变量 c_row c_job%rowtype;begin open c_job; loop --提取一行数据到c_row fetch c_job into c_row; --判读是否提取到值,没取到值就退出 --取到值c_job%notfound 是false --取不到值c_job%notfound 是true exit when c_job%notfound; dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal); end loop; --关闭游标 close c_job;end;--1:任意执行一个update操作,用隐式游标sql的属性%found,%notfound,%rowcount,%isopen观察update语句的执行情况。 begin update emp set ENAME='ALEARK' WHERE EMPNO=7469; if sql%isopen then dbms_output.put_line('Openging'); else dbms_output.put_line('closing'); end if; if sql%found then dbms_output.put_line('游标指向了有效行');--判断游标是否指向有效行 else dbms_output.put_line('Sorry'); end if; if sql%notfound then dbms_output.put_line('Also Sorry'); else dbms_output.put_line('Haha'); end if; dbms_output.put_line(sql%rowcount); exception when no_data_found then dbms_output.put_line('Sorry No data'); when too_many_rows then dbms_output.put_line('Too Many rows'); end;declare empNumber emp.EMPNO%TYPE; empName emp.ENAME%TYPE; begin if sql%isopen then dbms_output.put_line('Cursor is opinging'); else dbms_output.put_line('Cursor is Close'); end if; if sql%notfound then dbms_output.put_line('No Value'); else dbms_output.put_line(empNumber); end if; dbms_output.put_line(sql%rowcount); dbms_output.put_line('-------------'); select EMPNO,ENAME into empNumber,empName from emp where EMPNO=7499; dbms_output.put_line(sql%rowcount); if sql%isopen then dbms_output.put_line('Cursor is opinging'); else dbms_output.put_line('Cursor is Closing'); end if; if sql%notfound then dbms_output.put_line('No Value'); else dbms_output.put_line(empNumber); end if; exception when no_data_found then dbms_output.put_line('No Value'); when too_many_rows then dbms_output.put_line('too many rows'); end;
什么是触发器?
每当一个特定的数据操作语句(insert、update、delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。
注意:select语句不在上述的操作范围内。
create or replace trigger synch_userid after insert or deleteon eosoperatorFOR EACH ROW --每一行触发一次declarei number; --申明变量j number; --申明变量begin case --插入 when inserting then select sys_staff_seq.nextval into i from dual; --插入工号i insert into DN.sys_staff@YCL_VISITING_DN(staff_id,user_id,operator_name,org_id,password,duration) values(i,:new.userid,:new.operatorname,5,'13ee51a026df8a8265f79dad03dcfa2a',120); --人员机构表插入 insert into DN.sys_staff_attr@YCL_VISITING_DN(staff_id) values(i); --人员权限表插入 insert into DN.sys_staff_role_rel@YCL_VISITING_DN(role_id,staff_id) values(5,i); when deleting then --查询主键 select staff_id into j from DN.sys_staff@YCL_VISITING_DN where user_id=:old.userid; delete DN.sys_staff_attr@YCL_VISITING_DN where staff_id=j; delete DN.sys_staff_role_rel@YCL_VISITING_DN where staff_id=j; delete DN.sys_staff@YCL_VISITING_DN where user_id =:old.userid; end case;END;
注意:
1、:old \ :new :代表的是同一条记录。
2、:old :表示操作该行之前,这一行的值。
3、:new :标示操作该行之后,这一行的值。