Oracle--PL/SQL编程

  前言:本博客仅作记录学习使用,部分图片出自网络,如有侵犯您的权益,请联系删除

PL/SQL(Procedural Language/SQL)是Oracle数据库中的一种过程化编程语言,构建于SQL之上,允许编写包含SQL语句的程序。它结合了SQL的数据操纵和查询能力与过程化编程的控制结构,如IF语句和LOOP语句,支持变量定义和数据传递,能够实现复杂的业务逻辑。作为Oracle的专用语言,PL/SQL是对标准SQL的扩展,增强了数据库编程的灵活性和功能。

一、PL/SQL块结构

PL/SQL程序以块(block)为基本单位,整个PL/SQL块分为3部分,即声明部分(用DECLARE开头)、执行部分(以BEGIN开头)和异常处理部分(以EXCEPTION开头)。其中,执行部分是必须的,其他两个可选

标准PL/SQL块的语法格式如下:

 [DECLARE]       --声明部分,可选
 BEGIN           --执行部分,必须
 [EXCEPTION]     --异常处理部分
 END

1、声明部分

关键字DECLARE开始,到BEGIN关键字结束。在这里可以声明PL/SQL程序块中用到的变量、常量和游标等。注意,在某个PL/SQL块中声明的内容只能再当前块使用

2、执行部分

以关键字BEGIN开始,结束方式有两种:如果PL/SQL块中的代码在运行时出现异常,则执行完异常处理部分的代码就结束;如果没有使用异常处理或PL/SQL块未出现异常,则以关键字END结束。执行部分是整个PL/SQL块的主体,主要的逻辑控制和运算都在这部分被完成,所以在执行部分可以包含多个PL/SQL语句和SQL语句

3、异常处理部分

以关键字EXCEPTION开始,在该关键字所包含的代码被执行完毕后,整个PL/SQL块也将结束在执行过程中,可能会产生意想不到的错误,可以在异常处理部分通过编写一定量的代码来纠正错误或给一些提示,甚至将各数据回退到异常前。对于可能出现的多种异常情况,可以使用WHEN THEN语句来实现多分支判断,然后在每个分支下通过编写代码来处理相应的异常。

对于PL/SQL块中的语句,每一条PL/SQL语句都必须以分号结束,而每条PL/SQL语句均可以被写成多行的形式,同样必须使用分号来结束;另外,一行中也可以有多条PL/SQL语句,但是它们之间必须以分号分隔

示例:定义一个PL/SQL块,计算两个正数的和与这两个整数的差的商:

 --实现在服务端显示执行结果
 set serveroutput on
 --PL/SQL块
 declare
     a int:=100;
     b int:=200;
     c number;
 begin
     c:=(a+b)/(a-b);
     dbms_output.put_line(c);
 exception
     when zero_divide then
     dbmx_output.put_line('除数不允许为零!');
 end;

二、代码注释和标识符

1、单行注释

单行注释由两个连接字符"--"开始,后面紧跟着注释内容

 set serveroutput on
 declare
 Num_sal number;         --声明一个注释变量
 Var_ename varchar2(20); --声明一个字符串变量
 begin
 select ename,sal into Var_ename,Num_sal from emp    --检索指定的值并存储到变量中
 where empno=7369;
 dbms_output.put_line(Var_ename||'的工资是'||Num_sal);   --输出变量中值
 end;

2、多行注释

多行注释由"/*"开头,以"*/"结尾,这种多行注释的方法在大多数的编程语言中是相同的

 set serveroutput on
 declare
 Num_sal number;             /*声明一个数值变量*/
 Var_ename varchar2(20);     /*声明一个字符串变量*/
 begin
 /*检索指定的值并存储到变量中*/
 select ename,sal into Var_ename,Num_sal from emp
 where empno=7369;
 /*输出变量中的值*/
 dbms_output.put_line(Var_ename||'的工资是'||Num_sal);
 end

3、标识符

标识符(identifier)用于定义PL/SQL块和程序单元的名称;通过使用标识符,可以定义常量、变量、异常、显示游标、游标变量、参数、子程序以及包的名称。当使用标识符定义PL/SQL块或程序单元时,需要满足以下规则:

  • 当定义变量、常量时,每行只能定义一个变量或者常量(行终止符为";")
  • ~,名称必须以英文字符(A-Z,a-z)开始,并且最大长度为30个字符。如果以其他字符开始,那么必须使用双引号引住。
  • 名称只能使用A-Z,a-z,0-9以及符号_、$和#。如果使用其他字符,那么必须使用双引号引住
  • 名称不能使用Oracle关键字,若需要则以双引号引住

所有PL/SQL程序元素都是由一些字符序列组合而成的,而这些字符序列中的字符都必须取自PL/SQL所允许使用的字符集:

  • 大写和小写字母:A-Z或a-z
  • 数字:0-9
  • 非显示的字符:制表符、空格和按Enterjian
  • 数学符号:+、-、*、/、<、>、=等
  • 间隔符:()、{}、[]、?、!、;、:、@、#、%、$、&等
4、文本

