1. PL/SQL简介
PL/SQL是ORACLE系统的核心语言,现在ORACLE的许多部件都是由PL/SQL写成。在PL/SQL中可以使用的SQL语句有:
INSERT,UPDATE,DELETE,SELECT INTO,COMMIT,ROLLBACK,SAVEPOINT。
提示:在 PL/SQL中只能用 SQL语句中的 DML 部分,不能用 DDL 部分,如果要在PL/SQL中使用DDL(如CREATE table 等)的话,只能以动态的方式来使用。
ORACLE 的 PL/SQL 组件在对 PL/SQL 程序进行解释时,同时对在其所使用的表名、列名及数据类型进行检查。
PL/SQL 可以在SQL*PLUS 中使用。
PL/SQL 可以在高级语言中使用。
PL/SQL可以在ORACLE的开发工具中使用(如:SQL Developer或Procedure Builder等)。
其它开发工具也可以调用PL/SQL编写的过程和函数,如Power Builder 等都可以调用服务器端的PL/SQL过程。
2. PL/SQL块结构
2.1 PL/SQL块
PL/SQL程序由三个块组成,即声明部分、执行部分、异常处理部分。
PL/SQL块的结构如下:
1. DECLARE
2. --声明部分: 在此声明PL/SQL用到的变量类型及游标,以及局部的存储过程和函数
3. BEGIN
4. -- 执行部分: 过程及SQL 语句, 即程序的主要部分
5. EXCEPTION
6. -- 执行异常部分: 错误处理
7. END;
其中:执行部分不能省略。
PL/SQL块可以分为以下几类:
1. 无名块或匿名块(anonymous):动态构造,只能执行一次,可调用其它程序,但不能被其它程序调用。
2. 命名块(named):是带有名称的匿名块,这个名称就是标签。
3. 子程序(subprogram):存储在数据库中的存储过程、函数等。当在数据库上建立好后可以在其它程序中调用它们。
4. 触发器(Trigger):当数据库发生操作时,会触发一些事件,从而自动执行相应的程序。
5. 程序包/包(package):存储在数据库中的一组子程序、变量定义。在包中的子程序可以被其它程序包或子程序调用。但如果声明的是局部子程序,则只能在定义该局部子程序的块中调用该局部子程序。
2.2 PL/SQL结构
1.PL/SQL块中可以包含子块;
2.子块可以位于 PL/SQL中的任何部分;
3.子块也即PL/SQL中的一条命令;
2.3 标识符
PL/SQL程序设计中的标识符定义与SQL 的标识符定义的要求相同。要求和限制有:
1.标识符名不能超过30字符;
2.第一个字符必须为字母;
3.不分大小写;
4.不能用’-‘(减号);
5.不能是SQL保留字。
提示: 一般不要把变量名声明与表中字段名完全一样,如果这样可能得到不正确的结果.
例如:下面的例子将会删除所有的纪录,而不是’EricHu’的记录;
1. DECLARE ename varchar2 (20) : = 'EricHu';
2. BEGIN
3. DELETE
4. FROM scott.emp
5. WHERE ename = ename;
6. END;
变量命名在PL/SQL中有特别的讲究,建议在系统的设计阶段就要求所有编程人员共同遵守一定的要求,使得整个系统的文档在规范上达到要求。下面是建议的命名方法:
2.4 PL/SQL 变量类型
在前面的介绍中,有系统的数据类型,也可以自定义数据类型。下表给出ORACLE类型和PL/SQL中的变量类型的合法使用列表:
2.4.1变量类型
在ORACLE8i中可以使用的变量类型有:
例1. 插入一条记录并显示;
1. DECLARE
2. Row_id ROWID;
3. info VARCHAR2(40);
4. BEGIN
5. INSERT INTO scott.dept VALUES (90, '财务室', '海口')
6. RETURNING rowid, dname||':'||to_char(deptno)||':'||loc
7. INTO row_id, info;
8. DBMS_OUTPUT.PUT_LINE('ROWID:'||row_id);
9. DBMS_OUTPUT.PUT_LINE(info);
10. END;
其中:
RETURNING子句用于检索INSERT语句中所影响的数据行数,当INSERT语句使用VALUES 子句插入数据时,RETURNING 字句还可将列表达式、ROWID和REF值返回到输出变量中。在使用RETURNING 子句是应注意以下几点限制:
1.不能与DML语句和远程对象一起使用;
2.不能检索LONG 类型信息;
3.当通过视图向基表中插入数据时,只能与单基表视图一起使用。
例2. 修改一条记录并显示
1. DECLARE
2. ROW_ID ROWID;
3. INFO VARCHAR2(40);
4. BEGIN
5. UPDATE DEPT
6. SET DEPTNO = '90'
7. WHERE DNAME = '财务室'
8. RETURNING ROWID, DNAME || ':' || TO_CHAR(DEPTNO) || ':' || LOC INTO ROW_ID, INFO;
9. DBMS_OUTPUT.PUT_LINE('ROWID=' || ROW_ID);
10. DBMS_OUTPUT.PUT_LINE(INFO);
11. END;
其中:
RETURNING子句用于检索被修改行的信息。当UPDATE语句修改单行数据时,RETURNING 子句可以检索被修改行的ROWID和REF值,以及行中被修改列的列表达式,并可将他们存储到PL/SQL变量或复合变量中;当UPDATE语句修改多行数据时,RETURNING 子句可以将被修改行的ROWID和REF值,以及列表达式值返回到复合变量数组中。在UPDATE中使用RETURNING 子句的限制与INSERT语句中对RETURNING子句的限制相同。
例3. 删除一条记录并显示
1. DECLARE
2. ROW_ID ROWID;
3. INFO VARCHAR2(40);
4. BEGIN
5. DELETE DEPT
6. WHERE Deptno= '90'
7. RETURNING ROWID, DNAME || ':' || TO_CHAR(DEPTNO) || ':' || LOC INTO ROW_ID, INFO;
8. DBMS_OUTPUT.PUT_LINE('ROWID:' || ROW_ID);
9. DBMS_OUTPUT.PUT_LINE(INFO);
10. END;
其中:
RETURNING子句用于检索被删除行的信息:当DELETE语句删除单行数据时,RETURNING 子句可以检索被删除行的ROWID和REF值,以及被删除列的列表达式,并可将他们存储到PL/SQL变量或复合变量中;当DELETE语句删除多行数据时,RETURNING 子句可以将被删除行的ROWID和REF值,以及列表达式值返回到复合变量数组中。在DELETE中使用RETURNING 子句的限制与INSERT语句中对RETURNING子句的限制相同。
2.4.2 复合类型
ORACLE 在 PL/SQL 中除了提供象前面介绍的各种类型外,还提供一种称为复合类型的类型---记录和表.
2.4.2.1 记录类型
记录类型类似于C语言中的结构数据类型,它把逻辑相关的、分离的、基本数据类型的变量组成一个整体存储起来,它必须包括至少一个标量型或RECORD数据类型的成员,称作PL/SQL RECORD 的域(FIELD),其作用是存放互不相同但逻辑相关的信息。在使用记录数据类型变量时,需要先在声明部分先定义记录的组成、记录的变量,然后在执行部分引用该记录变量本身或其中的成员。
定义记录类型语法如下:
1. TYPE record_name IS RECORD(
2. v1 data_type1 [NOT NULL] [:= default_value ],
3. v2 data_type2 [NOT NULL] [:= default_value ],
4. ......
5. vn data_typen [NOT NULL] [:= default_value ] );
例4 :
1. DECLARE
2. TYPE REC_TEST IS RECORD(
3. NAME VARCHAR2(20) := 'lisa',
4. INFO VARCHAR2(30)) ;
5. REC_BOOK REC_TEST;
6. BEGIN
7. REC_BOOK.NAME := 'lisa';
8. REC_BOOK.INFO := 'PL/SQL';
9. DBMS_OUTPUT.PUT_LINE(REC_BOOK.NAME || REC_BOOK.INFO);
10. END;
可以用 SELECT语句对记录变量进行赋值,只要保证记录字段与查询结果列表中的字段相配即可。
例5 :
1. DECLARE
2. --定义与hr.employees表中的这几个列相同的记录数据类型
3. TYPE RECORD_TYPE_DEPT IS RECORD(
4. F_DEPTNO DEPT.DEPTNO%TYPE,
5. F_NAME DEPT.DNAME%TYPE,
6. F_LOC DEPT.LOC%TYPE);
7. --声明一个该记录数据类型的记录变量
8. V_DEPT_RECORD RECORD_TYPE_DEPT;
9. BEGIN
10. SELECT DEPTNO, DNAME, LOC
11. INTO V_DEPT_RECORD
12. FROM DEPT
13. WHERE DEPTNO = '90';
14. DBMS_OUTPUT.PUT_LINE(V_DEPT_RECORD.F_DEPTNO);
15. END;
一个记录类型的变量只能保存从数据库中查询出的一行记录,若查询出了多行记录,就会出现错误。
2.4.2.2 数组类型
数据是具有相同数据类型的一组成员的集合。每个成员都有一个唯一的下标,它取决于成员在数组中的位置。在PL/SQL中,数组数据类型是VARRAY。
定义VARRY数据类型语法如下:
1. TYPE varray_name IS VARRAY(size) OF element_type [NOT NULL];
varray_name是VARRAY数据类型的名称,size是下整数,表示可容纳的成员的最大数量,每个成员的数据类型是element_type。默认成员可以取空值,否则需要使用NOT NULL加以限制。对于VARRAY数据类型来说,必须经过三个步骤,分别是:定义、声明、初始化。
例6 :
1. DECLARE
2. --定义一个最多保存5个VARCHAR(25)数据类型成员的VARRAY数据类型
3. TYPE REG_VARRAY_TYPE IS VARRAY(5) OF VARCHAR(25);
4. --声明一个该VARRAY数据类型的变量
5. V_REG_VARRAY REG_VARRAY_TYPE;
6. BEGIN
7. --用构造函数语法赋予初值
8. V_REG_VARRAY := REG_VARRAY_TYPE('中国', '美国', '英国', '日本', '法国');
9. DBMS_OUTPUT.PUT_LINE('地区名称:' || V_REG_VARRAY(1) || '、' ||
10. V_REG_VARRAY(2) || '、' || V_REG_VARRAY(3) || '、' || V_REG_VARRAY(4));
11. DBMS_OUTPUT.PUT_LINE('赋予初值NULL的第5个成员的值:' || V_REG_VARRAY(5));
12. --用构造函数语法赋予初值后就可以这样对成员赋值
13. V_REG_VARRAY(5) := '法国';
14. DBMS_OUTPUT.PUT_LINE('第5个成员的值:' || V_REG_VARRAY(5));
15. END;
2.4.2.3 使用%TYPE
定义一个变量,其数据类型与已经定义的某个数据变量(尤其是表的某一列)的数据类型相一致,这时可以使用%TYPE。
使用%TYPE特性的优点在于:
1.所引用的数据库列的数据类型可以不必知道;
2.所引用的数据库列的数据类型可以实时改变,容易保持一致,也不用修改PL/SQL程序。
2.4.3 使用%ROWTYPE
PL/SQL 提供%ROWTYPE操作符, 返回一个记录类型, 其数据类型和数据库表的数据结构相一致。
使用%ROWTYPE特性的优点在于:
1. 所引用的数据库中列的个数和数据类型可以不必知道;
2. 所引用的数据库中列的个数和数据类型可以实时改变,容易保持一致,也不用修改PL/SQL程序。
例7:
1. DECLARE
2. V_EMPNO EMP.EMPNO%TYPE:=&no;
3. REC EMP%ROWTYPE;
4. BEGIN
5. SELECT * INTO REC FROM EMP WHERE EMPNO = V_EMPNO;
6. DBMS_OUTPUT.PUT_LINE('姓名:' || REC.ENAME || '工资:' || REC.SAL || '工作时间:' || REC.HIREDATE);
7. END;
2.4.4 LOB类型
ORACLE提供了LOB (Large OBject)类型,用于存储大的数据对象的类型。ORACLE目前主要支持BFILE, BLOB, CLOB 及 NCLOB 类型。
BFILE (Movie)
存放大的二进制数据对象,这些数据文件不放在数据库里,而是放在操作系统的某个目录里,数据库的表里只存放文件的目录。
BLOB(Photo)
存储大的二进制数据类型。变量存储大的二进制对象的位置。大二进制对象的大小<=4GB。
CLOB(Book)
存储大的字符数据类型。每个变量存储大字符对象的位置,该位置指到大字符数据块。大字符对象的大小<=4GB。
NCLOB
存储大的NCHAR字符数据类型。每个变量存储大字符对象的位置,该位置指到大字符数据块。大字符对象的大小<=4GB。
2.4.5 BIND 变量
绑定变量是在主机环境中定义的变量。在PL/SQL 程序中可以使用绑定变量作为他们将要使用的其它变量。为了在PL/SQL 环境中声明绑定变量,使用命令VARIABLE。例如:
VARIABLE return_code NUMBER
VARIABLE return_msg VARCHAR2(20)
可以通过SQL*Plus命令中的PRINT 显示绑定变量的值。例如:
PRINT return_code
PRINT return_msg
例10:
1. VARIABLE result NUMBER;
2. BEGIN
3. SELECT (sal*10)+nvl(comm, 0) INTO :result FROM emp
4. WHERE empno=7844;
5. END;
6. --然后再执行
7. PRINT result
2.4.6 PL/SQL 表(TABLE)
定义记录表(或索引表)数据类型。它与记录类型相似,但它是对记录类型的扩展。它可以处理多行记录,类似于高级中的二维数组,使得可以在PL/SQL中模仿数据库中的表。
定义记录表类型的语法如下:
1. TYPE table_name IS TABLE OF element_type [NOT NULL]
2. INDEX BY [BINARY_INTEGER | PLS_INTEGER | VARRAY2];
关键字INDEX BY表示创建一个主键索引,以便引用记录表变量中的特定行。
例11:
1. DECLARE
2. TYPE DEPT_TABLE_TYPE IS TABLE OF DEPT%ROWTYPE INDEX BY BINARY_INTEGER;
3. MY_DNAME_TABLE DEPT_TABLE_TYPE;
4. V_COUNT NUMBER(2) := 4;
5. BEGIN
6. FOR I IN 1 .. V_COUNT LOOP
7. SELECT * INTO MY_DNAME_TABLE(I) FROM DEPT WHERE DEPTNO = I * 10;
8. END LOOP;
9. FOR J IN MY_DNAME_TABLE.FIRST .. MY_DNAME_TABLE.LAST LOOP
10. DBMS_OUTPUT.PUT_LINE('Department number: ' || MY_DNAME_TABLE(J).DEPTNO);
11. DBMS_OUTPUT.PUT_LINE('Department name: ' || MY_DNAME_TABLE(J).DNAME);
12. END LOOP;
13. END;
执行结果:
1. Department number: 10
2. Department name: ACCOUNTING
3. Department number: 20
4. Department name: RESEARCH
5. Department number: 30
6. Department name: SALES
7. Department number: 40
8. Department name: OPERATIONS
例12:按一维数组使用记录表
1. DECLARE
2. TYPE REG_TABLE_TYPE IS TABLE OF VARCHAR2(25) INDEX BY BINARY_INTEGER;
3. V_REG_TABLE REG_TABLE_TYPE;
4. BEGIN
5. V_REG_TABLE(1) := 'Europe';
6. V_REG_TABLE(2) := 'Americas';
7. V_REG_TABLE(3) := 'Asia';
8. V_REG_TABLE(4) := 'Middle East and Africa';
9. V_REG_TABLE(5) := 'NULL';
10. DBMS_OUTPUT.PUT_LINE('地区名称:' || V_REG_TABLE(1) || '、' || V_REG_TABLE(2) || '、' ||
11. V_REG_TABLE(3) || '、' || V_REG_TABLE(4));
12. DBMS_OUTPUT.PUT_LINE('第5个成员的值:' || V_REG_TABLE(5));
13. END;
执行结果:
14. 地区名称:Europe、Americas、Asia、Middle East and Africa
15. 第5个成员的值:NULL
例13:按二维数组使用记录表
1. DECLARE
2. --定义记录表数据类型
3. TYPE EMP_TABLE_TYPE IS TABLE OF EMP%ROWTYPE INDEX BY BINARY_INTEGER;
4. --声明记录表数据类型的变量
5. V_EMP_TABLE EMP_TABLE_TYPE;
6. BEGIN
7. SELECT ENAME, HIREDATE, SAL
8. INTO V_EMP_TABLE(1).ENAME,V_EMP_TABLE(1).HIREDATE,V_EMP_TABLE(1).SAL
9. FROM EMP
10. WHERE EMPNO = 7782;
11. SELECT ENAME, HIREDATE, SAL
12. INTO V_EMP_TABLE(2).ENAME,V_EMP_TABLE(2).HIREDATE,V_EMP_TABLE(2).SAL
13. FROM EMP
14. WHERE EMPNO = 7788;
15. DBMS_OUTPUT.PUT_LINE('7782雇员名称:' || V_EMP_TABLE(1).ENAME || ' 雇佣日期:' || V_EMP_TABLE(1).HIREDATE || ' 薪水:' || V_EMP_TABLE(1).SAL);
16. DBMS_OUTPUT.PUT_LINE('7788雇员名称:' || V_EMP_TABLE(2).ENAME || ' 雇佣日期:' || V_EMP_TABLE(2).HIREDATE || ' 薪水:' || V_EMP_TABLE(2).SAL);
17. END;
执行结果:
18. 7782雇员名称:CLARK 雇佣日期:09-6月 -81 薪水:2450
19. 7788雇员名称:SCOTT 雇佣日期:19-4月 -87 薪水:3000
2.5 运算符和表达式(数据定义)
2.5.1 关系运算符
2.5.2 一般运算符
2.5.3 逻辑运算符
2.6 变量赋值
在PL/SQL编程中,变量赋值是一个值得注意的地方,它的语法如下:
variable := expression ;
variable 是一个PL/SQL变量, expression 是一个PL/SQL 表达式.
2.6.1 字符及数字运算特点
空值加数字仍是空值:NULL + < 数字> = NULL
空值加(连接)字符,结果为字符:NULL || <字符串> = < 字符串>
2.6.2 BOOLEAN 赋值
布尔值只有TRUE, FALSE及 NULL 三个值。
2.6.3 数据库赋值
数据库赋值是通过 SELECT语句来完成的,每次执行 SELECT语句就赋值一次,一般要求被赋值的变量与SELECT中的列名要一一对应。如:
例14:
1. DECLARE
2. EMP_ID EMP.EMPNO%TYPE := 7788;
3. EMP_NAME EMP.ENAME%TYPE;
4. WAGES EMP.SAL%TYPE;
5. BEGIN
6. SELECT ENAME, NVL(SAL, 0) + NVL(COMM, 0)
7. INTO EMP_NAME, WAGES
8. FROM EMP
9. WHERE EMPNO = EMP_ID;
10. DBMS_OUTPUT.PUT_LINE(EMP_NAME || '----' || TO_CHAR(WAGES));
11. END;
执行结果:
12. SCOTT----3000
提示:不能将SELECT语句中的列赋值给布尔变量。
2.6.4 可转换的类型赋值
1.CHAR 转换为 NUMBER:
使用 TO_NUMBER 函数来完成字符到数字的转换,如:
v_total := TO_NUMBER('100.0') + sal;
2.NUMBER 转换为CHAR:
使用 TO_CHAR函数可以实现数字到字符的转换,如:
v_comm := TO_CHAR('123.45') || '元' ;
3.字符转换为日期:
使用 TO_DATE函数可以实现 字符到日期的转换,如:
v_date := TO_DATE('2001.07.03','yyyy.mm.dd');
4.日期转换为字符
使用 TO_CHAR函数可以实现日期到字符的转换,如:
v_to_day := TO_CHAR(SYSDATE, 'yyyy.mm.dd hh24:mi:ss') ;
2.7 变量作用范围及可见性
在PL/SQL编程中,变量的作用域是指变量的有效作用范围,特点是:
1.变量的作用范围是在你所引用的程序单元(块、子程序、包)内。即从声明变量开始到该块的结束。
2.一个变量(标识)只能在你所引用的块内是可见的。
3.当一个变量超出了作用范围,PL/SQL引擎就释放用来存放该变量的空间(因为它可能不用了)。
4.在子块中重新定义该变量后,它的作用仅在该块内。
例15:
1. DECLARE
2. EMESS CHAR(80);
3. BEGIN
4. DECLARE
5. V1 NUMBER(4); --定义V1
6. BEGIN
7. SELECT EMPNO INTO V1 FROM EMP WHERE LOWER(JOB) = 'salesman';
8. DBMS_OUTPUT.PUT_LINE(V1);
9. EXCEPTION
10. WHEN TOO_MANY_ROWS THEN
11. DBMS_OUTPUT.PUT_LINE('More than one SALESMAN');
12. END;
13. DECLARE
14. V1 NUMBER(4); --定义V1
15. BEGIN
16. SELECT EMPNO INTO V1 FROM EMP WHERE LOWER(JOB) = 'manager';
17. EXCEPTION
18. WHEN TOO_MANY_ROWS THEN
19. DBMS_OUTPUT.PUT_LINE('More than one manager');
20. END;
21. EXCEPTION
22. WHEN OTHERS THEN
23. EMESS := SUBSTR(SQLERRM, 1, 80);
24. DBMS_OUTPUT.PUT_LINE(EMESS);
25. END;
2.8 注释
在PL/SQL里,可以使用两种符号来写注释,即:使用双 ‘-‘ ( 减号) 加注释
PL/SQL允许用 – 来写注释,它的作用范围是只能在一行有效。如:
V_Sal NUMBER(12,2); -- 人员的工资变量。
使用 /* */ 来加一行或多行注释,如:
/***********************************************/
/* 文件名: department_salary.sql */
/* 作 者: xxx */
/* 时 间: 2011-5-9 */
/***********************************************/
提示:被解释后存放在数据库中的 PL/SQL 程序,一般系统自动将程序头部的注释去掉。只有在 PROCEDURE 之后的注释才被保留;另外程序中的空行也自动被去掉。
3. PL/SQL流程控制语句
介绍PL/SQL的流程控制语句, 包括如下三类:
控制语句: IF 语句
循环语句: LOOP语句, EXIT语句
顺序语句: GOTO语句, NULL语句
3.1 条件语句
1. IF <布尔表达式> THEN
2. PL/SQL 和 SQL语句
3. END IF;
---------------------
4. IF <布尔表达式> THEN
5. PL/SQL 和 SQL语句
6. ELSE
7. 其它语句
8. END IF;
-----------------------
9. IF <布尔表达式> THEN
10. PL/SQL 和 SQL语句
11. ELSIF < 其它布尔表达式> THEN
12. 其它语句
13. ELSIF < 其它布尔表达式> THEN
14. 其它语句
15. ELSE
16. 其它语句
17. END IF;
提示: ELSIF 不能写成 ELSEIF
3.2 CASE 表达式
1. IF <布尔表达式> THEN
2. PL/SQL 和 SQL语句
3. END IF;
---------------------
4. IF <布尔表达式> THEN
5. PL/SQL 和 SQL语句
6. ELSE
7. 其它语句
8. END IF;
-----------------------
9. IF <布尔表达式> THEN
10. PL/SQL 和 SQL语句
11. ELSIF < 其它布尔表达式> THEN
12. 其它语句
13. ELSIF < 其它布尔表达式> THEN
14. 其它语句
15. ELSE
16. 其它语句
17. END IF;
3.3 循环
1. 简单循环
1. LOOP
2. 要执行的语句;
3. EXIT WHEN <条件语句> --条件满足,退出循环语句
4. END LOOP;
例 6:
1. DECLARE
2. I NUMBER(2):=0;
3. BEGIN
4. LOOP
5. I := I + 1;
6. DBMS_OUTPUT.PUT_LINE('i=' || I);
7. EXIT WHEN I = 3;
8. END LOOP;
9. END;
2. WHILE 循环
1. WHILE <布尔表达式> LOOP
2. 要执行的语句;
3. END LOOP;
例7.
1. DECLARE
2. X NUMBER := 1;
3. BEGIN
4. WHILE X < 5 LOOP
5. DBMS_OUTPUT.PUT_LINE('x=' || X);
6. X := X + 1;
7. END LOOP;
8. END;
3. 数字式循环
1. [<<循环标签>>]
2. FOR 循环计数器 IN [ REVERSE ] 下限 .. 上限 LOOP
3. 要执行的语句;
4. END LOOP [循环标签];
每循环一次,循环变量自动加1;使用关键字REVERSE,循环变量自动减1。跟在IN REVERSE 后面的数字必须是从小到大的顺序,而且必须是整数,不能是变量或表达式。可以使用EXIT 退出循环。
例 8.
1. DECLARE
2. V_COUNTER NUMBER := 10;
3. BEGIN
4. INSERT INTO TEMP_TABLE (NUM_COL) VALUES (V_COUNTER);
5. FOR V_COUNTER IN 20 .. 25 LOOP
6. INSERT INTO TEMP_TABLE (NUM_COL) VALUES (V_COUNTER);
7. END LOOP;
8. INSERT INTO TEMP_TABLE (NUM_COL) VALUES (V_COUNTER);
9. FOR V_COUNTER IN REVERSE 20 .. 25 LOOP
10. INSERT INTO TEMP_TABLE (NUM_COL) VALUES (V_COUNTER);
11. END LOOP;
12. COMMIT;
13. END;
例9 在While循环中嵌套loop循环:
1. /*求100至110之间的素数*/
2. DECLARE
3. V_M NUMBER := 101;
4. V_I NUMBER;
5. V_N NUMBER := 0;
6. BEGIN
7. WHILE V_M < 110 LOOP
8. V_I := 2;
9. LOOP
10. IF MOD(V_M, V_I) = 0 THEN
11. V_I := 0;
12. EXIT;
13. END IF;
14. V_I := V_I + 1;
15. EXIT WHEN V_I > V_M - 1;
16. END LOOP;
17. IF V_I > 0 THEN
18. V_N := V_N + 1;
19. DBMS_OUTPUT.PUT_LINE('第' || V_N || '个素数是' || V_M);
20. END IF;
21. V_M := V_M + 2;
22. END LOOP;
23. END;
3.4 标号和GOTO
PL/SQL中GOTO语句是无条件跳转到指定的标号去的意思。语法如下:
1. GOTO label;
2. ......
3. <<label>> /*标号是用<< >>括起来的标识符 */
注意,在以下地方使用是不合法的,编译时会出错误。
u 跳转到非执行语句前面。
u 跳转到子块中。
u 跳转到循环语句中。
u 跳转到条件语句中。
u 从异常处理部分跳转到执行。
u 从条件语句的一部分跳转到另一部分。
例12:
1. DECLARE
2. V_COUNTER NUMBER := 1;
3. BEGIN
4. LOOP
5. DBMS_OUTPUT.PUT_LINE('V_counter的当前值为:' || V_COUNTER);
6. V_COUNTER := V_COUNTER + 1;
7. IF V_COUNTER > 10 THEN
8. GOTO LABELOFFLOOP;
9. END IF;
10. END LOOP;
11. <<LABELOFFLOOP>>
12. DBMS_OUTPUT.PUT_LINE('V_counter的当前值为:' || V_COUNTER);
13. END;
3.5 NULL 语句
在PL/SQL 程序中,NULL语句是一个可执行语句,可以用 null 语句来说明“不用做任何事情”的意思,相当于一个占位符或不执行任何操作的空语句,可以使某些语句变得有意义,提高程序的可读性,保证其他语句结构的完整性和正确性。
例15:
1. DECLARE
2. V_EMP_ID emp.empno%TYPE;
3. V_FIRST_NAME emp.ename%TYPE;
4. V_SALARY emp.sal%TYPE;
5. V_SAL_RAISE NUMBER(3, 2);
6. BEGIN
7. V_EMP_ID:=#
8. SELECT ename, sal
9. INTO V_FIRST_NAME, V_SALARY
10. FROM emp
11. WHERE empno = V_EMP_ID;
12. IF V_SALARY <= 3000 THEN
13. V_SAL_RAISE := .10;
14. DBMS_OUTPUT.PUT_LINE(V_FIRST_NAME || '的工资是' || V_SALARY || '、工资涨幅是' ||V_SAL_RAISE);
15. ELSE
16. NULL;
17. END IF;
18. END;
4. 游标
4.1 游标概念
在PL/SQL块中执行SELECT、INSERT、DELETE和UPDATE语句时,ORACLE会在内存中为其分配上下文区(Context Area),即缓冲区。游标是指向该区的一个指针,或是命名一个工作区(Work Area),或是一种结构化数据类型。它为应用等量齐观提供了一种对具有多行数据查询结果集中的每一行数据分别进行单独处理的方法,是设计嵌入式SQL语句的应用程序的常用编程方式。
在每个用户会话中,可以同时打开多个游标,其数量由数据库初始化参数文件中的OPEN_CURSORS参数定义。
对于不同的SQL语句,游标的使用情况不同:
4.1.1 处理显式游标
1. 显式游标处理
显式游标处理需四个 PL/SQL步骤:
1)定义/声明游标:就是定义一个游标名,以及与其相对应的SELECT 语句。
格式:
1. CURSOR cursor_name[(parameter[, parameter]…)]
2. [RETURN datatype]
3. IS
4. select_statement;
游标参数只能为输入参数,其格式为:
5. parameter_name [IN] datatype [{:= | DEFAULT} expression]
在指定数据类型时,不能使用长度约束。如NUMBER(4),CHAR(10) 等都是错误的。
[RETURN datatype]是可选的,表示游标返回数据的数据。如果选择,则应该严格与select_statement中的选择列表在次序和数据类型上匹配。一般是记录数据类型或带“%ROWTYPE”的数据。
2)打开游标:就是执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有FOR UPDATE选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。
格式:
6. OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];
在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示法。PL/SQL 程序不能用OPEN 语句重复打开一个游标。
3)提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。
格式:
7. FETCH cursor_name INTO {variable_list | record_variable };
执行FETCH语句时,每次返回一个数据行,然后自动将游标移动指向下一个数据行。当检索到最后一行数据时,如果再次执行FETCH语句,将操作失败,并将游标属性%NOTFOUND置为TRUE。所以每次执行完FETCH语句后,检查游标属性%NOTFOUND就可以判断FETCH语句是否执行成功并返回一个数据行,以便确定是否给对应的变量赋了值。
对该记录进行处理;
继续处理,直到活动集合中没有记录;
4)关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用FETCH 语句取其中数据。关闭后的游标可以使用OPEN 语句重新打开。
格式:
8. CLOSE cursor_name;
注:定义的游标不能有INTO子句。
例1. 查询前10名员工的信息。
1. DECLARE
2. CURSOR C_CURSOR IS
3. SELECT ENAME, SAL FROM EMP WHERE ROWNUM < 11;
4. V_ENAME EMP.ENAME%TYPE;
5. V_SAL EMP.SAL%TYPE;
6. BEGIN
7. OPEN C_CURSOR;
8. FETCH C_CURSOR
9. INTO V_ENAME, V_SAL;
10. WHILE C_CURSOR%FOUND LOOP
11. DBMS_OUTPUT.PUT_LINE(V_ENAME || '---' || V_SAL);
12. FETCH C_CURSOR
13. INTO V_ENAME, V_SAL;
14. END LOOP;
15. END;
例2. 游标参数的传递方法。
1. DECLARE
2. V_ENAME VARCHAR2(20);
3. CURSOR C_EMP(DEPT_NO NUMBER DEFAULT 23) IS
4. SELECT DNAME FROM DEPT WHERE DEPTNO = DEPT_NO;
5. BEGIN
6. OPEN C_EMP;
7. LOOP
8. FETCH C_EMP
9. INTO V_ENAME;
10. EXIT WHEN C_EMP%NOTFOUND;
11. DBMS_OUTPUT.PUT_LINE(V_ENAME);
12. END LOOP;
13. END;
2.游标属性
Cursor_name%FOUND 布尔型属性,当最近一次提取游标操作FETCH成功则为 TRUE,否则为FALSE;
Cursor_name%NOTFOUND 布尔型属性,与%FOUND相反;
Cursor_name%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;
Cursor_name%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。
例3:给工资低于1200 的员工增加工资50。
1. DECLARE
2. V_EMPNO EMP.EMPNO%TYPE;
3. V_SAL EMP.SAL%TYPE;
4. CURSOR C_CURSOR IS
5. SELECT EMPNO, SAL FROM EMP;
6. BEGIN
7. OPEN C_CURSOR;
8. LOOP
9. FETCH C_CURSOR
10. INTO V_EMPNO, V_SAL;
11. EXIT WHEN C_CURSOR%NOTFOUND;
12. IF V_SAL <= 1200 THEN
13. UPDATE EMP SET SAL = SAL + 50 WHERE EMPNO = V_EMPNO;
14. DBMS_OUTPUT.PUT_LINE('编码为' || V_EMPNO || '工资已更新!');
15. END IF;
16. END LOOP;
17. CLOSE C_CURSOR;
18. END;
例4:没有参数且没有返回值的游标。
1. DECLARE
2. V_ENAME EMP.ENAME%TYPE;
3. V_EMPNO EMP.EMPNO%TYPE;
4. CURSOR C1 IS
5. SELECT ENAME, EMPNO FROM EMP WHERE EMPNO = '8800';
6. BEGIN
7. OPEN C1;
8. LOOP
9. FETCH C1
10. INTO V_ENAME, V_EMPNO;
11. IF C1%FOUND THEN
12. DBMS_OUTPUT.PUT_LINE(V_ENAME);
13. ELSE
14. DBMS_OUTPUT.PUT_LINE('处理完成');
15. EXIT;
16. END IF;
17. END LOOP;
18. CLOSE C1;
19. END;
例5:有参数且没有返回值的游标。
1. DECLARE
2. V_HIREDATE EMP.HIREDATE%TYPE;
3. V_SAL EMP.SAL%TYPE;
4. CURSOR C2(V_ENAME VARCHAR2, V_EMPNO NUMBER) IS --参数
5. SELECT HIREDATE, SAL
6. INTO V_HIREDATE, V_SAL
7. FROM EMP
8. WHERE ENAME = V_ENAME
9. AND EMPNO = V_EMPNO;
10. BEGIN
11. OPEN C2('hua','8000'); --传入参数
12. LOOP
13. FETCH C2
14. INTO V_HIREDATE, V_SAL;
15. IF C2%FOUND THEN
16. DBMS_OUTPUT.PUT_LINE(V_HIREDATE|| V_SAL);
17. ELSE
18. DBMS_OUTPUT.PUT_LINE('处理完成');
19. EXIT;
20. END IF;
21. END LOOP;
22. END;
例6:有参数且有返回值的游标
1. DECLARE
2. TYPE EMP_RECORD_TYPE IS RECORD(
3. V_HIREDATE EMP.HIREDATE%TYPE,
4. V_SAL EMP.SAL%TYPE);
5. V_EMP_RECORD EMP_RECORD_TYPE;
6. CURSOR C3(V_ENAME VARCHAR2, V_EMPNO NUMBER) --声明游标,有参数有返回值
7. RETURN EMP_RECORD_TYPE --返回值
8. IS
9. SELECT HIREDATE, SAL
10. INTO V_EMP_RECORD
11. FROM EMP
12. WHERE ENAME = V_ENAME
13. AND EMPNO = V_EMPNO;
14. BEGIN
15. OPEN C3('hua', '8000'); --打开游标,传递参数值
16. LOOP
17. FETCH C3
18. INTO V_EMP_RECORD; --提取游标
19. IF C3%FOUND THEN
20. DBMS_OUTPUT.PUT_LINE(V_EMP_RECORD.V_HIREDATE || V_EMP_RECORD.V_SAL);
21. ELSE
22. DBMS_OUTPUT.PUT_LINE('处理完成');
23. EXIT;
24. END IF;
25. END LOOP;
26. CLOSE C3;
27. END;
例7:基于游标定义记录变量。
1. DECLARE
2. CURSOR C4 IS
3. SELECT * FROM EMP WHERE JOB = 'MANAGER';
4. EMP_RECORD EMP%ROWTYPE;
5. BEGIN
6. OPEN C4;
7. LOOP
8. FETCH C4
9. INTO EMP_RECORD;
10. IF C4%FOUND THEN
11. DBMS_OUTPUT.PUT_LINE(EMP_RECORD.HIREDATE || EMP_RECORD.SAL);
12. ELSE
13. DBMS_OUTPUT.PUT_LINE('处理完成');
14. EXIT;
15. END IF;
16. END LOOP;
17. CLOSE C4;
18. END;
3. 游标的FOR循环
PL/SQL语言提供了游标FOR循环语句,自动执行游标的OPEN、FETCH、CLOSE语句和循环语句的功能;当进入循环时,游标FOR循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数据而进入下一次循环时,游标FOR循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标。
格式:
1. FOR index_variable IN cursor_name[(value[, value]…)]
2. LOOP
3. -- 游标数据处理代码
4. END LOOP;
其中:
index_variable为游标FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable中各元素的名称与游标查询语句选择列表中所制定的列名相同。如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标FOR 循环语句中的索引变量来访问这些列数据。
注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制FOR循环的记录。
例8:
1. DECLARE
2. CURSOR C IS
3. SELECT * FROM EMP WHERE JOB = 'MANAGER';
4. BEGIN
5. --隐含打开游标
6. FOR V_C IN C LOOP
7. --隐含执行一个FETCH语句
8. DBMS_OUTPUT.PUT_LINE(V_C.EMPNO || '--' || V_C.ENAME);
9. --隐含监测c_sal%NOTFOUND
10. END LOOP;
11. --隐含关闭游标
12. END;
例9:当所声明的游标带有参数时,通过游标FOR 循环语句为游标传递参数。
1. DECLARE
2. CURSOR C1( V_NAME VARCHAR2,V_NO NUMBER) IS
3. SELECT *
4. FROM EMP
5. WHERE ENAME = V_NAME
6. AND EMPNO = V_NO;
7. BEGIN
8. FOR V_C IN C1('hua', '8000') LOOP
9. DBMS_OUTPUT.PUT_LINE(V_C.EMPNO || '--' || V_C.ENAME);
10. END LOOP;
11. END;
例10:PL/SQL还允许在游标FOR循环语句中使用子查询来实现游标的功能。
1. BEGIN
2. FOR C1_REC IN (SELECT DNAME, DEPtNO FROM DEPT) LOOP
3. DBMS_OUTPUT.PUT_LINE(C1_REC.DNAME);
4. END LOOP;
5. END;
4.1.2 处理隐式游标
显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为SQL,这是由ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。
格式调用为: SQL%
注:INSERT, UPDATE, DELETE, SELECT 语句中不必明确定义游标。
隐式游标属性
例11: 通过隐式游标SQL的%ROWCOUNT属性来了解修改了多少行。
1. DECLARE
2. V_ROWS NUMBER;
3. BEGIN
4. --更新数据
5. UPDATE EMP SET SAL = 30000 WHERE JOB = 'SALESMAN';
6. --获取默认游标的属性值
7. V_ROWS := SQL%ROWCOUNT;
8. DBMS_OUTPUT.PUT_LINE('更新了' || V_ROWS || '个雇员的工资');
9. --回退更新,以便使数据库的数据保持原样
10. ROLLBACK;
11. END;
4.1.3 关于 NO_DATA_FOUND 和 %NOTFOUND的区别
SELECT … INTO 语句触发 NO_DATA_FOUND;
当一个显式游标的WHERE子句未找到时触发%NOTFOUND;当UPDATE或DELETE 语句的WHERE 子句未找到时触发 SQL%NOTFOUND;在提取循环中要用 %NOTFOUND 或%FOUND 来确定循环的退出条件,不要用 NO_DATA_FOUND。
4.1.4 使用游标更新和删除数据
游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中必须使用FOR UPDATE选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。
为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使ORACLE锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。
语法:
1. SELECT column_list FROM table_list FOR UPDATE [OF column[, column]…] [NOWAIT]
如果另一个会话已对活动集中的行加了锁,那么SELECT FOR UPDATE操作一直等待到其它的会话释放这些锁后才继续自己的操作,对于这种情况,当加上NOWAIT子句时,如果这些行真的被另一个会话锁定,则OPEN立即返回并给出:
ORA-0054 :resource busy and acquire with nowait specified.
如果使用 FOR UPDATE 声明游标,则可在DELETE和UPDATE 语句中使用
WHERE CURRENT OF cursor_name子句,修改或删除游标结果集合当前行对应的数据库表中的数据行。
例13:从EMPLOYEES表中查询某部门的员工情况,将其工资增加5000;
1. DECLARE
2. V_EMPNO NUMBER := 8000;
3. V_SAL EMP.SAL%TYPE;
4. CURSOR C IS
5. SELECT SAL INTO V_SAL FROM EMP WHERE EMPNO = V_EMPNO FOR UPDATE NOWAIT;
6. BEGIN
7. FOR V_C IN C LOOP
8. IF V_C.SAL < 5000 THEN
9. UPDATE EMP SET SAL = SAL + 5000 WHERE CURRENT OF C; --更新当前游标行对应的数据
10. END IF;
11. END LOOP;
12. COMMIT;
13. END;
4.2 游标变量
与游标一样,游标变量也是一个指向多行查询结果集合中当前数据行的指针。但与游标不同的是,游标变量是动态的,而游标是静态的。游标只能与指定的查询相连,即固定指向一个查询的内存处理区域,而游标变量则可与不同的查询语句相连,它可以指向不同查询语句的内存处理区域(但不能同时指向多个内存处理区域,在某一时刻只能与一个查询语句相连),只要这些查询语句的返回类型兼容即可。
4.2.1 声明游标变量
游标变量为一个指针,它属于参照类型,所以在声明游标变量类型之前必须先定义游标变量类型。在PL/SQL中,可以在块、子程序和包的声明区域内定义游标变量类型。
语法格式为:
1. TYPE ref_type_name IS REF CURSOR
2. [ RETURN return_type];
其中:ref_type_name为新定义的游标变量类型名称;
return_type 为游标变量的返回值类型,它必须为记录变量。
在定义游标变量类型时,可以采用强类型定义和弱类型定义两种。强类型定义必须指定游标变量的返回值类型,而弱类型定义则不说明返回值类型。
声明一个游标变量的两个步骤:
步骤一:定义一个REF CURSOU数据类型,如:TYPE ref_cursor_type IS REF CURSOR;
步骤二:声明一个该数据类型的游标变量,如: cv_ref REF_CURSOR_TYPE;
例:创建两个强类型定义游标变量和一个弱类型游标变量:
1. DECLARE
2. TYPE DEPTRECORD IS RECORD(
3. DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE,
4. DNAME DEPARTMENTS.DEPARTMENT_NAME%TYPE,
5. LOC DEPARTMENTS.LOCATION_ID%TYPE);
6. --强类型游标变量
7. TYPE DEPTCURTYPE IS REF CURSOR RETURN DEPARTMENTS%ROWTYPE;
8. TYPE DEPTCURTYP1 IS REF CURSOR RETURN DEPTRECORD;
9. --弱类型游标变量
10. TYPE CURTYPE IS REF CURSOR;
11. DEPT_C1 DEPTCURTYPE;
12. DEPT_C2 DEPTCURTYP1;
13. CV CURTYPE;
14. BEGIN
15. NULL;
16. END;
4.2.2 游标变量操作
与游标一样,游标变量操作也包括打开、提取和关闭三个步骤。
1. 打开游标变量
打开游标变量时使用的是OPEN…FOR 语句。格式为:
1. OPEN {cursor_variable_name | :host_cursor_variable_name}
2. FOR select_statement;
其中:cursor_variable_name为游标变量,host_cursor_variable_name为PL/SQL主机环境(如OCI: ORACLE Call Interface,Pro*c 程序等)中声明的游标变量。
OPEN…FOR 语句可以在关闭当前的游标变量之前重新打开游标变量,而不会导致CURSOR_ALREAD_OPEN异常错误。新打开游标变量时,前一个查询的内存处理区将被释放。
2. 提取游标变量数据
使用FETCH语句提取游标变量结果集合中的数据。格式为:
1. FETCH {cursor_variable_name | :host_cursor_variable_name}
2. INTO {variable [, variable]…| record_variable};
其中:cursor_variable_name和host_cursor_variable_name分别为游标变量和宿主游标变量名称;variable和record_variable分别为普通变量和记录变量名称。
3. 关闭游标变量
CLOSE语句关闭游标变量,格式为:
3. CLOSE {cursor_variable_name | :host_cursor_variable_name}
其中:cursor_variable_name和host_cursor_variable_name分别为游标变量和宿主游标变量名称,如果应用程序试图关闭一个未打开的游标变量,则将导致INVALID_CURSOR异常错误。
例15:强类型参照游标变量类型
1. DECLARE
2. TYPE EMP_JOB_REC IS RECORD(
3. V_ENAME EMP.ENAME%TYPE,
4. V_SAL EMP.SAL%TYPE);
5. TYPE EMP_JOB_REF IS REF CURSOR RETURN EMP_JOB_REC;
6. EMP_REC EMP_JOB_REC;
7. EMP_REF EMP_JOB_REF;
8. BEGIN
9. OPEN EMP_REF FOR
10. SELECT ENAME, EMPNO INTO EMP_REC FROM EMP;
11. FETCH EMP_REF
12. INTO EMP_REC;
13. WHILE EMP_REF%FOUND LOOP
14. DBMS_OUTPUT.PUT_LINE(EMP_REC.V_ENAME || '--' || EMP_REC.V_SAL);
15. FETCH EMP_REF
16. INTO EMP_REC;
17. END LOOP;
18. END;
例16:使用游标变量(没有RETURN子句)
1. DECLARE
2. --定义一个游标数据类型
3. TYPE EMP_CURSOR_TYPE IS REF CURSOR;
4. --声明一个游标变量
5. C1 EMP_CURSOR_TYPE;
6. --声明记录变量
7. V_EMP_REC EMP%ROWTYPE;
8. BEGIN
9. OPEN C1 FOR
10. SELECT * FROM EMP;
11. LOOP
12. ;
13. FETCH C1
14. INTO V_EMP_REC;
15. EXIT WHEN C1%NOTFOUND;
16. DBMS_OUTPUT.PUT_LINE(V_EMP_REC.ENAME);
17. END LOOP;
18. CLOSE C1;
19. END;
例17:使用游标变量(有RETURN子句)
1. DECLARE
2. --定义一个与emp表中的这几个列相同的记录数据类型
3. TYPE EMP_RECORD_TYPE IS RECORD(
4. F_NAME EMP.ENAME%TYPE,
5. H_DATE EMP.HIREDATE%TYPE,
6. J_ID EMP.JOB%TYPE);
7. --声明一个该记录数据类型的记录变量
8. V_EMP_RECORD EMP_RECORD_TYPE;
9. --定义一个游标数据类型
10. TYPE EMP_CURSOR_TYPE IS REF CURSOR RETURN EMP_RECORD_TYPE;
11. --声明一个游标变量
12. C1 EMP_CURSOR_TYPE;
13. BEGIN
14. OPEN C1 FOR
15. SELECT ENAME, HIREDATE, JOB FROM EMP;
16. LOOP
17. FETCH C1
18. INTO V_EMP_RECORD;
19. EXIT WHEN C1%NOTFOUND;
20. DBMS_OUTPUT.PUT_LINE('雇员名称:' || V_EMP_RECORD.F_NAME || ' 雇佣日期:' || V_EMP_RECORD.H_DATE || ' 岗位:' || V_EMP_RECORD.J_ID);
21. END LOOP;
22. CLOSE C1;
23. END;
5. 异常错误处理
ORACLE 提供异常情况(EXCEPTION)和异常处理(EXCEPTION HANDLER)来实现错误处理。
5.1 异常处理概念
异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件,程序块的异常处理预定义的错误和自定义错误,由于PL/SQL程序块一旦产生异常而没有指出如何处理时,程序就会自动终止整个程序运行.
有三种类型的异常错误:
1. 预定义 ( Predefined )错误
ORACLE预定义的异常情况大约有24个。对这种异常情况的处理,无需在程序中定义,由ORACLE自动将其引发。
2. 非预定义 ( Predefined )错误
即其他标准的ORACLE错误。对这种异常情况的处理,需要用户在程序中定义,然后由ORACLE自动将其引发。
3. 用户定义(User_define) 错误
程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要用户在程序中定义,然后显式地在程序中将其引发。
异常处理部分一般放在 PL/SQL 程序体的后半部,结构为:
1. EXCEPTION
2. WHEN first_exception THEN <code to handle first exception >
3. WHEN second_exception THEN <code to handle second exception >
4. WHEN OTHERS THEN <code to handle others exception >
5. END;
异常处理可以按任意次序排列,但 OTHERS 必须放在最后.
5.1.1 预定义的异常处理
预定义说明的部分 ORACLE 异常错误
对这种异常情况的处理,只需在PL/SQL块的异常处理部分,直接引用相应的异常情况名,并对其完成相应的异常错误处理即可。
例1:更新指定员工工资,如工资小于1500,则加100;
1. DECLARE
2. V_EMPNO EMP.EMPNO%TYPE := 8000;
3. V_SAL EMP.SAL%TYPE;
4. BEGIN
5. SELECT SAL INTO V_SAL FROM EMP WHERE EMPNO = V_EMPNO;
6. IF V_SAL <= 9000 THEN
7. UPDATE EMP SET SAL = SAL + 100 WHERE EMPNO = V_EMPNO;
8. DBMS_OUTPUT.PUT_LINE('编码为' || V_EMPNO || '员工工资已更新!');
9. ELSE
10. DBMS_OUTPUT.PUT_LINE('编码为' || V_EMPNO || '员工工资已经超过规定值!');
11. END IF;
12. EXCEPTION
13. WHEN NO_DATA_FOUND THEN
14. DBMS_OUTPUT.PUT_LINE('数据库中没有编码为' || V_EMPNO || '的员工');
15. WHEN TOO_MANY_ROWS THEN
16. DBMS_OUTPUT.PUT_LINE('程序运行错误!请使用游标');
17. WHEN OTHERS THEN
18. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
19. END;
5.1.2 非预定义的异常处理
对于这类异常情况的处理,首先必须对非定义的ORACLE错误进行定义。步骤如下:
1. 在PL/SQL 块的定义部分定义异常情况:
<异常情况> EXCEPTION;
2. 将其定义好的异常情况,与标准的ORACLE错误联系起来,使用EXCEPTION_INIT语句:
1. PRAGMA EXCEPTION_INIT(<异常情况>, <错误代码>);
3. 在PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。
例2:删除指定部门的记录信息,以确保该部门没有员工。
1. DECLARE
2. V_DEPTNO DEPARTMENTS.DEPARTMENT_ID%TYPE := &DEPTNO;
3. DEPTNO_REMAINING EXCEPTION;
4. PRAGMA EXCEPTION_INIT(DEPTNO_REMAINING, -2292);
5. /* -2292 是违反一致性约束的错误代码 */
6. BEGIN
7. DELETE FROM DEPARTMENTS WHERE DEPARTMENT_ID = V_DEPTNO;
8. EXCEPTION
9. WHEN DEPTNO_REMAINING THEN
10. DBMS_OUTPUT.PUT_LINE('违反数据完整性约束!');
11. WHEN OTHERS THEN
12. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
13. END;
5.1.3 用户自定义的异常处理
当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。用户定义的异常错误是通过显式使用 RAISE 语句来触发。当引发一个异常错误时,控制就转向到 EXCEPTION块异常错误部分,执行错误处理代码。
对于这类异常情况的处理,步骤如下:
1. 在PL/SQL 块的定义部分定义异常情况:
<异常情况> EXCEPTION;
2. RAISE <异常情况>;
3. 在PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。
例3:更新指定员工工资,增加100;
1. DECLARE
2. V_EMPNO EMP.EMPNO%TYPE := 8001;
3. NO_RESULT EXCEPTION;
4. BEGIN
5. UPDATE EMP SET SAL = SAL + 1000 WHERE EMPNO = V_EMPNO;
6. IF SQL%NOTFOUND THEN
7. RAISE NO_RESULT;
8. END IF;
9. EXCEPTION
10. WHEN NO_RESULT THEN
11. DBMS_OUTPUT.PUT_LINE('你的数据更新语句失败了!');
12. WHEN OTHERS THEN
13. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
14. END;
5.1.4 用户定义的异常处理
调用DBMS_STANDARD(ORACLE提供的包)包所定义的RAISE_APPLICATION_ERROR过程,可以重新定义异常错误消息,它为应用程序提供了一种与ORACLE交互的方法。
RAISE_APPLICATION_ERROR 的语法如下:
1. RAISE_APPLICATION_ERROR(error_number,error_message,[keep_errors] );
这里的error_number 是从 –20,000 到 –20,999 之间的参数,
error_message 是相应的提示信息(< 2048 字节),
keep_errors 为可选,如果keep_errors =TRUE ,则新错误将被添加到已经引发的错误列表中。如果keep_errors=FALSE(缺省),则新错误将替换当前的错误列表。
例4:创建一个函数get_salary, 该函数检索指定部门的工资总和,其中定义了-20991和-20992号错误,分别处理参数为空和非法部门代码两种错误:
1. CREATE TABLE errlog(
2. Errcode NUMBER,
3. Errtext CHAR(40)); --建立ERRLOG表存储错误信息
--定义函数:
1. CREATE OR REPLACE FUNCTION GET_SAL(NO NUMBER) RETURN NUMBER AS
2. V_SAL NUMBER;
3. BEGIN
4. IF NO IS NULL THEN
5. RAISE_APPLICATION_ERROR(-20991, '部门号为空');
6. ELSIF NO < 0 THEN
7. RAISE_APPLICATION_ERROR(-20992, '无效的部门代码');
8. ELSE
9. SELECT SUM(SAL) INTO V_SAL FROM EMP WHERE EMPNO = NO;
10. RETURN V_SAL;
11. END IF;
12. END GET_SAL;
调用函数Get_sal()
1. DECLARE
2. V_SALARY NUMBER(7, 2);
3. V_SQLCODE NUMBER;
4. V_SQLERR VARCHAR2(512);
5. NULL_DEPTNO EXCEPTION;
6. INVALID_DEPTNO EXCEPTION;
7. PRAGMA EXCEPTION_INIT(NULL_DEPTNO, -20991);
8. PRAGMA EXCEPTION_INIT(INVALID_DEPTNO, -20992);
9. BEGIN
10. V_SALARY := GET_SAL(8000);
11. DBMS_OUTPUT.PUT_LINE('8000号员工工资:' || TO_CHAR(V_SALARY));
12. BEGIN
13. V_SALARY := GET_SAL(-10);
14. EXCEPTION
15. WHEN INVALID_DEPTNO THEN
16. V_SQLCODE := SQLCODE;
17. V_SQLERR := SQLERRM;
18. –异常存入数据库
19. INSERT INTO ERRLOG (ERRCODE, ERRTEXT) VALUES (V_SQLCODE, V_SQLERR);
20. COMMIT;
21. END INNER1;
22. V_SALARY := GET_SAL(20);
23. DBMS_OUTPUT.PUT_LINE('20的工资为:' || TO_CHAR(V_SALARY));
24. BEGIN
25. V_SALARY := GET_SAL(NULL);
26. END INNER2;
27. V_SALARY := GET_SAL(30);
28. DBMS_OUTPUT.PUT_LINE('30的工资为:' || TO_CHAR(V_SALARY));
29. EXCEPTION
30. WHEN NULL_DEPTNO THEN
31. V_SQLCODE := SQLCODE;
32. V_SQLERR := SQLERRM;
33. INSERT INTO ERRLOG (ERRCODE, ERRTEXT) VALUES (V_SQLCODE, V_SQLERR);
34. COMMIT;
35. WHEN OTHERS THEN
36. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
37. END OUTER;
例5:定义触发器,使用RAISE_APPLICATION_ERROR阻止没有员工姓名的新员式记录插入:
1. CREATE OR REPLACE TRIGGER TR_INSERT_EMP
2. BEFORE INSERT ON EMP
3. FOR EACH ROW
4. BEGIN
5. IF :NEW.ENAME IS NULL THEN
6. RAISE_APPLICATION_ERROR(-20000, 'Employee must have a name.');
7. END IF;
8. END;
5.2 异常错误传播
由于异常错误可以在声明部分和执行部分以及异常错误部分出现,因而在不同部分引发的异常错误也不一样。
5.2.1 在执行部分引发异常错误
当一个异常错误在执行部分引发时,有下列情况:
l 如果当前块对该异常错误设置了处理,则执行它并成功完成该块的执行,然后控制转给包含块。
2 如果没有对当前块异常错误设置定义处理器,则通过在包含块中引发它来传播异常错误。然后对该包含块执行步骤1)。
5.2.2 在声明部分引发异常错误
如果在声明部分引起异常情况,即在声明部分出现错误,那么该错误就能影响到其它的块。比如在有如下的PL/SQL程序:
1. DECLARE
2. name varchar2(12):='EricHu';
3. 其它语句
4. BEGIN
5. 其它语句
6. EXCEPTION
7. WHEN OTHERS THEN
8. 其它语句
9. END;
例子中,由于name varchar2(12):='EricHu'; 出错,尽管在EXCEPTION中说明了WHEN OTHERS THEN语句,但WHEN OTHERS THEN也不会被执行。 但是如果在该错误语句块的外部有一个异常错误,则该错误能被抓住,如:
1. DECLARE
2. name varchar2(12):='EricHu';
3. 其它语句
4. BEGIN
5. 其它语句
6. EXCEPTION
7. WHEN OTHERS THEN
8. 其它语句
9. END;
10. EXCEPTION
11. WHEN OTHERS THEN
12. 其它语句
13. END;
5.3 异常错误处理编程
在一般的应用处理中,建议程序人员要用异常处理,因为如果程序中不声明任何异常处理,则在程序运行出错时,程序就被终止,并且也不提示任何信息。下面是使用系统提供的异常来编程的例子。
5.4 在 PL/SQL 中使用 SQLCODE, SQLERRM异常处理函数
由于ORACLE 的错信息最大长度是512字节,为了得到完整的错误提示信息,我们可用 SQLERRM和 SUBSTR 函数一起得到错误提示信息,方便进行错误,特别是如果WHEN OTHERS异常处理器时更为方便。
SQLCODE 返回遇到的Oracle错误号,
SQLERRM 返回遇到的Oracle错误信息.
如: SQLCODE=-100 SQLERRM=’no_data_found ‘
SQLCODE=0 SQLERRM=’normal, successfual completion’
例8. 利用ORACLE错误代码,编写异常错误处理代码;
1. DECLARE
2. EMPNO_REMAINING EXCEPTION;
3. PRAGMA EXCEPTION_INIT(EMPNO_REMAINING, -1);
4. /* -1 是违反唯一约束条件的错误代码 */
5. BEGIN
6. INSERT INTO EMPLOYEES
7. (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, HIRE_DATE, DEPARTMENT_ID)
8. VALUES
9. (3333, 'Eric', 'Hu', SYSDATE, 20);
10. DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
11. INSERT INTO EMPLOYEES
12. (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, HIRE_DATE, DEPARTMENT_ID)
13. VALUES
14. (3333, '胡', '勇', SYSDATE, 20);
15. DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
16. EXCEPTION
17. WHEN EMPNO_REMAINING THEN
18. DBMS_OUTPUT.PUT_LINE('违反数据完整性约束!');
19. WHEN OTHERS THEN
20. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
21. END;
6. 过程与函数
6.1 引言
过程与函数(另外还有包与触发器)是命名的PL/SQL块(也是用户的方案对象),被编译后存储在数据库中,以备执行。因此,其它PL/SQL块可以按名称来使用他们。所以,可以将商业逻辑、企业规则写成函数或过程保存到数据库中,以便共享。
过程和函数统称为PL/SQL子程序,他们是被命名的PL/SQL块,均存储在数据库中,并通过输入、输出参数或输入/输出参数与其调用者交换信息。过程和函数的唯一区别是函数总向调用者返回数据,而过程则不返回数据。在本节中,主要介绍:
1. 创建存储过程和函数。
2. 正确使用系统级的异常处理和用户定义的异常处理。
3. 建立和管理存储过程和函数。
6.2 创建函数
1. 创建函数
语法如下:
1. CREATE [OR REPLACE] FUNCTION function_name
2. (arg1 [ { IN | OUT | IN OUT }] type1 [DEFAULT value1],
3. [arg2 [ { IN | OUT | IN OUT }] type2 [DEFAULT value1]],
4. ......
5. [argn [ { IN | OUT | IN OUT }] typen [DEFAULT valuen]])
6. [ AUTHID DEFINER | CURRENT_USER ]
7. RETURN return_type
8. IS | AS
9. <类型.变量的声明部分>
10. BEGIN
11. 执行部分
12. RETURN expression
13. EXCEPTION
14. 异常处理部分
15. END function_name;
IN,OUT,IN OUT是形参的模式。若省略,则为IN模式。IN模式的形参只能将实参传递给形参,进入函数内部,但只能读不能写,函数返回时实参的值不变。OUT模式的形参会忽略调用时的实参值(或说该形参的初始值总是NULL),但在函数内部可以被读或写,函数返回时形参的值会赋予给实参。IN OUT具有前两种模式的特性,即调用时,实参的值总是传递给形参,结束时,形参的值传递给实参。调用时,对于IN模式的实参可以是常量或变量,但对于OUT和IN OUT模式的实参必须是变量。
一般,只有在确认function_name函数是新函数或是要更新的函数时,才使用OR REPALCE关键字,否则容易删除有用的函数。
例1. 获取某部门的工资总和:
1. ----获取某部门的工资总和
2. CREATE OR REPLACE FUNCTION GET_SUMSAL(EMP_NO IN NUMBER,
3. EMP_COUNT OUT NUMBER) RETURN NUMBER IS
4. V_SUM NUMBER;
5. BEGIN
6. SELECT SUM(SAL), COUNT(*)
7. INTO V_SUM, EMP_COUNT
8. FROM EMP
9. WHERE EMPNO = EMP_NO;
10. RETURN V_SUM;
11. EXCEPTION
12. WHEN NO_DATA_FOUND THEN
13. DBMS_OUTPUT.PUT_LINE('你需要的数据不存在!');
14. WHEN OTHERS THEN
15. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
16. RETURN(V_SUM);
17. END GET_SUMSAL;
2. 函数的调用
函数声明时所定义的参数称为形式参数,应用程序调用时为函数传递的参数称为实际参数。应用程序在调用函数时,可以使用以下三种方法向函数传递参数:
第一种参数传递格式:位置表示法。
即在调用时按形参的排列顺序,依次写出实参的名称,而将形参与实参关联起来进行传递。用这种方法进行调用,形参与实参的名称是相互独立,没有关系,强调次序才是重要的。
格式为:
16. argument_value1[,argument_value2 …]
例2:计算某部门的工资总和:
1. DECLARE
2. V_num NUMBER;
3. V_sum NUMBER;
4. BEGIN
5. V_sum := GET_SUMSAL(10, v_num);
6. DBMS_OUTPUT.PUT_LINE('部门号为:10的工资总和:'||v_sum||',人数为:'||v_num);
7. END;
第二种参数传递格式:名称表示法。
即在调用时按形参的名称与实参的名称,写出实参对应的形参,而将形参与实参关联起来进行传递。这种方法,形参与实参的名称是相互独立的,没有关系,名称的对应关系才是最重要的,次序并不重要。
格式为:
argument => parameter [,…]
其中:argument 为形式参数,它必须与函数定义时所声明的形式参数名称相同parameter 为实际参数。
在这种格式中,形势参数与实际参数成对出现,相互间关系唯一确定,所以参数的顺序可以任意排列。
例3:计算某部门的工资总和:
1. DECLARE
2. V_num NUMBER;
3. V_sum NUMBER;
4. BEGIN
5. V_sum :=get_sumsal(emp_count => v_num, dept_no => 10);
6. DBMS_OUTPUT.PUT_LINE('部门号为:10的工资总和:'||v_sum||',人数为:'||v_num);
7. END;
第三种参数传递格式:组合传递。
即在调用一个函数时,同时使用位置表示法和名称表示法为函数传递参数。采用这种参数传递方法时,使用位置表示法所传递的参数必须放在名称表示法所传递的参数前面。也就是说,无论函数具有多少个参数,只要其中有一个参数使用名称表示法,其后所有的参数都必须使用名称表示法。
无论采用哪一种参数传递方法,实际参数和形式参数之间的数据传递只有两种方法:传址法和传值法。所谓传址法是指在调用函数时,将实际参数的地址指针传递给形式参数,使形式参数和实际参数指向内存中的同一区域,从而实现参数数据的传递。这种方法又称作参照法,即形式参数参照实际参数数据。输入参数均采用传址法传递数据。
传值法是指将实际参数的数据拷贝到形式参数,而不是传递实际参数的地址。默认时,输出参数和输入/输出参数均采用传值法。在函数调用时,ORACLE将实际参数数据拷贝到输入/输出参数,而当函数正常运行退出时,又将输出形式参数和输入/输出形式参数数据拷贝到实际参数变量中。
3. 参数默认值
在CREATE OR REPLACE FUNCTION 语句中声明函数参数时可以使用DEFAULT关键字为输入参数指定默认值。
具有默认值的函数创建后,在函数调用时,如果没有为具有默认值的参数提供实际参数值,函数将使用该参数的默认值。但当调用者为默认参数提供实际参数时,函数将使用实际参数值。在创建函数时,只能为输入参数设置默认值,而不能为输入/输出参数设置默认值。
6.3 存储过程
6.3.1 创建过程
建立存储过程
在 ORACLE SERVER上建立存储过程,可以被多个应用程序调用,可以向存储过程传递参数,也可以向存储过程传回参数.
创建过程语法:
1. CREATE [OR REPLACE] PROCEDURE procedure_name
2. ([arg1 [ IN | OUT | IN OUT ]] type1 [DEFAULT value1],
3. [arg2 [ IN | OUT | IN OUT ]] type2 [DEFAULT value1]],
4. ......
5. [argn [ IN | OUT | IN OUT ]] typen [DEFAULT valuen])
6. [ AUTHID DEFINER | CURRENT_USER ]
7. { IS | AS }
8. <声明部分>
9. BEGIN
10. <执行部分>
11. EXCEPTION
12. <可选的异常错误处理程序>
13. END procedure_name;
例4.删除指定员工记录;
1. CREATE OR REPLACE PROCEDURE DELEMP(V_EMPNO IN EMP.EMPNO%TYPE) AS
2. NO_RESULT EXCEPTION;
3. BEGIN
4. DELETE FROM EMP WHERE EMPNO = V_EMPNO;
5. IF SQL%NOTFOUND THEN
6. RAISE NO_RESULT;
7. END IF;
8. DBMS_OUTPUT.PUT_LINE('编码为' || V_EMPNO || '的员工已被删除!');
9. EXCEPTION
10. WHEN NO_RESULT THEN
11. DBMS_OUTPUT.PUT_LINE('温馨提示:你需要的数据不存在!');
12. WHEN OTHERS THEN
13. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
14. END DELEMP;
例5.使用存储过程向departments表中插入数据。
1. CREATE OR REPLACE PROCEDURE INSERT_DEPT(V_DEPT_ID IN DEPT.DEPTNO%TYPE,
2. V_DEPT_NAME IN DEPT.DNAME%TYPE,
3. V_LOC_ID IN DEPT.LOC%TYPE) IS
4. EPT_NULL_ERROR EXCEPTION;
5. PRAGMA EXCEPTION_INIT(EPT_NULL_ERROR, -1400);
6. EPT_NO_LOC_ID EXCEPTION;
7. PRAGMA EXCEPTION_INIT(EPT_NO_LOC_ID, -2291);
8. BEGIN
9. INSERT INTO DEPT
10. (DEPTNO, DNAME, LOC)
11. VALUES
12. (V_DEPT_ID, V_DEPT_NAME, V_LOC_ID);
13. DBMS_OUTPUT.PUT_LINE('插入部门' || V_DEPT_ID || '成功');
14. EXCEPTION
15. WHEN DUP_VAL_ON_INDEX THEN
16. RAISE_APPLICATION_ERROR(-20000, '部门编码不能重复');
17. WHEN EPT_NULL_ERROR THEN
18. RAISE_APPLICATION_ERROR(-20001, '部门编码、部门名称不能为空');
19. WHEN EPT_NO_LOC_ID THEN
20. RAISE_APPLICATION_ERROR(-20002, '没有该地点');
21. END INSERT_DEPT;
调用实例
22. --调用实例一:
23. DECLARE
24. EPT_20000 EXCEPTION;
25. PRAGMA EXCEPTION_INIT(EPT_20000, -20000);
26. EPT_20001 EXCEPTION;
27. PRAGMA EXCEPTION_INIT(EPT_20001, -20001);
28. EPT_20002 EXCEPTION;
29. PRAGMA EXCEPTION_INIT(EPT_20002, -20002);
30. BEGIN
31. INSERT_DEPT(300, '部门300', 100, 2400);
32. INSERT_DEPT(310, NULL, 100, 2400);
33. INSERT_DEPT(310, '部门310', 100, 900);
34. EXCEPTION
35. WHEN EPT_20000 THEN
36. DBMS_OUTPUT.PUT_LINE('ept_20000部门编码不能重复');
37. WHEN EPT_20001 THEN
38. DBMS_OUTPUT.PUT_LINE('ept_20001部门编码、部门名称不能为空');
39. WHEN EPT_20002 THEN
40. DBMS_OUTPUT.PUT_LINE('ept_20002没有该地点');
41. WHEN OTHERS THEN
42. DBMS_OUTPUT.PUT_LINE('others出现了其他异常错误');
43. END;
44. --调用实例二: DECLARE EPT_20000 EXCEPTION;
45. PRAGMA EXCEPTION_INIT(EPT_20000, -20000);
46. EPT_20001 EXCEPTION;
47. PRAGMA EXCEPTION_INIT(EPT_20001, -20001);
48. EPT_20002 EXCEPTION;
49. PRAGMA EXCEPTION_INIT(EPT_20002, -20002);
50. BEGIN
51. INSERT_DEPT(V_DEPT_NAME => '部门310',
52. V_DEPT_ID => 310,
53. V_MGR_ID => 100,
54. V_LOC_ID => 2400);
55. INSERT_DEPT(320, '部门320', V_MGR_ID => 100, V_LOC_ID => 900);
56. EXCEPTION
57. WHEN EPT_20000 THEN
58. DBMS_OUTPUT.PUT_LINE('ept_20000部门编码不能重复');
59. WHEN EPT_20001 THEN
60. DBMS_OUTPUT.PUT_LINE('ept_20001部门编码、部门名称不能为空');
61. WHEN EPT_20002 THEN
62. DBMS_OUTPUT.PUT_LINE('ept_20002没有该地点');
63. WHEN OTHERS THEN
64. DBMS_OUTPUT.PUT_LINE('others出现了其他异常错误');
65. END;
6.3.2 调用存储过程
存储过程建立完成后,只要通过授权,用户就可以在SQLPLUS 、ORACLE开发工具或第三方开发工具中来调用运行。对于参数的传递也有三种:按位置传递、按名称传递和组合传递,传递方法与函数的一样。ORACLE 使用EXECUTE 语句来实现对存储过程的调用:
EXEC[UTE] procedure_name( parameter1, parameter2…);
例11:查询指定员工记录;
1. CREATE OR REPLACE PROCEDURE QUERYEMP(V_EMPNO IN EMP.EMPNO%TYPE,
2. V_ENAME OUT EMP.ENAME%TYPE,
3. V_SAL OUT EMP.SAL%TYPE) AS
4. BEGIN
5. SELECT EMPNO, SAL INTO V_ENAME, V_SAL FROM EMP WHERE EMPNO = V_EMPNO;
6. DBMS_OUTPUT.PUT_LINE('温馨提示:编码为' || V_EMPNO || '的员工已经查到!');
7. EXCEPTION
8. WHEN NO_DATA_FOUND THEN
9. DBMS_OUTPUT.PUT_LINE('温馨提示:你需要的数据不存在!');
10. WHEN OTHERS THEN
11. DBMS_OUTPUT.PUT_LINE(SQLCODE || '---' || SQLERRM);
12. END QUERYEMP;
--调用:
1. DECLARE
2. V1 EMP.ENAME%TYPE;
3. V2 EMP.SAL%TYPE;
4. BEGIN
5. QUERYEMP(100, V1, V2);
6. DBMS_OUTPUT.PUT_LINE('姓名:' || V1);
7. DBMS_OUTPUT.PUT_LINE('工资:' || V2);
8. QUERYEMP(103, V1, V2);
9. DBMS_OUTPUT.PUT_LINE('姓名:' || V1);
10. DBMS_OUTPUT.PUT_LINE('工资:' || V2);
11. QUERYEMP(104, V1, V2);
12. DBMS_OUTPUT.PUT_LINE('姓名:' || V1);
13. DBMS_OUTPUT.PUT_LINE('工资:' || V2);
14. END;
在PL/SQL 程序中还可以在块内建立本地函数和过程,这些函数和过程不存储在数据库中,但可以在创建它们的PL/SQL 程序中被重复调用。本地函数和过程在PL/SQL 块的声明部分定义,它们的语法格式与存储函数和过程相同,但不能使用CREATE OR REPLACE 关键字。
6.3.3 AUTHID
过程中的AUTHID 指令可以告诉ORACLE ,这个过程使用谁的权限运行.默任情况下,存储过程会作为调用者的过程运行,但是具有设计者的特权.这称为设计者权利运行。
AUTHID DEFINER:存储过程执行时,以这个存储过程的创建者的身份来验证存取权限。
AUTHID CURRENT_USER:执行存储过程时根据当前调用存储过程的用户权限来验证。
6.3.4授权执行权给相关的用户或角色
如果调式正确的存储过程没有进行授权,那就只有建立者本人才可以运行。所以作为应用系统的一部分的存储过程也必须进行授权才能达到要求。在SQL*PLUS下可以用GRANT命令来进行存储过程的运行授权。
GRANT语法:
1. GRANT system_privilege | role
2. TO user | role | PUBLIC [WITH ADMIN OPTION]
3. GRANT object_privilege | ALL ON schema.object
4. TO user | role | PUBLIC [WITH GRANT OPTION]
6.3.5 与过程相关数据字典
USER_SOURCE ALL_SOURCE DBA_SOURCE USER_ERRORS,
ALL_PROCEDURES USER_OBJECTS ALL_OBJECTS DBA_OBJECTS
相关的权限:
CREATE ANY PROCEDURE
DROP ANY PROCEDURE
在SQL*PLUS 中,可以用DESCRIBE 命令查看过程的名字及其参数表。
5. DESC[RIBE] Procedure_name;
6.3.6 删除过程和函数
1.删除过程
可以使用DROP PROCEDURE命令对不需要的过程进行删除,语法如下:
DROP PROCEDURE [user.]Procudure_name;
2.删除函数
可以使用DROP FUNCTION 命令对不需要的函数进行删除,语法如下:
6.3.7 过程与函数的比较
使用过程与函数具有如下优点:
1、共同使用的代码可以只需要被编写和测试一次,而被需要该代码的任何应用程序(如:.NET、C++、JAVA、VB程序,也可以是DLL库)调用。
2、这种集中编写、集中维护更新、大家共享(或重用)的方法,简化了应用程序的开发和维护,提高了效率与性能。
3、这种模块化的方法,使得可以将一个复杂的问题、大的程序逐步简化成几个简单的、小的程序部分,进行分别编写、调试。因此使程序的结构清晰、简单,也容易实现。
4、可以在各个开发者之间提供处理数据、控制流程、提示信息等方面的一致性。
5、节省内存空间。它们以一种压缩的形式被存储在外存中,当被调用时才被放入内存进行处理。并且,如果多个用户要执行相同的过程或函数时,就只需要在内存中加载一个该过程或函数。
6、提高数据的安全性与完整性。通过把一些对数据的操作放到过程或函数中,就可以通过是否授予用户有执行该过程或的权限,来限制某些用户对数据进行这些操作。
过程与函数的相同功能有:
1、 都使用IN模式的参数传入数据、OUT模式的参数返回数据。
2、 输入参数都可以接受默认值,都可以传值或传引导。
3、 调用时的实际参数都可以使用位置表示法、名称表示法或组合方法。
4、 都有声明部分、执行部分和异常处理部分。
5、 其管理过程都有创建、编译、授权、删除、显示依赖关系等。
使用过程与函数的原则:
1、如果需要返回多个值和不返回值,就使用过程;如果只需要返回一个值,就使用函数。
2、过程一般用于执行一个指定的动作,函数一般用于计算和返回一个值。
3、可以SQL语句内部(如表达式)调用函数来完成复杂的计算问题,但不能调用过程。所以这是函数的特色。