游标是构建在PL/SQL中,用来查询数据,获取记录集的指针。它让开发者 一次访问结果集中一行记录。
在oracle中提供了两种游标: 1 静态游标 2 ref游标
静态游标:静态游标是在编译的时候就被确定。然后把结果集复制到内存中 静态游标又分为两种:隐式游标和显示游标。
ref游标:ref游标是在运行的时候加载结果集
隐式游标 在PL/SQL中为所有的SQL数据操纵语句(包括返回一行的select)隐式声明游标
称为隐式游标。主要原因是用户不能直接命名和控制此类游标。当用户在PL/SQL
中使用数据操纵语句(DML)时,oracle预先定义一个名称为SQL的隐式游标,通过
检查隐式游标的属性获取与最近执行的SQL语句相关信息。 在执行DML语句之后,隐式游标属性返回信息。隐式游标属性包括: %found
%notfound %rowcount
%isopen
1 %found
只有DML语句影响一行或多行时,%found属性才返回true
declare num number;
begin update emp set empno=123 where empno=111;
if sql%found then dbms_output.put_line('存在记录');
else dbms_output.put_line('不存在记录');
end if;
end;
2 %notfound
%notfound属性作用正好跟%found属性相反。如果DML语句没有影响任何行数
,则%notfound属性返回true.
declare
begin delete from emp where empno=111;
if sql%notfound then dbms_output.put_line('删除失败');
end if;
end;
3 %rowcount
%rowcount属性返回DML语句影响的行数。如果DML语句没有影响任何行数
,则%rowcount属性将返回0。
declare num number;
begin update emp set empno=123 where empno=111;
if sql%rowcount=0 then dbms_output.put_line('不存在记录');
else dbms_output.put_line('存在记录');
end if;
end;
4 %isopen
%isopen属性判断SQL游标是否已经打开。在执行SQL语句之后,oracle自动关闭SQL
游标,所以隐式游标的%isopen属性始终为false.
在PL/SQL中向标准的select语句增加单独的into子句,就可以将从表或视图中查询
记录赋予变量或行变量。需要注意的是select ..into 语句结果必须有且只能有一行。
如果查询没有返回行,PL/SQL将抛出no_data_found异常。如果查询返回多行,则抛出 too_many_rows
异常。如果抛出异常,则停止执行,控制权转移到异常处理部分(没有
异常处理,则程序中断)。在引发异常时,将不使用属性%found,%notfound,%rowcount来查明DML语句是否
已影响了行数。
declare num number;
begin select empno into num from emp where empno=111;
if sql%rowcount=0 or sql%notfound then
dbms_output.put_line('不存在记录');
else dbms_output.put_line('存在记录');
end if;
end;
显示游标 显示游标是由用户显示声明的游标。根据在游标中定义的查询,查询返回的行集合可以
包含零行或多行,这些行称为活动集。游标将指向活动集中的当前行。 显示游标的操作过程。使用显示游标的4个步骤:
(1)声明游标
(2)打开游标
(3)从游标中获取结果集
(4)关闭游标
cursor cursor_name [(parameter[,parameter])] [return
return_type] is select_statement; cursor_name 指游标的名称。
parameter 为游标指定输入参数。
return_type 定义游标提取行的行类型。
select_statement 为游标定义查询语句。
open 游标名称 fetch 从游标中提取行 close 关闭游标
1 打开游标,执行游标中定义的查询语句,绑定输入参数,将游标指针指 向结果集的BOF位置。 open
cursor_name [parameters]
2 fetch 在打开游标之后,可以从游标中提取记录 fetch
cursor_name into variable_name; fetch
是提取结果集中一行记录存储在变量中。每次提取之后,结果集指针 就向前移动一行。
3 close
在处理游标中的所有行之后,必须关闭游标,以释放分配给游标的所有资源。 close cursor_name
用户可以通过检查游标属性来确定游标的当前状态。
显示游标的属性如下:
%found:如果执行最后一条fetch语句,成功返回行,则%found属性为true。
%notfound:如果执行最后一条fetch语句,未能提取行,则%notfound属性为true。
%isopen:如果游标已经打开,则返回true,否则返回false。
%rowcount:返回到目前为止游标提取的行数。
%rowcount为数字类型属性。在第一
次获取之前,%rowcount为零。当fetch语句返回一行时,则该数加1。
declare info emp%rowtype;
cursor my_cur is select * from emp where empno=111;
begin open my_cur;
dbms_output.put_line(my_cur%rowcount); loop
if my_cur%isopen then
fetch my_cur into info;
exit when my_cur%notfound;
dbms_output.put_line(info.empno);
dbms_output.put_line(my_cur%rowcount);
end if; end loop;
close my_cur;
end;
使用显示游标删除或更新 使用游标时,如果处理过程中需要删除或更新。在定义游标查询语句时 必须使用select..for
update语句,而在执行delete或update时使用 where current of 子句指定游标当前行。
cursor cursor_name is select_statement for update[of column]
wait/nowait
在使用for update 子句声明游标之后,可以使用以下语法更新行
update table_name set column_name=column_value where current
of cursor_name;
update命令中使用的列必须出现在for update of 子句中 select
语句必须只包括一个表,而且delete和update语句只有在打开游标并且提取 特定行之后才能使用。
declare cursor cur_emp is select * from emp where sal<2000
for update of sal;
num emp%rowtype;
begin open cur_emp; loop
fetch cur_emp into num;
exit when cur_emp%notfound;
update emp set sal=2000 where current of cur_emp;
end loop;
close cur_emp;
end;
带参数的显示游标 PL/SQL中允许显示游标接受输入参数。用于声明带参数的显示游标语法
cursor cursor_name[ data_type] [return ] is select_statement
declare dept_num emp.deptno%type;
emp_num emp.empno%type;
emp_nam emp.ename%type;
cursor emp_cur(deptparam number) is select empno,ename from
emp where deptno=deptparam;
begin dept_num :=&部门编号;
open emp_cur(dept_num); loop
fetch emp_cur into emp_num,emp_nam;
exit when emp_cur%notfound;
dbms_output.put_line(emp_num||' '||emp_nam);
end loop;
close emp_cur;
end;
可以使用循环游标来简化显示游标 循环游标隐式打开显示游标(不需要open)
自动从结果集提取记录,然后处理完所有记录自动关闭游标。循环游标自动创建 %rowtype类型的变量并将此变量用做记录的索引。
循环游标语法如下:
for record_index in cursor_name record_
index是PL/SQL自动创建的变量,此变量的属性声明为%rowtype类型。作用 域for循环之内。 循环游标的特性有:
从游标中提取所有记录之后自动关闭游标。 提取和处理游标中每一条记录
提取记录之后%notfound属性为true则退出循环。如果未有结果集,则不进入循环。
declare cursor emp_cur is select * from emp;
begin for temp in emp_cur loop
dbms_output.put_line(temp.ename);
end loop;
end;
循环游标自动打开,提取,关闭。只适用于静态游标
ref游标 隐式游标和显示游标都是静态定义的。它们在编译的时候结果集就已经被确定。
如果想在运行的时候动态确定结果集,就要使用ref游标和游标变量。
创建ref游标需要两个步骤:
1 声明ref cursor类型
2 声明 ref cursor类型变量。
语法如下:
type ref_cursor_name is ref cursor [return record_type]
其中,return 用于指定游标提取结果集的返回类型。有return表示是强类型ref游标,
没有return表示是弱类型的游标。弱类型游标可以提取任何类型的结果集。
定义游标变量之后,就可以在PL/SQL执行部门打开游标变量 open cursor_name for
select_statement;
declare type emp_cur is ref cursor;
my_cur emp_cur;
num number;
selection varchar(2):='&请输入编号';
begin
if selection='1' then dbms_output.put_line('员工信息');
open my_cur for select deptno from emp;
elsif selection='2' then dbms_output.put_line('部门信息');
open my_cur for select deptno from dept;
else
dbms_output.put_line('请输入员工信息(1)或门部信息(2)');
end if;
fetch my_cur into num;
while my_cur%found loop
dbms_output.put_line(num);
fetch my_cur into num;
end loop;
close my_cur;
end;
在PL/SQL中可以执行动态SQL语句,execute immediate 语句只能语句处理返回单行
或没有返回的SQL语句,ref游标则可以处理返回结果集的动态SQL。
ref游标的声明 方法与普通ref游标相同,只是在open时指定了动态SQL字符串。
open cursor_name for dynamic_select_string [using
bind_argument_list]
declare type sql_cur is ref cursor;
my_cur sql_cur;
emp_info emp%rowtype;
sql_string varchar2(100):='&请输入查询字符串';
begin open my_cur for sql_string; loop
fetch my_cur into emp_info;
exit when my_cur%notfound;
dbms_output.put_line(emp_info.ename);
end loop;
close my_cur;
end;
游标变量的特点:
(1)游标变量可以从不同的结果集中提取记录
(2)游标变量可以做为存储过程参数进行传递
(3)游标变量可以引用游标的所有属性
(4)游标变量可以进行赋值运算 使用游标变量也有一定的限制:
(1)for update 子句不能与游标变量一起使用
(2)不允许在程序包使用游标变量(可以声明游标类型)
(3)另一台服务器上的子过程不能接受游标变量参数
(4)不能将NULL值赋给游标变量。
(5)游标变量不能使用比较运算符。
(6)数据库中的列不能存储游标变量。
总结:
(1)游标是使用在PL/SQL中,是用来查询数据,获取结果集的指针
(2)游标类型包括隐式游标,显示游标和ref游标。
(3)游标属性包括%found,%notfound,%rowcount,%isopen
(4)PL/SQL自动定义隐式游标,以获取最近执行SQL语句信息。
(5)循环游标简化处理游标中所有行的查询。
(6)在声明ref游标时,不要将它与select 语句相关联。
动态SELECT语句和动态游标的用法
Oracle支持动态SELECT语句和动态游标,动态的方法大大扩展了程序设计的能力。
对于查询结果为一行的SELECT语句,可以用动态生成查询语句字符串的方法,在程序执行阶段临时地生成并执行,语法是:
execute immediate 查询语句字符串 into 变量1[,变量2...];
以下是一个动态生成SELECT语句的例子。
【训练1】 动态SELECT查询。
Sql代码
1.SET SERVEROUTPUT ON
2. DECLARE
3. str varchar2(100);
4. v_ename varchar2(10);
5. begin
6. str:='select ename from scott.emp where
empno=7788';
7. execute immediate str into v_ename;
8. dbms_output.put_line(v_ename);
9. END;
SET SERVEROUTPUT ON
DECLARE
str varchar2(100);
v_ename varchar2(10);
begin
str:='select ename from scott.emp where
empno=7788';
execute immediate str into v_ename;
dbms_output.put_line(v_ename);
END;
执行结果为:
Sql代码
1.SCOTT
2. PL/SQL 过程已成功完成。
SCOTT
PL/SQL 过程已成功完成。
说明:SELECT...INTO...语句存放在STR字符串中,通过EXECUTE语句执行。
在变量声明部分定义的游标是静态的,不能在程序运行过程中修改。虽然可以通过参数传递来取得不同的数据,但还是有很大的局限性。通过采用动态游标,可以在程序运行阶段随时生成一个查询语句作为游标。要使用动态游标需要先定义一个游标类型,然后声明一个游标变量,游标对应的查询语句可以在程序的执行过程中动态地说明。
定义游标类型的语句如下:
TYPE 游标类型名 REF CURSOR;
声明游标变量的语句如下:
游标变量名 游标类型名;
在可执行部分可以如下形式打开一个动态游标:
OPEN 游标变量名 FOR 查询语句字符串;
【训练2】 按名字中包含的字母顺序分组显示雇员信息。
输入并运行以下程序:
Sql代码
1.declare
2. type cur_type is ref cursor;
3. cur cur_type;
4. rec scott.emp%rowtype;
5. str varchar2(50);
6. letter char:= 'A';
7.begin
8. loop
9. str:= 'select ename from emp where ename like
''%'||letter||'%''';
10. open cur for str;
11. dbms_output.put_line('包含字母'||letter||'的名字:');
12. loop
13. fetch cur into rec.ename;
14. exit when cur%notfound;
15. dbms_output.put_line(rec.ename);
16.end loop;
17. exit when letter='Z';
18. letter:=chr(ascii(letter)+1);
19. end loop;
20.end;
declare
type cur_type is ref cursor;
cur cur_type;
rec scott.emp%rowtype;
str varchar2(50);
letter char:= 'A';
begin
loop
str:=
'select ename from emp where ename like ''%'||letter||'%''';
open cur
for str;
dbms_output.put_line('包含字母'||letter||'的名字:');
loop
fetch cur
into rec.ename;
exit when
cur%notfound;
dbms_output.put_line(rec.ename);
end loop;
exit when letter='Z';
letter:=chr(ascii(letter)+1);
end loop;
end;
运行结果为:
Sql代码
1.包含字母A的名字:
2.ALLEN
3.WARD
4.MARTIN
5.BLAKE
6.CLARK
7.ADAMS
8.JAMES
9.包含字母B的名字:
10.BLAKE
11.包含字母C的名字:
12.CLARK
13.SCOTT
包含字母A的名字:
ALLEN
WARD
MARTIN
BLAKE
CLARK
ADAMS
JAMES
包含字母B的名字:
BLAKE
包含字母C的名字:
CLARK
SCOTT
说明:使用了二重循环,在外循环体中,动态生成游标的SELECT语句,然后打开。通过语句letter:=chr(ascii(letter)+1)可获得字母表中的下一个字母。
异常处理
错误处理
错误处理部分位于程序的可执行部分之后,是由WHEN语句引导的多个分支构成的。错误处理的语法如下:
EXCEPTION
WHEN 错误1[OR 错误2] THEN
语句序列1;
WHEN 错误3[OR 错误4] THEN
语句序列2;
WHEN OTHERS
语句序列n;
END;
其中:
错误是在标准包中由系统预定义的标准错误,或是由用户在程序的说明部分自定义的错误,参见下一节系统预定义的错误类型。
语句序列就是不同分支的错误处理部分。
凡是出现在WHEN后面的错误都是可以捕捉到的错误,其他未被捕捉到的错误,将在WHEN
OTHERS部分进行统一处理,OTHENS必须是EXCEPTION部分的最后一个错误处理分支。如要在该分支中进一步判断错误种类,可以通过使用预定义函数SQLCODE(
)和SQLERRM( )来获得系统错误号和错误信息。
如果在程序的子块中发生了错误,但子块没有错误处理部分,则错误会传递到主程序中。
下面是由于查询编号错误而引起系统预定义异常的例子。
【训练1】 查询编号为1234的雇员名字。
Sql代码
1.SET SERVEROUTPUT ON
2.DECLARE
3.v_name VARCHAR2(10);
4.BEGIN
5. SELECT ename
6. INTO v_name
7. FROM emp
8. WHERE empno = 1234;
9.DBMS_OUTPUT.PUT_LINE('该雇员名字为:'|| v_name);
10.EXCEPTION
11. WHEN NO_DATA_FOUND
THEN
12. DBMS_OUTPUT.PUT_LINE('编号错误,没有找到相应雇员!');
13. WHEN OTHERS THEN
14. DBMS_OUTPUT.PUT_LINE('发生其他错误!');
15.END;
SET SERVEROUTPUT ON
DECLARE
v_name VARCHAR2(10);
BEGIN
SELECT ename
INTO v_name
FROM emp
WHERE empno = 1234;
DBMS_OUTPUT.PUT_LINE('该雇员名字为:'|| v_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('编号错误,没有找到相应雇员!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生其他错误!');
END;
执行结果为:
Sql代码
1.编号错误,没有找到相应雇员!
2. PL/SQL 过程已成功完成。
编号错误,没有找到相应雇员!
PL/SQL 过程已成功完成。
说明:在以上查询中,因为编号为1234的雇员不存在,所以将发生类型为“NO_DATA_
FOUND”的异常。“NO_DATA_FOUND”是系统预定义的错误类型,EXCEPTION部分下的WHEN语句将捕捉到该异常,并执行相应代码部分。在本例中,输出用户自定义的错误信息“编号错误,没有找到相应雇员!”。如果发生其他类型的错误,将执行OTHERS条件下的代码部分,显示“发生其他错误!”。
【训练2】 由程序代码显示系统错误。
Sql代码
1.SET SERVEROUTPUT ON
2.DECLARE
3.v_temp NUMBER(5):=1;
4.BEGIN
5.v_temp:=v_temp/0;
6.EXCEPTION
7. WHEN OTHERS THEN
8.DBMS_OUTPUT.PUT_LINE('发生系统错误!');
9. DBMS_OUTPUT.PUT_LINE('错误代码:'|| SQLCODE( ));
10. DBMS_OUTPUT.PUT_LINE('错误信息:' ||SQLERRM( ));
11. END;
SET SERVEROUTPUT ON
DECLARE
v_temp NUMBER(5):=1;
BEGIN
v_temp:=v_temp/0;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生系统错误!');
DBMS_OUTPUT.PUT_LINE('错误代码:'|| SQLCODE( ));
DBMS_OUTPUT.PUT_LINE('错误信息:' ||SQLERRM( ));
END;
执行结果为:
Sql代码
1.发生系统错误!
2. 错误代码:?1476
3. 错误信息:ORA-01476: 除数为 0
4. PL/SQL 过程已成功完成。
发生系统错误!
错误代码:?1476
错误信息:ORA-01476: 除数为 0
PL/SQL 过程已成功完成。
说明:程序运行中发生除零错误,由WHEN
OTHERS捕捉到,执行用户自己的输出语句显示错误信息,然后正常结束。在错误处理部分使用了预定义函数SQLCODE(
)和SQLERRM( )来进一步获得错误的代码和种类信息。
预定义错误
Oracle的系统错误很多,但只有一部分常见错误在标准包中予以定义。定义的错误可以在EXCEPTION部分通过标准的错误名来进行判断,并进行异常处理。常见的系统预定义异常如下所示。
Sql代码
1.错 误 名 称 错误代码 错 误 含 义
2.CURSOR_ALREADY_OPEN ORA_06511 试图打开已经打开的游标
3.INVALID_CURSOR ORA_01001
试图使用没有打开的游标
4.DUP_VAL_ON_INDEX ORA_00001 保存重复值到惟一索引约束的列中
5.ZERO_DIVIDE ORA_01476 发生除数为零的除法错误
6.INVALID_NUMBER ORA_01722
试图对无效字符进行数值转换
7.ROWTYPE_MISMATCH ORA_06504 主变量和游标的类型不兼容
8.VALUE_ERROR ORA_06502 转换、截断或算术运算发生错误
9.TOO_MANY_ROWS ORA_01422
SELECT…INTO…语句返回多于一行的数据
10.NO_DATA_FOUND ORA_01403
SELECT…INTO…语句没有数据返回
11.TIMEOUT_ON_RESOURCE ORA_00051 等待资源时发生超时错误
12.TRANSACTION_BACKED_OUT ORA_00060
由于死锁,提交失败
13.STORAGE_ERROR ORA_06500
发生内存错误
14.PROGRAM_ERROR ORA_06501
发生PL/SQL内部错误
15.NOT_LOGGED_ON ORA_01012
试图操作未连接的数据库
16.LOGIN_DENIED ORA_01017
在连接时提供了无效用户名或口令
比如,如果程序向表的主键列插入重复值,则将发生DUP_VAL_ON_INDEX错误。
如果一个系统错误没有在标准包中定义,则需要在说明部分定义,语法如下:
错误名 EXCEPTION;
定义后使用PRAGMA
EXCEPTION_INIT来将一个定义的错误同一个特别的Oracle错误代码相关联,就可以同系统预定义的错误一样使用了。语法如下:
PRAGMA EXCEPTION_INIT(错误名,- 错误代码);
【训练1】 定义新的系统错误类型。
Sql代码
1.SET SERVEROUTPUT ON
2. DECLARE
3. V_ENAME VARCHAR2(10);
4. NULL_INSERT_ERROR EXCEPTION;
5. PRAGMA EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);
6. BEGIN
7. INSERT INTO EMP(EMPNO) VALUES(NULL);
8.EXCEPTION
9.WHEN NULL_INSERT_ERROR THEN
10. DBMS_OUTPUT.PUT_LINE('无法插入NULL值!');
11. WHEN OTHERS
THEN
12. DBMS_OUTPUT.PUT_LINE('发生其他系统错误!');
13.END;
SET SERVEROUTPUT ON
DECLARE
V_ENAME VARCHAR2(10);
NULL_INSERT_ERROR EXCEPTION;
PRAGMA
EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);
BEGIN
INSERT INTO EMP(EMPNO) VALUES(NULL);
EXCEPTION
WHEN NULL_INSERT_ERROR THEN
DBMS_OUTPUT.PUT_LINE('无法插入NULL值!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生其他系统错误!');
END;
执行结果为:
Sql代码
1.无法插入NULL值!
2. PL/SQL 过程已成功完成。
无法插入NULL值!
PL/SQL 过程已成功完成。
说明:NULL_INSERT_ERROR是自定义异常,同系统错误1400相关联。
自定义异常
程序设计者可以利用引发异常的机制来进行程序设计,自己定义异常类型。可以在声明部分定义新的异常类型,定义的语法是:
错误名 EXCEPTION;
用户定义的错误不能由系统来触发,必须由程序显式地触发,触发的语法是:
RAISE 错误名;
RAISE也可以用来引发模拟系统错误,比如,RAISE ZERO_DIVIDE将引发模拟的除零错误。
使用RAISE_APPLICATION_ERROR函数也可以引发异常。该函数要传递两个参数,第一个是用户自定义的错误编号,第二个参数是用户自定义的错误信息。使用该函数引发的异常的编号应该在20
000和20 999之间选择。
自定义异常处理错误的方式同前。
【训练1】 插入新雇员,限定插入雇员的编号在7000~8000之间。
Java代码
1.SET SERVEROUTPUT ON
2.DECLARE
3.new_no NUMBER(10);
4.new_excp1 EXCEPTION;
5.new_excp2 EXCEPTION;
6.BEGIN
7.new_no:=6789;
8.INSERT INTO emp(empno,ename)
9. VALUES(new_no, '小郑');
10. IF new_no<7000 THEN
11. RAISE new_excp1;
12. END IF;
13. IF new_no>8000 THEN
14. RAISE new_excp2;
15. END IF;
16. COMMIT;
17.EXCEPTION
18.WHEN new_excp1 THEN
19. ROLLBACK;
20. DBMS_OUTPUT.PUT_LINE('雇员编号小于7000的下限!');
21. WHEN new_excp2
THEN
22. ROLLBACK;
23. DBMS_OUTPUT.PUT_LINE('雇员编号超过8000的上限!');
24. END;
SET SERVEROUTPUT ON
DECLARE
new_no NUMBER(10);
new_excp1 EXCEPTION;
new_excp2 EXCEPTION;
BEGIN
new_no:=6789;
INSERT INTO emp(empno,ename)
VALUES(new_no, '小郑');
IF new_no<7000 THEN
RAISE new_excp1;
END IF;
IF new_no>8000 THEN
RAISE new_excp2;
END IF;
COMMIT;
EXCEPTION
WHEN new_excp1 THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('雇员编号小于7000的下限!');
WHEN new_excp2 THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('雇员编号超过8000的上限!');
END;
执行结果为:
雇员编号小于7000的下限!
PL/SQL 过程已成功完成。
说明:在此例中,自定义了两个异常:new_excp1和new_excp2,分别代表编号小于7000和编号大于8000的错误。在程序中通过判断编号大小,产生对应的异常,并在异常处理部分回退插入操作,然后显示相应的错误信息。
【训练2】
使用RAISE_APPLICATION_ERROR函数引发系统异常。
Sql代码
1.SET SERVEROUTPUT ON
2.DECLARE
3.New_no NUMBER(10);
4.BEGIN
5. New_no:=6789;
6. INSERT INTO emp(empno,ename)
7. VALUES(new_no, 'JAMES');
8.IF new_no<7000 THEN
9. ROLLBACK;
10. RAISE_APPLICATION_ERROR(-20001, '编号小于7000的下限!');
11. END IF;
12. IF new_no>8000
THEN
13. ROLLBACK;
14. RAISE_APPLICATION_ERROR (-20002, '编号大于8000的下限!');
15. END IF;
16.END;
SET SERVEROUTPUT ON
DECLARE
New_no NUMBER(10);
BEGIN
New_no:=6789;
INSERT INTO emp(empno,ename)
VALUES(new_no, 'JAMES');
IF new_no<7000 THEN
ROLLBACK;
RAISE_APPLICATION_ERROR(-20001, '编号小于7000的下限!');
END IF;
IF new_no>8000 THEN
ROLLBACK;
RAISE_APPLICATION_ERROR
(-20002, '编号大于8000的下限!');
END IF;
END;
执行结果为:
Sql代码
1.DECLARE
2. *
3. ERROR 位于第 1 行:
4. ORA-20001: 编号小于7000的下限!
5. ORA-06512: 在line 9
DECLARE
*
ERROR 位于第 1 行:
ORA-20001: 编号小于7000的下限!
ORA-06512: 在line 9
说明:在本训练中,使用RAISE_APPLICATION_ERROR引发自定义异常,并以系统错误的方式进行显示。错误编号为20001和20002。
注意:同上一个训练比较,此种方法不需要事先定义异常,可直接引发。
可以参考下面的程序片断将出错信息记录到表中,其中,errors为记录错误信息的表,SQLCODE为发生异常的错误编号,SQLERRM为发生异常的错误信息。
DECLARE
v_error_code NUMBER;
v_error_message VARCHAR2(255);
BEGIN
...
EXCEPTION
...
WHEN OTHERS THEN
v_error_code := SQLCODE
;
v_error_message :=
SQLERRM ;
INSERT INTO errors
VALUES(v_error_code,
v_error_message);
END;
【练习1】修改雇员的工资,通过引发异常控制修改范围在600~6000之间。
阶段训练
【训练1】 将雇员从一个表复制到另一个表。
步骤1:创建一个结构同EMP表一样的新表EMP1:
CREATE TABLE emp1 AS SELECT * FROM SCOTT.EMP WHERE 1=2;
步骤2:通过指定雇员编号,将雇员由EMP表移动到EMP1表:
Sql代码
1.SET SERVEROUTPUT ON
2.DECLARE
3.v_empno NUMBER(5):=7788;
4.emp_rec emp%ROWTYPE;
5.BEGIN
6. SELECT * INTO emp_rec FROM emp WHERE empno=v_empno;
7. DELETE FROM emp WHERE empno=v_empno;
8.INSERT INTO emp1 VALUES emp_rec;
9. IF SQL%FOUND THEN
10. COMMIT;
11. DBMS_OUTPUT.PUT_LINE('雇员复制成功!');
12. ELSE
13. ROLLBACK;
14. DBMS_OUTPUT.PUT_LINE('雇员复制失败!');
15. END IF;
16.END;
SET SERVEROUTPUT ON
DECLARE
v_empno NUMBER(5):=7788;
emp_rec emp%ROWTYPE;
BEGIN
SELECT * INTO emp_rec FROM emp WHERE
empno=v_empno;
DELETE FROM emp WHERE empno=v_empno;
INSERT INTO emp1 VALUES emp_rec;
IF SQL%FOUND THEN
COMMIT;
DBMS_OUTPUT.PUT_LINE('雇员复制成功!');
ELSE
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('雇员复制失败!');
END IF;
END;
执行结果为:
雇员复制成功!
PL/SQL 过程已成功完成。
步骤2:显示复制结果:
SELECT empno,ename,job FROM emp1;
执行结果为:
Sql代码
1.EMPNO ENAME JOB
2.------------- -------------- ----------------
3. 7788
SCOTT ANALYST
EMPNO ENAME JOB
-------------- -------------- ----------------
7788
SCOTT ANALYST
说明:emp_rec变量是根据emp表定义的记录变量,SELECT...INTO...语句将整个记录传给该变量。INSERT语句将整个记录变量插入emp1表,如果插入成功(SQL%FOUND为真),则提交事务,否则回滚撤销事务。试修改雇员编号为7902,重新执行以上程序。
【训练2】 输出雇员工资,雇员工资用不同高度的*表示。
输入并执行以下程序:
Sql代码
1.SET SERVEROUTPUT ON
2.BEGIN
3. FOR re IN (SELECT ename,sal FROM EMP)
LOOP
4. DBMS_OUTPUT.PUT_LINE(rpad(re.ename,12,'
')||rpad('*',re.sal/100,'*'));
5. END LOOP;
6.END;
SET SERVEROUTPUT ON
BEGIN
FOR re IN (SELECT ename,sal FROM EMP)
LOOP
DBMS_OUTPUT.PUT_LINE(rpad(re.ename,12,'
')||rpad('*',re.sal/100,'*'));
END LOOP;
END;
输出结果为:
Sql代码
1.SMITH ********
2.ALLEN ****************
3.WARD *************
4.JONES ******************************
5.MARTIN *************
6.BLAKE *****************************
7.CLARK *****************************
8.SCOTT ******************************
9.KING **************************************************
10.TURNER ***************
11.ADAMS ***********
12.JAMES **********
13.FORD ******************************
14.MILLER *************
15. 执行结果为:
16. PL/SQL 过程已成功完成。
SMITH ********
ALLEN ****************
WARD *************
JONES ******************************
MARTIN *************
BLAKE *****************************
CLARK *****************************
SCOTT ******************************
KING **************************************************
TURNER ***************
ADAMS ***********
JAMES **********
FORD ******************************
MILLER *************
执行结果为:
PL/SQL 过程已成功完成。
说明:第一个rpad函数产生对齐效果,第二个rpad函数根据工资额产生不同数目的*。该程序采用了隐式的简略游标循环形式。
【训练3】 编写程序,格式化输出部门信息。
输入并执行如下程序:
Sql代码
1.SET SERVEROUTPUT ON
2. DECLARE
3. v_count number:=0;
4. CURSOR dept_cursor IS SELECT * FROM dept;
5. BEGIN
6. DBMS_OUTPUT.PUT_LINE('部门列表');
7.DBMS_OUTPUT.PUT_LINE('---------------------------------');
8. FOR Dept_record IN dept_cursor LOOP
9. DBMS_OUTPUT.PUT_LINE('部门编号:'||
Dept_record.deptno);
10. DBMS_OUTPUT.PUT_LINE('部门名称:'||
Dept_record.dname);
11. DBMS_OUTPUT.PUT_LINE('所在城市:'|| Dept_record.loc);
12.DBMS_OUTPUT.PUT_LINE('---------------------------------');
13. v_count:= v_count+1;
14. END LOOP;
15. DBMS_OUTPUT.PUT_LINE('共有'||to_char(v_count)||'个部门!');
16. END;
SET SERVEROUTPUT ON
DECLARE
v_count number:=0;
CURSOR dept_cursor IS SELECT * FROM dept;
BEGIN
DBMS_OUTPUT.PUT_LINE('部门列表');
DBMS_OUTPUT.PUT_LINE('---------------------------------');
FOR
Dept_record IN dept_cursor LOOP
DBMS_OUTPUT.PUT_LINE('部门编号:'|| Dept_record.deptno);
DBMS_OUTPUT.PUT_LINE('部门名称:'|| Dept_record.dname);
DBMS_OUTPUT.PUT_LINE('所在城市:'||
Dept_record.loc);
DBMS_OUTPUT.PUT_LINE('---------------------------------');
v_count:=
v_count+1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('共有'||to_char(v_count)||'个部门!');
END;
输出结果为:
Sql代码
1.部门列表
2.------------------------------------
3.部门编号:10
4.部门名称:ACCOUNTING
5.所在城市:NEW YORK
6.------------------------------------
7.部门编号:20
8.部门名称:RESEARCH
9.所在城市:DALLAS
10....
11.共有4个部门!
12.PL/SQL 过程已成功完成。
部门列表
------------------------------------
部门编号:10
部门名称:ACCOUNTING
所在城市:NEW YORK
------------------------------------
部门编号:20
部门名称:RESEARCH
所在城市:DALLAS
...
共有4个部门!
PL/SQL 过程已成功完成。
说明:该程序中将字段内容垂直排列。V_count变量记录循环次数,即部门个数。