文本指实际数值的文字,包括数字文本、字符文本、布尔文本、日期时间文本、字符串文本等

  • 数字文本:整数或者浮点数。编写PL/SQL代码时,可以十一科学计数法和幂操作符(**)表示。这只适用与PL/SQL语句,不适用SQL语句
  • 字符文本:用单引号引住的单个字符。这些字符可以是PL/SQL支持的所有字符
  • 布尔文本:通常指BOLLEAN值(TRUE、FALSE和NULL),主要用在条件表达式中
  • 日期时间文本:指日期和时间值。日期文本必须用单引号引住,并且日期值必须与日期格式和日期语言匹配
  • 字符串文本:由两个或两个以上字符组成的多个字符值。字符串文本必须由单引号引住,

在Oracle Database 10g之前,如果字符串文本包含单引号,必须使用两个单引号表示:例如

 string_vat:= 'I''m a string,you''re a string.';

在Oracle Database 10g之后,增加可以使用其他分隔符([]、{}、<>)赋值、需要在分隔符前后加上单引号,而且需要带有前缀q:

 string_var:= q'[I'm a string,you're a string.]';

三、基本数据类型

1、数组类型

数组类型包括NUMBER、BINARY_INTEGER和PLS_INTEGER3种类型

  • NUMBER类型的变量可以存储整数或浮点数;
  • BINARY_INTEGER和PLS_INTEGER类型的变量只能存储整数

NUMBER类型还可以通过NUMBER(p,s)的形式来格式化数字,其中,参数p表示精度,参数s表示刻度范围。精度是指数值中所有有效数字的个数,而刻度范围是指小数点右边小数位的个数,在这里精度和刻度范围都是可选的。

 --声明一个精度为8且刻度范围为3的标识金额的变量Num_Money
 Num_Money NUMBER(8,3);

PL/SQL子类型,是与NUMBER类型等价的类型别名,甚至可以说是NUMBER类型的多种重命名形式;这些等价的子类型主要包括DEC、DECIMAL、DOUBLE、INTEGER、INT、NUMERIC、SMALLINT、BINARY_INTEGER、PLS_INTEGER等。

2、字符类型

字符类型主要包括VARCHAR2、CHAR、LONG、NCHAR和HVARCHAR2,用来存储字符串或字符数据。

  • VARCHAR2类型:PL/SQL中的VARCHAR2类型和数据库类型中VARCHAR2比较类似,用于存储可变长度的字符串,其格式:

 VARCHAR2(maxlength)

参数maxlength表示可存储字符串的最大长度,定义变量时必须给出(因为VARCHAR2类型没有默认的最大长度),最大值32767字节。

注意:数据库类型中的VARCHAR2的最大长度是4000字节,所以一个长度大于4000字节的PL/SQL中的类型VARCHAR2变量不可以赋值给给数据库中的一个VARCHAR2变量,而只能赋值给LONG类型的数据库变量

  • CHAR类型:表示指定长度的字符串,其语法格式如下:

 CHAR(maxlength)

CHAR类型的默认最大长度为1。与VARCHAR2不同,maxlength可以不指定,默认为1。如果赋给CAHR类型的值不足maxlength, 则会在其后面用空格补全,这也是不同于VARCHAR2的地方

注意:数据库类型中的CHAR只有2000字节,所以如果PL/SQL中的CAHR类型变量的长度大于2000字节,则不能赋给数据库中的CHAR

  • LONG类型:表示可变的字符串,最大长度是32 767字节。数据库类型中的LONG最大长度可达2GB。
  • NCHAR和NVARCHAR2类型:PL/SQL 8.0以后加入的类型,它们长度根据国字符集来确定。

3、日期类型

日期类型只有一种,即DATE类型,用来存储日期和时间信息。DATE类型的存储空间是7字节,分别使用1字节存储世纪、年、月、天、小时、分钟和秒。

4、布尔类型

布尔类型也只有一种,即BOOLEAN类型,主要用于程序的流程控制和业务逻辑判断,其变量值可以是TRUE、FALSE或NULL中的一种。

特殊数据类型

5、特殊数据类型

5.1、%TYPE类型

使用%TYPE关键字 可以声明一个与指定列相同的数据类型,它通常紧跟在指定列名的后面

 --声明一个与emp表中job列的数据类型完全相同的变量var_job
 declare
 var_job emp.job%type;

使用%TYPE定义变量有两个好处:

  • 无需手动指定数据类型
  • 自动适配数据类型变化
 --使用%TYPE类型的变量输出emp表中编号为7369的员工名称和职务信息
 set serveroutput on
 declare
     var_ename emp.ename%type;   --声明与ename列类型相同的变量
     var_job emp.job%type;       --声明与job列类型相同的变量
 begin
     select ename,job
     into var_ename,var_job
     from emp
     where empno=7369;       --检索数据,并保存在变量中
 dbms_output.puty_line(var_ename||'的职务是'||var_job);  --输出变量的值
 end;

注意:由于INFO子句中的变量只能存储一个单独的值,因此要求SELECT子句只能返回一行数据,这个由WHERE子句进行了限定。若SELECT子句返回多行数据,则代码运行后会返回错误信息

5.2、RECORD类型

在PL/SQL中,RECORD类型是一种复合数据类型,用于在单个变量中存储多个相关字段,每个字段可以有不同的数据类型。RECORD类型常用于从数据库表或多列查询中检索数据,并将这些数据存储在一个单一的变量中,便于操作和传递。

 TYPE record_type IS RECORD      --record_type表示要定义的记录数据类型名称
 (
 var_menber1 data_type [not_null] [:=default_value],   --data_type:表示成员变量的数据类型
 ...
 var_membern data_type [not_null] [:=default_value])

记录类型的声明类似于C或C++中的结构类型,并且成员变量的声明与普通PL/SQL变量的声明相同。

声明一个记录类型 emp_type,然后使用该类型的变量存储emp表中的一条记录信息,并输出这条记录信息,代码:

 declare
     type emp_type is record         --声明RECORD类型emp_type
     (
     var_ename varchar2(20),         --定义字段/成员变量
     var_job varchar2(20),
     var_sal number
     );
     empinfo emp_type;       --定义变量
 begin
     select ename,job,sal
     into empinfo
     from emp
     where empno=7369;       --检索数据
     /*输出员工信息*/
     dbms_output.put_line('员工'||empinfo.var_ename||'的职务是'||empinfo.var_job||'、工资是'empinfo.var_sal);
 end;
5.3、%ROWTYPE类型

%ROWTYPE类型的变量结合了"%TYPE类型"和"记录类型"变量的优点,它可以根据数据表中行的结构定义一种特殊的数据类型,用来存储从数据表中检索到的一行数据,其语法:

 rowVar_name table_name%ROWTYPE;
 --示例:声明一个%ROWTYPE类型的变量rowVAR_emp,然后使用该变量存储emp表中的一行数据
 declare
     rowVar_emp emp%rowType; --定义能够存储emp表中一行数据的变量rowVar_emp
 begin
     select *
     into rowVar_emp
     from emp
     where empno=7369;   --检索数据
     /*输出员工信息*/
     dbms_output.put_line('员工'||rowVar_emp.empno||',职务是'||rowVar_emp.job);
 end;

6、自定义变量和常量

6.1、定义常量

<变量名><数据类型>[(长度):=<初始值>];

 --示例:定义一个用于存储国家名称的可变字符串变量var_countryname,该变量的最大长度是50,并且该变量初始值是"中国"
 var_countryname varchar2(50):= '中国';
6.2、变量的初始化

PL/SQL定义了一个未初始化变量应该存储的内容,其值被赋值为NULL

6.3、PL/SQL表达式
(1)字符表达式

唯一的字符运算符就是并置运算符 "||",它的作用是把几个字符串连在一起,如表达式'Hello'||'World'||'!'的值就等于'Hello Wordl!'。

(2)布尔表达式

PL/SQL控制结构都涉及布尔表达式。布尔表达式是一个判断结果为真还是假的条件表达式,值有TRUE、FALSE或NULL;

布尔表达式有3个布尔运算符,即AND、OR和NOT,与高级语言中的逻辑运算符一样,它的操作对象时布尔变量或表达式

此外,BETWEEN操作符可以划定一个范围,在范围内则为真,否则为假;

IN操作符判断某一元素是否属于某个集合,属于则问真,不属于则为假

7、流程控制语句

控制语句 说明
if...then 判断IF正确,则执行THEN
if...then...else 判断IF正确,则执行THEN,否则执行ELSE
if...then...elsif 嵌套式判断
case 有逻辑地从数值中做出选择
loop...exit...END 循环控制,用判断语句执行EXIT
loop...exit when...END 同上,当WHRN为真时执行EXIT
while...loop...END 当WHILE为真时循环
for...in...loop...END 已知循环次数的循环
goto 无条件转向控制
7.1、选择语句
(1)IF...THEN语句

只做一种情况或条件的判断,其语法:

 IF THEN  

 plsql_sentence  

 END IF;

 --定义两个字符串变量,赋值,使用IF...THEN比较其长度,并输出结果
 set serveroutput on
 declare
     var_name1 varchar2(50);     --定义两个字符串变量
     var_name2 varchar2(50);
 begin
     var_name1:='Wast';      --给两个字符串变量赋值
     var_name2:='xiaofei';
     if length(var_name1) < length(var_name2) then   --比较长度大小
     /*输出比较后的结果*/
     dbms_output.put_line('字符串"'||var_name1||'"的长度比字符串"'||var_name2||'"的长度小')
     end if;
 end;

如果IF后面的条件表达式存在"并且"或者"非"等逻辑运算,则可以使用AND、OR、NOT等逻辑运算符。另外,如果要判断IF后面的条件表达式的值是否为空值,则需要在条件表达式中使用is和null关键字,如:

 if last_name is null then
 ...
 end if;
(2)IF...THEN...ELSE语句

可以实现判断两种情况,只有IF后面的条件表达式为FALSE,程序就会执行ELSE语句下面的PL/SQL语句,其格式:

IF < condition_expression> THEN  

plsql_sentence1;  

ELSE  plsql_sentence2;  

END IF;

 --只有年龄大于或等于60岁,才可以申请退休的功能,否则程序会提示不可申请退休
 set serveroutput on
 declare
     age int:=55;
 begin
     if age >= 60 then
     dbms_output.put_line('您可以申请退休了!');
     else
     dbms_output.put_line('您小于60岁,不可以申请退休!')
     end if;
 end;
(3)IF...THEN...ELSIF语句

实现多分支判断选择,使程序的判断条件更丰富。如果该语句中的哪个判断分支的表达式为TRUE,那么程序就会执行对应的PL/SQL语句,其格式:

 IF < condition_expression1 > THEN  

 plsql_sentence_1;  

 ELSIF < condition_expression2 >  

 THEN plsql_sentence_2;

  ...  

 ELSE plsql_sentence_n;

 END IF;

 --首先指定一个月份数值,然后使用其语句判断它所属的季节,并输出季节信息:
 set serveroutput on
 declare
     month int:=6;
 begin
     if month >= 0 and month <= 3 then
         dbms_output.put_line(month||'月是春季');
     elsif month >= 4 and month <= 6 then
         dbms_output.put_line(month||'月是夏季');
     elsif month >= 7 and month <= 9 then
         dbms_output.put_line(month||'月是秋季');
     elsif month >= 10 and month <= 12 then
         dbms_output.put_line(month||'月是冬季');
     else
         dbms_output.put_line('对不起,月份不合法!');
     end if;
 end;
(4)case语句

CASE语句的执行方式与IF..THEN...ELSIF语句十分相似。在CASE关键字的后面有一个选择器,它通常是一个变量,程序就是从这个选择器开始执行

 CASE < selector>

 WHTN THEN plsql_sentence_1;

 WHTN THEN plsql_sentence_2;

 ...

 WHTN THEN plsql_sentence_n;

 [ELSE plsql_sentence;]

 END CASE;

 --示例:指定一个季度数的值,然后使用CASE语句判断它所包含的月份信息并输出
 set serveroutput on
 declare
     session int:=3;
     aboutInfo varchar2(50);
 begin
     case season
     when 1 then
     aboutInfo := season||'季度包括1,2,3月份';
     case season
     when 2 then
     aboutInfo := season||'季度包括4,5,6月份';
     case season
     when 3 then
     aboutInfo := season||'季度包括7,8,9月份';
     case season
     when 4 then
     aboutInfo := season||'季度包括10,11,12月份';
     else 
     aboutInfo := season||'季度不合法';
     end case;
     dbms_output.put_line(aboutinfo);
 end;

在执行多种情况判断时,建议使用CASE语句替换IF...THEN...ELSIF语句。

7.2、循环语句
(1)LOOP语句

LOOP语句会先执行一次循环体,然后判断EXIT WHEN关键字后面的条件表达式的值是TRUE还是FALSE。如果是TRUE,程序会退出循环体;否则再次执行循环体。这使得程序至少能执行一次循环体,其格式:

 LOOP

 plsql_sentence;

 EXIT WHEN end_condition_exp

 END LOOP

 --示例:使用LOOP计算前100个自然数的和,并输出到屏幕
 set serveroutput on
 declare
     sum_i int:= 0;
     i int:= 0;
 begin
     loop
     i:=i+1;
     sum_i = sum_i+i;
     exit when i =100;   --当循环到100时退出循环体
     end loop;
     dbms_output.put_line('前100个自然数的和是:'||sum_i);
 end;
(2)WHILE语句

WHILE语句根据它的条件表达式的值执行零次或多次循环体,在每次执行循环体之前,首先要判断条件表达式的值是否为TRUE,若为TRUE,则程序执行循环体;否则退出WHILE循环,然后继续执行WHILE语句后面的其他代码,其语法:

 WHILE condition_expression LOOP

 plsql_sentence;

 END LOOP;

 --示例:使用WHILE语句计算前100个自然数的和,并输出到屏幕
 set serveroutput on
 declare
     sum_i int := 0;
     i int := 0;
 begin
     while i<=99 loop    --当i的值等于100时,程序退出WHILE循环
     i:=i+1
     sum_i := sum_i+i,
     end loop;
     dbms_output.pu_line('前100个自然数的和是:'||sum_i);
 end;
(3)FOR语句

FOR语句时一个可预置循环次数的循环控制语句,它有一个循环计数器,通常是一个整型变量,通过这个循环计数器来控制循环执行的次数。该计数器可以从小到大进行记录,也可相反。另外,该计数器值的合法性由上限值和下限值控制,若计数器值在上限值和下限值的范围内,则程序执行循环;否则终止;其格式:

 FOR variable_counter_name in [REVERSE] lower_limit..upper_limit LOOP

 plsql_sentence;

 END LOOP;

 --示例:使用FOR语句计算前100个自然数中偶数之和,并输出到屏幕
 set serveroutput on
 declare
     sum_i := 0;
 begin
     for i in reverse 1..100 loop    --遍历前100个自然数
     if mod(i,2)=0 then              --判断是否为偶数
     sum_i:=sum_i+i;                 --计算偶数和
     end if;
     end loop;
     dbms_output.put_line('前100个自然数中偶数之和是:'||sum_i);
 end;
(4)GOTO语句

 GOTO label;

这个是无条件转向语句。当执行GOTO语句时,控制程序会立即转到由标签标识的语句中。其中,label是在PL/SQL中定义的符号。标签使用双箭头括号(<<>>)括起来

 --示例
 ..  --程序其他部分
 <>       --定义了一个转向标签goto_mark
 ..  --程序其他部分
 IF no>98050 THEN
     GOTO goto_mark;     --如果条件成立,则转向goto_mark继续执行
 ..  --程序其他部分

8、游标

游标提供了一种从表中检索数据并进行操作的灵活手段,游标主要用在服务器上,处理由客户端发送给服务端的SQL语句,或是批处理、存储过程、触发器中的数据处理请求。游标的作用就相当于指针,通过游标PL/SQL程序可以一次处理查询结果集中的一行,并可以对该数值执行特定操作。

在Oralce中,通过游标操作数据主要使用显式游标和隐式游标。另外还包括具有引用类型特性的REF游标

8.1、基本原理

在PL/SQL块中执行SELECT、INSERT、UPDATE和DELETE语句时,Oracle会在内存中为其分配上下文区,即一个缓冲区游标是指向该区的一个指针,或是命名一个工作区,或是一种结构化数据类型。游标为应用程序提供了一种具有对多行数据查询结果集中的每一行数据分别进行单独处理的方法,是设计嵌入式SQL语句的应用程序的常用编程方法。

8.2、显式游标

显式游标是由用户声明和操作的一种游标,通常用于操作查询结果集,使用它处理数据的步骤包括声明游标、打开游标、读取游标和关闭游标4个步骤。其中,读取游标可能需要反复操作,因为游标每次只能读取一行数据,所以对于多条记录,需要反复读取。

(1)声明游标

声明游标主要包括指定游标名称和为游标提供结果集的SELECT语句,其格式:

 CURSOR cur_name[(input_parameter1[,input_paramater2]...)] [RETURN ret_type]

 IS select_ setence;  ​

 --cur_name:表名所声明的游标名称

 --ret_type:表名执行游标操作后的返回值类型,这是一个可选项

 --select_sentence:游标所使用的SELECT语句,为游标的反复读取提供了结果集

 --input_paramater1:作为游标的"输入参数"可以有多个,这是一个可选项。它指定用户在打开游标后向游标中传递的值,该参数的定义和初始化格式如下:

 para_name [IN] DATATYPE [{:= | DEFAULT} para_value]

 --示例:声明游标,用来读取emp表中职务为销售员(SALESMAN)的员工信息
 declare
     cursor cur_emp(var_job in varchar2:='SALESMAN')
     is select empno,ename,sal
         from emp
         where job=var_job;

在上述代码中,首先声明了一个名称为cur_emp的游标,并定义了一个输入参数var_job(类型为varchar2,但不可以指定长度,否则程序报错),该参数用来存储员工的职务(初始值为SALESMAN);然后使用SELECT语句检索得到职务是销售员的结果集,以等待游标逐行读取它。

(2)打开游标

游标声明完后,必须打开游标才能使用,其格式:

 OPEN cur_name[(para_value[,para_value2]...)];

打开游标就是执行定义的SELECT语句。执行完毕,将查询结果装入内存,游标停在查询结果的首部(注意,并不是第一行)。打开一个游标时,会完成以下几件事:

  • 检查联编变量的取值
  • 根据联编变量的取值,确定活动集
  • 活动集的指针指向第一行
 --打开游标cur_emp,给游标的"输入参数"赋值为"MANAGER"
 OPEN cur_emp('MANAGER');        --若省略('MANAGER')则表示使用默认值
(3)读取游标

当打开一个游标后,就可以读取游标中的数据了,读取游标就是逐行将结果集中的数据保存到变量中。读取游标使用FETCH...INTO语句,其格式:

 FETCH cur_name INTO {variable};

在游标中包含一个数据行指针,它用来指向当前数据行。刚打开游标时,指针指向结果集中的第一行,当使用FETCH...INTO语句读取数据完毕之后,游标中的指针将自动指向下一行数据。这样,就可以在循环结构中使用FETCH...INTO来读取数据,知道指针指向结果集中最后一条记录为止,这时游标的%FOUND属性值为FALSE;

 --声明一个检索emp表中员工信息的游标,然后打开游标,并指定检索职务是MANAGER的员工信息,接着使用FETCH...INTO语句和WHILE循环语句读取游标中的所有员工信息,最后输出读取的员工信息
 set serveroutput on
 declare
     /*声明游标,检索员工信息*/
     cursor cur_emp(var_job in varchar2:='SALESMAN')
     is select empno,ename,sal
         from emp
         where job=var_job;
     type record_emp is record   --声明一个记录类型
     (
         /*定义当前记录的成员变量*/
         var_empno emp.empno%type,
         var_ename emp.ename%type,
         var_sal emp.sal%type
     );
     emp_row record_emp;     --声明一个record_emp类型的变量
 begin
     open cur_emp('MANAGER');    --打开游标
         fetch cur_emp into emp_row;  --先让指针指向结果集中的第一行,并将值报错到emp_row中
     while cur_emp%found loop
         dbms_output.put_line(emp_row.var_ename||'的编号是'||emp_row.var_empno||',工资是'||emp_row.var_sal);
         fetch cur_emp into emp_row; --让指针指向结果集中的下一行,并将值保存到emp_row中
     end loop;
     close cur_emp;      --关闭游标
 end;
 /
(4)关闭游标

当所有活动集都被检索以后,游标就应该被关闭。其语法:

CLOSE cur_name;

一旦关闭了游标,SELECT操作就会被关闭,并释放占用的内存区。如果再从游标提取数据就是非法的,这样做会产生以下两种错误:

 ORA-1001:Invalid CUSOR      --非法游标
 ORA-1002:FETCH out of sequence  --超出界限
8.3、隐式游标

在执行一个SQL语句时,Oracle会自动创建一个隐式游标,这个游标是内存中处理该语句的工作区域。隐式游标主要是处理数据操作语语句(如UPDATE、DELETE语句)的执行结果,当然在特殊情况下,也可以处理SELECT语句的查询结果。由于隐式游标也有属性,因此当使用隐式游标的属性时,需要在属性前面加上隐式游标的默认名称-SQL

在实际的PL/SQL编程中,经常使用隐式游标来判断更新数据行或删除数据行的情况

 --示例:把emp表中销售员的工资上调20%,然后使用隐式游标SQL的%ROWCOUNT属性输出上调工资的员工数量
 set serveroutput on
 ​
 begin
     update emp
     set sal=sal*(1+0.2)
     where job='SALESMAN';
     if sql%notfound then
     dbms_output.put_line('没有员工需要上调工资');
     else    --若UPDATE语句没有影响到一行数据
     dbms_output.put_line('有'||sql%rowcount||'个员工工资上调20%');
     end if;
 end;
 /
8.4、游标的属性

无论是显式游标还是隐式游标,都具有%FOUND、%NOTFOUND、%ROWCOUNT和%ISOPEN 4个属性,通过这4个属性可以获知SQL语句的执行结果以及该游标的状态信息

游标属性只能用在PL/SQL的流程控制语句内,而不能用在SQL语句内。

(1)是否找到游标(%FOUND)

布尔型属性,如果SQL语句至少影响到一行数据,则该属性为TRUE否则为FALSE,检查此属性可以判断是否结束游标使用

 open cur_emp;                               --打开游标
 fetch cur_emp into var_ename,var_job;       --将第一行数据放入变量中,游标后移
 loop
     exit when not cur_emp%found;            --使用了%FOUND属性
 end loop;

在隐式游标中%FOUND属性的引用方法是SQL%FOUND:

delete from emp where empno = emp_id;		--emp_id为一个有值变量
if sql%found then	--删除成功则将该行员工编号写入success表中
	insert into success values(empno);
else		--失败则写入fail表中
	insert into fail values(empno);
end if;
(2)是否没找到游标(%FOUND)

与%FOUND相反,这里省略

(3)游标行数(%ROWCOUNT)

%ROWCOUNT属性 记录了游标抽取过的记录行数,也可以理解为当前游标所在的行号。这个属性在循环判断中也很有用,使得不必抽取所有记录行就可以中断游标操作。

 loop
     fetch cur_emp into var_empno,var_ename,var_job;
     exit when cur_emp%rowcount = 10;    --只抽取10条记录
     ...
 end loop;

还可以使用FOR语句控制游标的循环,系统隐含地定义了一个数据类型为ROWCOUNT的记录,作为循环计数器,将隐式地打开和关闭游标

(4)游标是否打开(%ISOPEN)

%ISOPEN属性 表示游标是否处理打开状态。在实际应用中,使用一个游标前,第一步往往是检查它的%ISOPEN属性,看其是否已打开,若没有,要打开游标再往下操作。

 if cur_emp%isopen then
     fetch cur_emp into var_empno,var_ename,var_job;
 else
     open cur_emp;
 end if;

在隐式游标中此属性的引用方法是SQL%ISOPEN。隐式游标中SQL%ISOPEN属性总为TRUE,因此在隐式游标中不用打开和关闭游标,也不用检查其打开状态

(5)参数化游标

在定义游标时,可以带上参数,使得在使用游标时,根据参数不同所选中的数据行业不同,达到动态使用的目的

 --声明一个游标,用于检索指定员工编号的员工信息,然后使用游标的%FOUND属性来判断是否检索到指定员工编号的员工信息
 set serveroutput on
 declare
     var_ename varchar2(50); 
     var_job varchar2(50);
     /*声明游标,检索指定员工编号的员工信息*/
     cursor cur_emp
     is select ename.job
         from emp
         where empno=7499;
 begin
     open cur_emp;
     fetch cur_emp into var_ename,var_job;
     if cur_emp%found then
         dbms_output.put_line('编号是7499的员工名称为:'||var_ename||',职务是:'||var_job);
     else
         dbms_output.put_line('无数据记录');
     end if;
 end;
 /
8.5、游标变量
(1)声明游标变量

游标变量时一种引用类型。当程序运行时,他们可以指向不同的存储单元。如果要使用引用类型,首先要生命该变量,然后使用相应的存储单元必须被分配。PL/SQL中的引用类型变量通过下述语法进行声明:

 REF type

type是已经被定义的类型。REF关键字指明新的类型必须是一个指向经过定义的类型的指针。因此,游标变量可以使用的类型就是REF CURSOR。定义一个游标变量其语法:

 TYPE <类型名> IS REF CURSOR
 RETURN <返回类型>

游标变量的返回类型必须是一个记录类型。它可以被显式声明为一个用户定义的列表,或隐式使用%ROWTYPE进行声明。在定义了引用类型后就可以声明该变量了

 set serveroutput on
 declare
     type t_StudentRef is ref cursor     --定义使用%ROWTYPE
     return students%rowtype;
     type t_AbstractstudentsRecord is record(    --定义新的记录类型
         sname students.sname%type,
         sex students.sex%type
     );
     v_AbstractStudentsRecord t_AbstractstudentsRecord;
     type t_AbstractStudentsRed is ref corsor    --使用记录类型的游标变量
     return t_AbstractStudentsRecord;
     type t_NameRef2 is ref cursor       --另一类型定义
     return v_AbstractStudentsRecord%type;
     v_StudentCV t_StudentsRef;      --声明上述类型的游标变量
     v_AbstractStudentCV t_AbstractStudentsRef;

在上述代码中极少的游标变量时受限的,它的返回类型只能是特定类型。而在PL/SQL语句中,还有一种非受限游标变量,它在声明时没有return子句,一个非受限游标变量可以为任何查询打开

 declare
     --定义非受限游标变量
     type t_FlexibleRefIS ref cursor;
     --游标变量
     V_CURSORVar t_FlexiableRef;
(2)打开游标变量

如果要将一个游标变量与一个特定的SELECT语句相关联,需要使用OPEN FOR语句

 OPEN<游标变量>FOR;

如果游标变量是受限的,则SELECT语句的返回类型必须与游标变量所受限的记录类型匹配,如果不匹配,则Oracle会返回错误ORA_6504。

 --打开游标变量v_StudentSCV
 DECLARE
     TYPE t_StudentRef IS REF CURSOR     --定义使用%ROWTYPE
     RETURN STUDENTS%ROWTYPE;
     v_StudentSCV t_StudentRef;      --定义新的记录类型  
 BEGIN
     OPEN v_StudentSCV FOR
         SELECT * FROM STUDENTS;
 END;
(3)关闭游标变量

游标变量的关闭和静态游标的关闭类型,均使用CLOSE语句。关闭已经关闭的游标变量是非法的。

8.6、通过FOR语句循环游标

在使用隐式游标或显式游标处理具有多行数据的结果集时,可以配合使用FOR语句来完成。在使用FOR语句遍历游标中的数据时,可以把它的计时器看做一个自动的RECORD类型的变量

在FOR语句中变量隐式游标中的数据时,通常在关键字IN的后面提供由SELECT语句检索的结果集,在检索结果集的过程中,Oracle系统会自动提供一个隐式的游标SQL

 --使用隐式游标和FOR语句检索出的职务是销售员的员工信息并输出
 set serveroutput on
 begin
     for emp_record in (select empno,ename,sal from emp where job='SALESMAN')    --遍历隐式游标中的记录
     loop
         dbms_output.put('员工编号:'||emp_record.empno); 
         dbms_output.put(':员工名称:'||emp_record.ename);    
         dbms_output.put_line(':员工工资:'||emp_record.sal); 
     end loop;
 end;
 /

在FOR语句中变量显式游标中的数据时,通常在关键字IN的后面提供游标的名称,其格式:

 FOR var_auto_record IN cur_name LOOP

 plsqlsentence;

 END LOOP;

 --使用显式游标和FOR语句检索部门编号是30的员工信息并输出
 set serveroutput on
 declare
     cursor cur_emp is 
         select * from emp
         where deptno = 30;      --检索部门编号为30的员工信息
 begin
     for emp_record in cur_emp       --遍历员工信息
     loop
         dbms_output.put('员工编号:'||emp_record.empno);
         dbms_output.put(':员工名称:'||emp_record.ename);
         dbms_output.put_line(': 员工职务:'||emp_reocrd.job);
     end loop;
 end;
 /

注意:在使用游标(包括显式和隐式)的FOR循环中,可以声明游标,但不用进行打开、读取和关闭游标等操作,这些由Oracle自动完成

9、异常处理

9.1、异常处理方法
(1)预定义异常处理

每当PL/SQL程序违反了Oracle的规则或超出系统的限制时,系统就自动地产生内部异常。每个Oracle异常都有一个号码,但异常必须按名个处理。因此PL/SQL对那些常见的异常预定义了异常名。

(2)用户自定义异常
  • 异常声明:包括预定义异常和用户自定义异常。用户定义的异常只能再PL/SQL块的声明部分进行声明,声明方式与变量声明类似
  • 抛出异常:使用RAISE语句显式地提出
  • 为内部异常命名:必须使用OTHERS处理程序或用伪命令EXCEPTION_INIT来处理未命名的内部异常

注意:异常是一种状态而不是一个对象,因此异常名不能出现在赋值语句或SQL语句中。PRAGMA EXCEPTION_INIT的作用是将一个异常名与一个Oracle错误号码联系起来。因此,用户就可以按名称引用任何内部异常,并为它编写一个特定的处理程序

9.2、异常处理语法
(1)声明异常
 exception_name EXCEPTION
(2)为内部异常命名
 PRAGE EXCEPTION_INIT(exception_name.ORA_errornumber);
(3)异常定义
 DECLARE
     exception_name EXCEPTION;
 BEGIN
     IF condition THEN
         RAISE exception_name;
     END IF;
     EXCEPTION
         WHERE exception_name THEN
         statement;
 END;
(4)异常处理
 SET SERVEROUTPUT ON
 EXCEPTION
     WHEN exception1 THEN
         statement1
     WHEN exception2 THEN
         statement2
     ...
     WHEN OTHERS THEN
         statement3
(5)使用SQLCODE和SQLERRM函数定义提示信息
 DBMS_OUTPUT.PUT_LINE('错误号:'||SQLCODE);DBMS_OUTPUT.PUT_LINE('错误号:'||SQLERRM);
9.3、预定义异常

以下是一些在Oracle官方文档中查找预定义异常的常用链接:

  1. Oracle Database PL/SQL Language Reference
  2. Oracle Database PL/SQL Packages and Types Reference
  3. Oracle Database Error Messages

 --示例:使用SELECT INTO语句检索emp表中部门编号为10的员工记录信息,然后使用TOO_MANY_ROWS预定义异常捕获错误信息并输出
 set serveroutput on
 declare
     var_empno number;           --定义变量,存储员工编号
     var_ename varchar2(50);     --定义变量,存储员工名称
 begin
     select empno,ename into var_empno,var_ename
     from emp
     where deptno=10;            --检索部门编号为10的员工信息
     if sql%found then           --若检索成功,则输出员工信息
         dbms_output.put_line('员工编号:'||var_empno||':员工名称'||var_ename);
     end if;
 exception           --捕获异常
     when too_many_rows then
         dbms_output.put_line('返回记录超过一行');
     when no_data_found then
         dbms_output.put_line('无数据记录');
 end;
 /
9.4、自定义异常
(1)错误编号异常

错误编号是指在Oracle系统发生错误时,系统会显示错误号和相关描述信息的异常。虽然直接使用错误编号也可以完成异常处理 ,但错误编号较为抽象,不易于用户理解和记忆。对于这种异常,首先在PL/SQL块的声明部分(DECLARE部分)使用EXCEPTION类型定义一个异常变量名,然后使用语句PRAGMA EXCEPTION_INIT为"错误编号"关联"这个异常变量名",接下来就可以像对待系统预定义异常一样处理了。

 --插入一条已存在的记录,会报错
 insert into dept values(10,'研发部','QINGDAO');

 --首先定义错误编号"00001"的异常处理,然后向dept表中插入一条能够违反"唯一约束条件"的记录,最后在EXCEPTION代码体中输出异常提示信息
 set serveroutput on
 declare
     primary_iterant exception;
     pragma exception_init(primary_iterant,-00001);
 begin
     /*向dept表中插入一条与已有主键值重复的记录,以便引发异常*/
     insert into dept values(10,'研发部','青岛');
 exception
(2)业务逻辑异常

在实际的应用中,程序开发人员可以根据具体的业务逻辑规则自定义一个异常。这样,当用户操作违反业务逻辑规则时,就会引发一个异常,从而中断程序的正常执行,并转到自定义的异常处理部分。

无论是预定义异常,还是错误编号异常,都是由Oracle系统判断的错误,但业务逻辑异常是Oracle系统本身无法知道的,这样就需要一个引发异常的机制,引发业务逻辑异常通常使用RAISE语句来实现。当引发一个异常时,控制就会转到EXCEPTION异常处理部分执行异常处理语句。业务逻辑异常首先要在DECLARE部分使用EXCEPTION类型声明一个异常变量,然后在BEGIN部分根据一定的业务逻辑规则执行RAISE语句,最后在EXCEPTION部分编写异常处理语句

 --自定义一个异常变量,向dept表中插入数据时,若判断loc字段的值为NULL,则使用RAISE语句引发异常,并将程序的执行流程转入EXCEPTION部分中进行处理
 set serveroutput on
 declare
     null_exception exception;       --声明一个EXCEPTION类型的异常变量
     dept_row dept%rowtype;          --声明ROWTYPE类型的变量dept_row
 begin
     dept_row.deptno := 66;          --给部门编号变量赋值
     dept_row.dname := '公关部';      --给部门名称变量赋值
     insert into dept
     values(dept_row.deptno,dept_row.dname,dept_row.loc);    --插入一条记录
     if dept_row.loc is null then        --如果判断loc变量的值为NULL
         raise null_exception;           --引发NULL异常,程序转入EXCEPTION部分
      end if;
 exception
     when null_exception then            --当RAISE引发的异常是NULL_EXCEPTION时
     dbms_output.put_line('loc字段的值不许为null'); --输出异常提示信息
     rollback;           --回滚插入的数据记录
 end;

学习永无止境,让我们共同进步!!

你可能感兴趣的:(Oracle,oracle,数据库)