Oracle编程入门经典 第10章 PLSQL
2013-05-08 10:28 夜雨瞳 阅读(2046) 评论(1) 编辑 收藏 举报目录
10.1 总览... 1
10.2 基于程序块的开发... 1
试验:PL/SQL程序块... 2
工作原理... 2
块嵌套... 2
10.3 声明... 3
10.3.1 变量和常量... 3
10.3.2 为变量和常量赋值... 5
10.3.3 可视性和作用域... 5
10.3.4 定义不确定内容,解释NULL. 7
10.3.5 使用%TYPE和%ROWTYPE. 8
10.4 PL/SQL数据类型... 9
10.4.1 字符数据类型... 9
10.4.2 数值数据类型... 9
10.4.3 BOOLEAN.. 10
10.5 PL/SQL集合... 10
10.5.1 记录... 10
10.5.2 PL/SQL表... 12
试验:删除PL/SQL表记录... 14
工作原理... 16
FIRST、NEXT和LAST. 16
10.5.3 VARRAYS. 17
试验:建立和使用VARRAY. 17
10.5.4 NESTED TABLE. 19
试验:订单... 19
10.6 游标... 21
10.6.1 显式游标... 21
10.6.2 隐式游标... 23
10.6.3 游标属性... 24
试验:隐式和显式游标... 25
工作原理... 26
10.6.4 REF CURSORS和游标变量... 27
试验:强类型REF CURSOR. 27
试验:弱类型REF CURSOR. 28
10.6.5 单独SELECT. 30
10.7 控制语句... 31
10.7.1 条件... 31
10.7.2 循环... 32
10.7.3 控制语句概要... 34
10.8 错误处理... 34
10.8.1 异常部分... 34
10.8.2 预定义异常... 35
10.8.3 用户定义异常... 36
试验:用户麦片粥的温度如何... 36
10.8.4 PRAGMA EXCEPTION_INIT. 38
10.8.5 异常传播... 39
试验:异常传播... 39
10.9 小结... 44
在本章中,我们将要讨论PL/SQL编程基础知识。特别是:
- l 开发匿名(或者没有命名)PL/SQL程序块的含义
- l 如何在PL/SQL中声明变量和常量
- l SQL和PL/SQL数据类型之间的差异
- l 怎样使用游标在用户代码中将SQL与PL/SQL进行集成
- l 怎样使用内建的功能,执行常见的例程
- l 在用户代码中可以使用哪些循环结构和条件语句,来操纵PL/SQL代码的进程流
- l 怎样俘获和控制用户代码中的潜在错误
Oracle 9i产品帮助文档:
http://docs.oracle.com/cd/B10501_01/index.htm
可根据自己需要进行查询,包含了众多的文档。
Sample Schemas的目录:
http://docs.oracle.com/cd/B10501_01/server.920/a96539/toc.htm
Sample Schemas的文档(示例模式的表及介绍):
http://docs.oracle.com/cd/B10501_01/server.920/a96539.pdf
10.1 总览
PL/SQL是大体基于另一种编程语言Ada的结构化编程语言。就如我们已经提及的,SQL语句不能单独让用户构建应用;用户需要使用C或者Java等 其它语言来连接数据库,完成应用的工作。PL/SQL是Oracle的专有产品,由Oracle公司编写,并且随着向业界发布新的数据库而进行了改善。
PL/SQL不能在Oracle以外的其它数据库中运行。通常,用户可以采用过程、函数、程序包或者触发器的形式,编写PL/SQL代码并且存储它。这些代码可以存储在数据库中,并且能够由具有适当特权的数据库用户重新使用。在第5章中,我们谈论了在共享池中缓存SQL语句,让许多用户对其进行重用。一量将PL/SQL存储在共享池中让许多用户对其进行访问,它就不会有什么区别。
PL/SQL对大小写不敏感,所以即使用户在我们本章的例子中看到了混合的字母形式,用户也应该选择符合用户和用户开发团队的编码标准。
10.2 基于程序块的开发
PL/SQL代码使用了程序块(block),模块化方式进行构建。它们可以为更大的程序和应用构建程序块。每个程序块都是一组逻辑上的变量、可执行代码以及错误控制代码。
我们来讨论一个示例程序块,然后将其分解为三个部分:
SQL> set serveroutput on SQL> declare 2 l_text varchar2(100); 3 begin 4 l_text:='Hello,World!'; 5 dbms_output.put_line(l_text); 6 exception 7 when others then 8 dbms_output.put_line('We encountered an exception!'); 9 raise; 10 end; 11 / Hello,World! PL/SQL 过程已成功完成。
试验:PL/SQL程序块
向SQL*Plus输入如下代码(这些代码会向用户屏幕显示一些输出结果):
SQL> set serveroutput on SQL> declare 2 l_number number:=1; 3 begin 4 l_number :=1+1; 5 dbms_output.put_line('1 + 1 ='|| to_char(l_number)||'!'); 6 exception 7 when others then 8 dbms_output.put_line('We encountered an exception!'); 9 end; 10 / 1 + 1 =2! PL/SQL 过程已成功完成。
工作原理
我们的程序块中首先需要注意的就是声明部分,我们在那里声明了一个NUMBER类型的变量(L-NUMBER)。然后,我们输入了程序块的可执行部分,并且在那里为我们的变量进行了赋值,并将一些数据发送到DMBS_OUTPUT.PUT_LINE。
块嵌套
除了标准的PL/SQL程序块以外,用户还可以建立包含附加程序块,或者子程序的程序块。程序块可以在可执行部分和异常处理部分包含另外的程序块。在以下的示例中,用户将会看到指出第一个和第二个程序块开始和结束的注释。例如:
SQL> set serveroutput on SQL> declare 2 l_text varchar2(20); 3 begin 4 l_text :='First Block'; 5 dbms_output.put_line(l_text); 6 7 declare 8 l_more_text varchar2(20); 9 begin 10 l_more_text :='Second Block'; 11 dbms_output.put_line(l_more_text); 12 end; 13 end; 14 / First Block Second Block PL/SQL 过程已成功完成。
10.3 声明
10.3.1 变量和常量
对于每个变量,用户都必须规定名称和数据类型,以便用户可以在可执行部分为其规定数值。
SQL> declare 2 l_number_variable number; 3 begin 4 l_number_variable:=50; 5 end; 6 / PL/SQL 过程已成功完成。
用户可以选择在声明部分的相同代码行中为变量同仁(称为初始化变量)。
SQL> declare 2 l_number_variable number:=50; 3 begin 4 --NULL; means do nothing.The executable section 5 --needs at least one line of code to be valid 6 null; 7 end; 8 / PL/SQL 过程已成功完成。
常量的声明方式与变量大体相同,但是有一些区别需要注意。就如其名称所暗示的那样,常量不能改变。为了赋值,用户必须在声明的时候初始化常量。而且用户还必须在数据类型的左边规定保留字CONSTANT。例如:
SQL> declare 2 l_number_variable constant number:=50; 3 begin 4 null; 5 end; 6 / PL/SQL 过程已成功完成。
如果我们在声明块中声明常量的时候没有对其进行初始化,我们就会接收到一个错误。
SQL> declare 2 l_number_variable constant number; 3 begin 4 null; 5 end; 6 / l_number_variable constant number; * ERROR 位于第 2 行: ORA-06550: 第 2 行, 第 2 列: PLS-00322: 常数 'L_NUMBER_VARIABLE' 的说明必须包含初始赋值 ORA-06550: 第 2 行, 第 20 列: PL/SQL: Item ignored
这种错误正式称为异常,但是它是与我们在异常块中规定的异常不同类型的异常。这种异常会在程序块的编译期间俘获,并且不能恢复。程序块的异常部分不能获取这种错误(有关这个问题的更多信息,可以参看后面在声明中产生的异常部分)。
如果在程序块的可执行部分为常量赋值,我们就会激发不同的异常。
SQL> declare 2 l_number_variable constant number:=50; 3 begin 4 l_number_variable number:=51; 5 end; 6 / l_number_variable number:=51; * ERROR 位于第 4 行: ORA-06550: 第 4 行, 第 20 列: PLS-00103: 出现符号 "NUMBER"在需要下列之一时? := . ( @ % ; 符号 "." 被替换为 "NUMBER" 后继续。
10.3.2 为变量和常量赋值
最深见的是使用PL/SQL赋值操作符:=。使用这个操作符的语法如下所示:
variable datatype := expression; --在程序块的声明部分 variable :=expression; --在程序块的可执行部分 variable datatype default expression; --在程序块声明部分
这里是使用DEFAULT关键字为变量赋值的示例:
SQL> declare 2 l_days_in_week constant number:=7; 3 l_weeks_in_month number default 4; 4 begin 5 l_weeks_in_month:=5; 6 end; 7 / PL/SQL 过程已成功完成。
除了保留字DEFAULT以外,用户还可以选择在用户变量上使用NOT NULL操作符。使用NOT NULL意味着值可以改变,但是决不能够赋予NULL。试图为声明了NOT NULL属性的变量赋予NULL,将会导致抛出异常:
SQL> declare 2 l_number number not null :=1; 3 l_another_number number; 4 begin 5 l_number:=2; 6 l_number:=l_another_number; 7 end; 8 / declare * ERROR 位于第 1 行: ORA-06502: PL/SQL: 数字或值错误 ORA-06512: 在 line 6
10.3.3 可视性和作用域
当声明变量和常量的时候,有一些规则可以控制用户声明的作用域和可视性。作用域(Scope)是用户能够引用变量名称这样的标识符的程序块。只有用户处于可以使用非限定名称(也就是说,没有使用定义标识符的程序块名称作为标识符的前缀“--”)引用标识符的程序区域时,标识符才是可见的(visible)。
在单独的程序块中,整个程序块中都是在声明部分定义的标识符的作用域,标识符都可见。在子块声明中定义的标识符只有在子块本身中才处于它的作用域,才可见。以下的示例展示了这些情况:
SQL> declare 2 l_parent_number number; 3 begin 4 --l_parent_number is visiable and in cope 5 l_parent_number:=1; 6 7 declare 8 l_child_number number:=2; 9 begin 10 --l_child_number is visiable and in scope 11 dbms_output.put_line('parent + child = '|| to_char(l_parent_number + l_child_number)); 12 end; 13 14 --l_child_number is now not visiable nor in scope; 15 l_child_number :=2; 16 end; 17 / l_child_number :=2; * ERROR 位于第 15 行: ORA-06550: 第 15 行, 第 2 列: PLS-00201: 必须说明标识符 'L_CHILD_NUMBER' ORA-06550: 第 15 行, 第 2 列: PL/SQL: Statement ignored
在涉及嵌套块的时候,可视性还有一个重要的限制。用户可能会认为,在程序块的声明部分声明变量的时候,无论采用什么样的声明次序。这里是一个展示我们进行这种声明时所出现情况的示例:
SQL> declare 2 l_number number:=l_another_number; 3 l_another_number number:=10; 4 begin 5 null; 6 end; 7 / l_number number:=l_another_number; * ERROR 位于第 2 行: ORA-06550: 第 2 行, 第 19 列: PLS-00320: 此表达式的类型说明不完整或格式不正确 ORA-06550: 第 2 行, 第 11 列: PL/SQL: Item ignored
10.3.4 定义不确定内容,解释NULL
在Oracle中,NULL是表示缺失、不可知或者不适用的保留字。从根本上讲,它就是没有定义的内容。在以下的代码示例中,所有四个变量声明都会产生相同的结果:具有NULL值的VARCHAR2变量。
SQL> declare 2 l_value1 varchar2(100); 3 l_value2 varchar2(100):=''; 4 l_value3 varchar2(100):=null; 5 l_value4 varchar2(100) default null; 6 begin 7 null; 8 end; 9 / PL/SQL 过程已成功完成。
我们不仅可以将变量定义为NULL,而且就如我们早先看到的,我们还可以在PL/SQL程序块的可执行部分使用保留字NULL作为桩基模块(stub)或者占位符(placeholder),代替用户随后进行编码的内容。一些程序员将这些称为NO-OP(无操作)。这非常适用于在用户的PL/SQL程序块中没有可执行代码的时候,因为没有在用户的PL/SQL块中定义可执行代码将会导致编译错误发生:
SQL> declare 2 l_text varchar2(100); 3 begin 4 end; 5 / end; * ERROR 位于第 4 行: ORA-06550: 第 4 行, 第 1 列: PLS-00103: 出现符号 "END"在需要下列之一时: begin case declare exit for goto if loop mod null pragma raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge <a single-quoted SQL string> pipe
通过在用户可执行部分规定NULL作为桩基模块,您就作为随后使用的程序块的“桩基模块”。当用户编写过程、函数以及程序包这样的存储过程代码时,以及当用户需要临时为用户代码提供“桩基模块”,以便随后可以回来填充它的时候,NULL就更为有用。
10.3.5 使用%TYPE和%ROWTYPE
用户可以使用%TYPE和%ROWTYPE来声明变量,而不必规定特定的数据类型。这2个属性可以让我们规定变量,并且让变量的数据类型由表/视图列或者PL/SQL程序包变量来定义。
%TYPE和%ROWTYPE可以用于不同的原因。当用户声明单独的变量,而不是记录的时候,就可以使用%TYPE。当用户声明表示表、视图或者游标的完整行的记录变量时,就可以使用%ROWTYPE。例如:
SQL> conn hr/hr; 已连接。 SQL> desc departments; 名称 是否为空? 类型 ----------------------------------------- -------- ----------------------------DEPARTMENT_ID NOT NULL NUMBER(4) DEPARTMENT_NAME NOT NULL VARCHAR2(30) MANAGER_ID NUMBER(6) LOCATION_ID NUMBER(4) SQL> declare 2 l_dept departments%rowtype; 3 l_another_dept departments.department_name%type; 4 begin 5 l_dept.department_id:=1000; 6 l_dept.department_name:='Graphic Art'; 7 8 insert into departments(department_id,department_name) 9 values(l_dept.department_id,l_dept.department_name); 10 l_dept.department_id:=1001; 11 l_another_dept:='Web Design/User Interface'; 12 13 insert into departments(department_id,department_name) 14 values(l_dept.department_id,l_another_dept); 15 16 dbms_output.put_line('The departments created were '|| 17 l_dept.department_name || ' and '|| l_another_dept); 18 end; 19 / PL/SQL 过程已成功完成。
这里为用户提供了使用%TYPE和%ROWTYPE的实际知识。当用户编写过程、函数和程序包等内容的时候,它们就会变得更为重要。
10.4 PL/SQL数据类型
PL/SQL能够使用与SQL相同的数据类型,只是在界限上有些不同。
10.4.1 字符数据类型
首先,表10-1将与字符相关的数据类型的SQL范围和PL/SQL的范围进行了比较:
表10-1 SQL和PL/SQL字符数据类型比较
数据类型 |
SQL界限 |
PL/SQL界限 |
其它信息 |
CHAR |
1..2000 |
1..32767 |
|
LONG |
1..2GB |
1..32760 |
|
LONG RAW |
1..25B |
1..32760 |
|
RAW |
1..2000 |
1..32767 |
|
VARCHAR2 |
1..4000 |
1..32767 |
也VARCHAR和STRING(*) |
NCHAR |
1..2000(bytes) |
1..32767/3(UTF8) OR 1..32767/2(AL16UTF16) |
在Oracle 9i中,2种数据库National语言是UTF8或者AL16UTF16 |
NVARCHAR2 |
1..4000(bytes) |
1..32767/3(UTF8) OR 1..32767/2(AL16UTF16) |
在Oracle 9i中,2种数据库National语言是UTF8或者AL16UTF16 |
10.4.2 数值数据类型
- PLS_INTEGER
PLS_INTEGER是专为在PL/SQL使用而建立的数据类型。您不能在数据库的列中存储PLS_INTEGER。PLS_INTEGER是-2^31之间的有符号整数。它为我们提供了从-2147483648到2147483648的最大范围。PLS_INTEGER具有比NUMBER变量更小的范围,因此会占用更少的内存。另外,PLS_INTEGER能够利用CPU运算器,因此可以比使用数据库API执行算数的NUMBER和BINARY_INTEGER更快。
- BINARY_INTEGER
BINARY_INTEGER与PLS_INTEGER相似,都需要少于NUMBER变量的内存,但是它通常要比PLS_INTEGER更慢。BINARY_INTEGER是以下子类型的基本数据类型,见表10-2.
表10-2 BINARY_INTEGER子类型
子类型 |
定义 |
NATURAL |
非负数值变量 |
NATURALN |
不能为NULL的NATURAL变量 |
POSITIVE |
比0大的非负数值变量 |
POSITIVEN |
可以为NULL的POSITIVE变量 |
SIGNTYPE |
这种类型的变量可以为-1、0或者1.它们适用于PL/SQL应用中编程处理三种状态的逻辑 |
10.4.3 BOOLEAN
在PL/SQL中可以使用BOOLEAN数据类型。它不能够在SQL中用作数据库的列。BOOLEAN变量可以是TRUE、FALSE或者NULL,它能够作为条件语句的单一参数。
10.5 PL/SQL集合
在大多数编程语言中,提供这样或者那样的方式声明对象集合都很有必要。PL/SQL中也是如此,它具有可以用于这一目的的大量集合类型。它们是:
- l 记录。在称为记录的单独集合中可以存储一对多的标题属性。
- l PL/SQL表。这些集合是可以在用户的PL/SQL代码中使用的“表”,它们只存在于用户应用运行期间。它们非常类似于其它语言中的数组,不能够存储在数据库表中。
- l VARRAY。这是能够在表列中存储的集合。varray是通常用于存储小集合的固定大小的集合。
- l NESTED TABLE。这是另一种可以在表列中存储的集合。NESTED TABLE大小可变,因此适用于用户不知道集合大小,或者用户知道它要包含大量数据的时候。
10.5.1 记录
为了定义记录类型,可以使用如下语法:
TYPE <record_name> IS RECORD( field_declaration {,field_declaration,field_declaration} );
字段声明与其它变量声明大体相同。用户可以将用户字段定义为REF CURSOR以外的任何PL/SQL类型。通常,字段声明与PL/SQL声明部分的标准变量声明大体相同。例如:
SQL> set serverout on SQL> declare 2 type location_record_type is record( 3 street_address varchar2(40), 4 postal_code varchar2(12), 5 city varchar2(30), 6 state_province varchar2(25), 7 country_id CHAR(2) NOT NULL:='US' 8 ); 9 10 l_my_loc location_record_type; 11 begin 12 l_my_loc.street_address:='1 Oracle Way'; 13 l_my_loc.postal_code:='20190'; 14 l_my_loc.city:='Reston'; 15 l_my_loc.state_province:='VA'; 16 dbms_output.put_line('MY LOCATION IS:'); 17 dbms_output.put_line(l_my_loc.street_address); 18 dbms_output.put_line(l_my_loc.city ||','|| l_my_loc.state_province); 19 dbms_output.put_line(' '|| l_my_loc.postal_code); 20 dbms_output.put_line(l_my_loc.country_id); 21 end; 22 / MY LOCATION IS: 1 Oracle Way Reston,VA 20190 US PL/SQL 过程已成功完成。
如果记录变量基本记录类型,那么用户还可以将记录变量赋予另一个记录变量。如果基本记录类型不相同,无论记录中的字符是否相同,用户都不能将一个记录变量赋予另一个记录变量。在以下代码中,我们将会展示这个概念:
SQL> declare 2 type emp_rec_t is record( 3 empno number, 4 name varchar2(60), 5 job varchar2(60), 6 sal number(9,2), 7 location varchar2(255) 8 ); 9 10 type mgr_rec_t is record( 11 empno number, 12 name varchar2(60), 13 job varchar2(60), 14 sal number(9,2), 15 location varchar2(255) 16 ); 17 18 19 l_sean emp_rec_t; 20 l_chris emp_rec_t; 21 l_tom mgr_rec_t; 22 begin 23 l_sean.empno :=100; 24 l_sean.name :='Sean Dillon'; 25 l_sean.job:='Technologist'; 26 l_sean.sal:=99.99; 27 l_sean.location:='2d Floor,OSI Building'; 28 29 l_chris:=l_sean; 30 l_chris.empno:=101; 31 l_chris.name:='Christopher Beck'; 32 33 l_tom:=l_chris; 34 l_tom.empno:=5; 35 l_tom.name:='Tom Kyte'; 36 l_tom.job:='Technologist'; 37 end; 38 / l_tom:=l_chris; * ERROR 位于第 33 行: ORA-06550: 第 33 行, 第 10 列: PLS-00382: 表达式类型错误 ORA-06550: 第 33 行, 第 3 列: PL/SQL: Statement ignored
TOM记录和CHRIS记录具有不同的基本记录类型(分别是MGR_REC_T和EMP_REC_T)。因此,第35行的赋值会出现错误,并且会指出异常由错误类型引起。
10.5.2 PL/SQL表
PL/SQL表,或者有时也称为索引表(index-by tables),是可以在PL/SQL例程中使用,能够模仿数组的非永久表。用户可以定义一个PL/SQL表类型,然后声明这种类型的变量。接下来,用户就可以将记录增加到用户的PL/SQL表中,并且采用与引用数组元素大体相同的方式引用它们。这些表是一维数组,不应该与成熟的Oracle表相混淆。
PL/SQL表最初会很稀疏,这意味着用户可以声明表类型的变量,而它没有元素,所以不会占用空间。当用户声明表中的值的时候,用户就必须提供作为PL/SQL表仍为主键的整数值。我们来看看使用HR模式的一个示例:
SQL> set serverout on SQL> declare 2 type my_text_table_type is table of varchar2(200) 3 index by binary_integer; 4 type my_emp_table_type is table of employees%rowtype 5 index by binary_integer; 6 7 l_text_table my_text_table_type; 8 l_emp_table my_emp_table_type; 9 begin 10 l_text_table(1):='Some varchar2 value'; 11 l_text_table(2):='Another varchar2 value'; 12 13 l_emp_table(10).employee_id:=10; 14 l_emp_table(10).first_name:='Sean'; 15 l_emp_table(10).last_name:='Dillon'; 16 l_emp_table(10).email:='sdillon@somecorp.com'; 17 l_emp_table(10).hire_date:=SYSDATE; 18 l_emp_table(10).job_id:='ST_CLERK'; 19 20 l_emp_table(20).employee_id:=20; 21 l_emp_table(20).first_name:='Chris'; 22 l_emp_table(20).last_name:='Beck'; 23 l_emp_table(20).email:='clbeck@somecorp.com'; 24 l_emp_table(20).hire_date:=SYSDATE; 25 l_emp_table(20).job_id:='SH_CLERK'; 26 27 dbms_output.put('We have '||l_text_table.count||' varchar2 is'); 28 dbms_output.put_line('and '||l_emp_table.count||' employees.'); 29 dbms_output.put_line('-'); 30 dbms_output.put_line('vc2(1)='||l_text_table(1)); 31 dbms_output.put_line('vc2(2)='||l_text_table(2)); 32 dbms_output.put_line('-'); 33 dbms_output.put_line('l_emp_table(10)='||l_emp_table(10).first_name); 34 dbms_output.put_line('l_emp_table(20)='||l_emp_table(20).first_name); 35 end; 36 / We have 2 varchar2 isand 2 employees. -vc2(1)=Some varchar2 value vc2(2)=Another varchar2 value -l_emp_table(10)=Sean l_emp_table(20)=Chris PL/SQL 过程已成功完成。
在这个相对简单的示例中,引入了许多PL/SQL表的概念。我们来进一步分析在这里所做的工作,解释各个部分:
- 定义PL/SQL表
2 type my_text_table_type is table of varchar2(200) 3 index by binary_integer; 4 type my_emp_table_type is table of employees%rowtype 5 index by binary_integer; 6 7 l_text_table my_text_table_type; 8 l_emp_table my_emp_table_type;
- 为PL/SQL表赋值
10 l_text_table(1):='Some varchar2 value'; 11 l_text_table(2):='Another varchar2 value'; 12 13 l_emp_table(10).employee_id:=10; 14 l_emp_table(10).first_name:='Sean'; 15 l_emp_table(10).last_name:='Dillon'; 16 l_emp_table(10).email:='sdillon@somecorp.com'; 17 l_emp_table(10).hire_date:=SYSDATE; 18 l_emp_table(10).job_id:='ST_CLERK'; 19 20 l_emp_table(20).employee_id:=20; 21 l_emp_table(20).first_name:='Chris'; 22 l_emp_table(20).last_name:='Beck'; 23 l_emp_table(20).email:='clbeck@somecorp.com'; 24 l_emp_table(20).hire_date:=SYSDATE; 25 l_emp_table(20).job_id:='SH_CLERK';
表10-3 L_TEXT_TABLE表
索引 |
VARCHAR2(200) |
1 |
SOME VARCHAR2 VALUE |
2 |
ANOTHER VARCHAR2 VALUE |
表10-4 L_EMP_TABLE表(1)
INDEX |
EMPLOYEE_ID |
FIRST_NAME |
LAST_NAME |
|
10 |
10 |
SEAN |
DILLON |
SDILLON@SOMECORP.COM |
20 |
20 |
CHRIS |
BECK |
CLBECK@SOMECORP.COM |
- 引用PL/SQL表中的值
30 dbms_output.put_line('vc2(1)='||l_text_table(1)); 31 dbms_output.put_line('vc2(2)='||l_text_table(2));
- 从PL/SQL表中删除记录
我们可以使用一些技术来从我们的PL/SQL表中删除记录。我们要通过一个示例展示这些技术。
试验:删除PL/SQL表记录
- 首先,交2个变量声明为具有VARCHAR2类型的PL/SQL表类型。
SQL> set serveroutput on SQL> declare 2 type my_text_table_type is table of varchar2(200) 3 index by binary_integer; 4 l_text_table my_text_table_type; 5 l_empty_table my_text_table_type;
- 接下来,在我们的表中建立一些表项,并且使用COUNT方法展示表中的表项数量。
6 begin 7 l_text_table(10):='A value'; 8 l_text_table(20):='Another value'; 9 l_text_table(30):='Yet another value'; 10 11 dbms_output.put_line('We start with '|| l_text_table.count || ' varchar2s '); 12 dbms_output.put_line('-');
- 接下来,我们要使用DELETE方法,传递将要从表中删除的索引。我们向这个函数传递了二进制整数20,指出我们要删除相应于索引20的表项,而不是第20个表项。所以,在这个函数执行完毕之后,表项ANOTHER VALUE就消失了:
14 l_text_table.DELETE(20); 15 dbms_output.put_line('After using the DELETE operator on the second '); 16 dbms_output.put_line('record(ie,DELETE(20),we have '|| l_text_table.count); 17 dbms_output.put_line(' varchar2s'); 18 dbms_output.put_line('-'); 19
- 在接下来的几行中,我们要使用根本没有索引参数的DELETE方法。这将会从表中删除所有表项:
20 l_text_table.DELETE; 21 dbms_output.put_line('After using the DELETE operator,we have '); 22 dbms_output.put_line(l_text_table.count || ' varchar2s '); 23 dbms_output.put_line('-');
- 最后,我们要向表中增加一些新的行,练习另一种从PL/SQL表变量中删除行的方法:
25 l_text_table(15):='some text'; 26 l_text_table(25):='some more text'; 27 dbms_output.put_line('After some assignments,we end up with '); 28 dbms_output.put_line(l_text_table.count || ' varchar2s '); 29 dbms_output.put_line('-'); 30
在表中加入了一些行之后,我们就可以将NULL的表变量L_EMPTY_TABLE赋予L_TEXT_TABLE变量。最后,这会产生与DELETE方法相同的效果;表变量被赋予了NULL表,因此它就不再具有表项。当我们将NULL赋予我们的L_TEXT_TABLE变量之后,我们要再查看数量。
31 l_text_table:=l_empty_table; 32 dbms_output.put_line('Once we assign our populated table to an empty '); 33 dbms_output.put_line('table,we end up with '||l_text_table.count); 34 dbms_output.put_line(' varchar2s '); 35 end; 36 / We start with 3 varchar2s -After using the DELETE operator on the second record(ie,DELETE(20),we have 2 varchar2s -After using the DELETE operator,we have 0 varchar2s -After some assignments,we end up with 2 varchar2s -Once we assign our populated table to an empty table,we end up with 0 varchar2s PL/SQL 过程已成功完成。
工作原理
在这个例子中,我们使用了2种方法来消除PL/SQL表的所有记录。第一种方法展示于第17行,它使用了DELETE操作符。使用DELETE可以删除PL/SQL表中的所有记录。第二种方法展示于第34行,它为PL/SQL表赋予了一个没有初始化的变量L_EMPTY_TABLE,它是我们在前面定义的相同类型的空变量。这将会产生相同的效果。
FIRST、NEXT和LAST
我们已经讨论了PL/SQL表变量上使用的COUNT集合方法,它可以返回表中的表项数量。在部分,我们将要讨论其它一些集合方法,以及我们在使用表变量时,怎样在PL/SQL中使用它们得到帮助。
- l FIRST 可以用于返回PL/SQL表的“第一个”,或者最小的索引。
- l LAST 与FIRST大体,只是它要返回“最后”或者最大的索引。
- l NEXT 可以用来找到PL/SQL表变量的下一个索引。
在以下代码救命中,我们将要使用CURSOR FOR LOOP,建立一个简单的PL/SQL表。循环负责执行查询,并且一次一个记录遍历结果集。
SQL> conn scott/tiger; 已连接。 SQL> set serveroutput on; SQL> declare 2 type my_text_table_type is table of varchar2(200) index by binary_integer; 3 l_text_table my_text_table_type; 4 l_index number; 5 begin 6 for emp_rec in (select * from emp) loop 7 l_text_table(emp_rec.empno):=emp_rec.ename; 8 end loop; 9 10 l_index:=l_text_table.first; 11 loop 12 exit when l_index is null; 13 dbms_output.put_line(l_index ||':'||l_text_table(l_index)); 14 l_index:=l_text_table.next(l_index); 15 end loop; 16 end; 17 / 7369:SMITH 7499:ALLEN 7521:WARD 7566:JONES 7654:MARTIN 7698:BLAKE 7782:CLARK 7839:KING 7844:TURNER 7900:JAMES 7902:FORD 7934:MILLER PL/SQL 过程已成功完成。
10.5.3 VARRAYS
VARRAYS是能够在用户的表列中存储的PL/SQL集合。当用户建立VARRAY的时候,用户必须为它提供最大的规模。当用户开始向VARRAY中加入元素之后,它就会消耗空间。这些集合是密集对象,这意味着没有办法删除其中包含的单独元素。
试验:建立和使用VARRAY
- 首先,我们要作为用户SCOTT进行连接,然后生成一个简单的对象类型,它将会作为我们在VARRAY中所存储值的数据类型。
SQL> conn scott/tiger; 已连接。 SQL> create or replace type employee_type as object( 2 employee_id number, 3 first_name varchar2(30), 4 last_name varchar2(30) 5 ); 6 / 类型已创建。
- 接下来,声明用于我们的变量的VARRAY类型。
SQL> create type employee_list_type as varray(50) of employee_type 2 / 类型已创建。
- 现在,我们将要建立一个存储集合的表。EMPLOYEES列将会存储EMPLOYEE_LIST_TYPE,它是VARRAY。
SQL> create table departments( 2 department_id number, 3 department_name varchar2(30), 4 manager employee_type, 5 employees employee_list_type 6 ); 表已创建。
- 既然所有的类型和结构都已就位,那么接下来我们就可以在表中增加一些行。要记住,当向DEPARTMENTS表增加一行的时候,我们能够增加0个、1个或者多个行到EMPLOYEES VARRAY中:
SQL> insert into departments(department_id,department_name,manager,employees) 2 values(10,'Accounting',employee_type(1,'Danielle','Steeger'), 3 employee_list_type( 4 employee_type(2,'Madison','Sis'), 5 employee_type(3,'Robert','Cabove'), 6 employee_type(4,'Michelle','Sechrist')) 7 ) 8 / 已创建 1 行。 SQL> insert into departments(department_id,department_name,manager,employees) 2 values(20,'Accounting',employee_type(11,'Ricky','Lil'), 3 employee_list_type( 4 employee_type(12,'Ricky','Ricardo'), 5 employee_type(13,'Lucy','Ricardo'), 6 employee_type(14,'Fred','Mertz'), 7 employee_type(15,'Ethel','Mertz')) 8 ) 9 / 已创建 1 行。
在这里,我们已经看到了怎样使用标准的INSERT SQL语句,以及内联VARRAY或者内联对象构造函数,向表中进行插入。内嵌的对象构造函数就是当用户建立对象的时候,所提供的可以用来构建对象实例的默认方法。方法的名称与对象的名称相同,方法的参数就是对象的属性。在以上的INSERT语句中,我们使用了构造方法的语法向MANAGER列中插入了EMPLOYEE_TYPE对象。
- 现在这个表由VARRAY生成,所以我们需要使用变化的查询类型来获取VARRAY列中的数据。这里是一个从DEPARTMENTS表中获取EMPLOYEES和DEPARTMENT_NAME列的简单查询:
SQL> column department_name format a13 SQL> column employees format a63 word_wrapped SQL> select department_name,employees from departments 2 / DEPARTMENT_NA EMPLOYEES(EMPLOYEE_ID, FIRST_NAME, LAST_NAME) ------------- ---------------------------------------------------------Accounting EMPLOYEE_LIST_TYPE(EMPLOYEE_TYPE(2, 'Madison', 'Sis'), EMPLOYEE_TYPE(3, 'Robert', 'Cabove'), EMPLOYEE_TYPE(4, 'Michelle', 'Sechrist')) Accounting EMPLOYEE_LIST_TYPE(EMPLOYEE_TYPE(12, 'Ricky', 'Ricardo'), EMPLOYEE_TYPE(13, 'Lucy', 'Ricardo'), EMPLOYEE_TYPE(14, 'Fred', 'Mertz'), EMPLOYEE_TYPE(15, 'Ethel', 'Mertz'))
可以看到,我们从表外将EMPLOYEES varray列作为一个单一实体查询。
10.5.4 NESTED TABLE
我们将要分析的最后一种类型的集合是NESTED TABLE。它与索引表非常相似。最重要的区别是嵌套表可以存储在数据库的列中,而索引表不行。使用嵌套表非常类似于在用户数据库的列中存储一个记录数组(Oracle 9i或以上)。
嵌套表与VARRAY大体相同,但是也有一些明显的例外:
- l NESTED TABLE是稀疏的,而VARRAY是密集的。用户能够删除嵌套表的单独记录,但是不能够删除VARRAY的单独记录。
- l NESTED TABLE没有上限(超出了数据库的常规上限),VARRAY具有固定的大小。
- l NESTED TABLE能够存储在系统生成表中,而VARRAY要存储在内嵌(4K或者更小),或者外延表空间中。
我们来讨论一个在数据库中使用NESTED TABLE的示例。我们要构造类似于OE模式的ORDERS表的ORDERS表。我们将要在ORDERS中使用NESTED TABLE表存储各个订单的产品项。然后,我们会使用OE模式的ORDERS表中的所有订单生成这个表。
试验:订单
(1) 首先,我们需要建立ORDER_ITEM_TYPE。它会存储我们ORDERS表的各个产品项。当建立了这个对象之后,我们就可以建立ORDER_ITEM_LIST_TYPE,它是ORDER_ITEM_TYPE的表:
注意:
用户必须作为能够看到OE模式对象的用户登录,但是不要作为OE用户自己登录。
SQL> conn scott/tiger; 已连接。 SQL> create type order_item_type as object( 2 line_item_id number(3), 3 product_id number(6), 4 unit_price number(8,2), 5 quanlity number(4) 6 ) 7 / 类型已创建。 SQL> create type order_item_list_type as table of order_item_type 2 / 类型已创建。
(2) 接下来,我们要建立自己的ORDERS表(conn oe/oe用户中也有ORDERS表,现在在scott账户中重建另一张名称一样结构不同的表):
SQL> create table orders( 2 order_id number(12) not null, 3 order_date timestamp(6) with local time zone, 4 customer_id number(6), 5 order_items order_item_list_type 6 )nested table order_items store as order_items_tab 7 / 表已创建。 SQL> conn oe/oe; 已连接。 SQL> grant all on orders to scott; 授权成功。
(3) 我们声明一个变量,然后使用CURSOR FOR LOOP选择OE.ORDERS表中的数据:
SQL> declare 2 l_lineitems orders.order_items%type; 3 begin 4 l_lineitems := order_item_list_type(); 5 for ord in (select * from oe.orders order by order_date) loop 6 l_lineitems.delete; 7 for items in (select * from oe.order_items where order_id=ord.order_id order by line_item_id) loop
接下来,我们要使用EXTEND集合方法。它会在集合中生成新的元素。当-L-LINEITEMS扩展之后,我们就可以为其赋予新的ORDER_ITEM_TYPE类型元素。
8 l_lineitems.extend; 9 l_lineitems(l_lineitems.count):=order_item_type(items.line_item_id,items.product_id,items.unt_price,items.quantity); 10 end loop;
现在,我们就拥有了向ORDERS表中增加一个记录所需的所有订单信息:
11 insert into orders(order_id,order_date,customer_id,order_items) 12 values(ord.order_id,ord.order_date,ord.customer_id,l_lineitems); 13 end loop; 14 end; 15 / PL/SQL 过程已成功完成。
集合是PL/SQL语言的核心组件。增加到Oracle8.0中的VARRAY和NESTED TABLE可以支持数据库中的对象打磨,它们还可以存储集合。当用户决定使用什么类型的集合时,首先要仔细考虑PL/SQL表。如果用户不是必须要在列中存储集合,那么使用它们来代替NESTED TABLE或者VARRAY就会很有优势。例如,PL/SQL表可以有负下标;而NESTED TABLE和VARRAY却不能。为了向NESTED TABLE或者VARRAY增加另一条记录,用户必须使用集合方法EXTEND。而这在索引表中没有必要。
10.6 游标
在Oracle中最学使用的2种游标类型是显式游标和隐性游标。显式游标(explicit cursor)是那些需要作为PL/SQL程序员的用户显式编写必要的PL/SQL全程来进行管理的游标。游标的整个生命周期都在用户的控件之下,因此,用户可以详细地控制PL/SQL怎样在结果集中访问记录。用户可以定义游标、打开游标,从游标中获取数据,使用合适的PL/SQL代码关闭游标。而另一方面,隐式游标(Implicit cursor)不用提供明确的代码来处理游标就可以在用户的PL/SQL中使用。用户在使用隐式游标的时候,仍然可以处理结果集中的记录,但是用户不必显式编写代码管理游标的声明周期。
10.6.1 显式游标
用户必须使用如下语法声明用户的游标:
CURSOR cursor_name [(parameter[,parameter]…)] [RETURN return_type] IS some select statement;
游标都具有自己的声明周期。当想要使用PL/SQL代码中的游标时,您需要完成以下工作:
- l 打开游标。这个过程要解析由游标标识的查询,绑定输入,并且进行输入,以便用户能够成功地获取数据。然而在下一步之前,不会实际从数据库中检索行。
- l 从游标中获取记录。这个步骤将会执行查询,基于游标的查询找到应该返回的那些行返回从数据库中检索数据行的游标。
- l 关闭游标。这个过程将会完成游标处理,使用户不能够再从游标中获取其它的行。当游标关闭之后,用户还可以重新打开游标进行进一步的处理,而且可以使用不同的参数值来生成新的结果集。
- 显式游标选项
SQL> set serveroutput on; SQL> declare 2 cursor emp_cur (p_deptid in number) 3 is select * from employees where department_id=p_deptid; 4 l_emp employees%rowtype; 5 begin 6 dbms_output.put_line('Getting employees from department 30'); 7 open emp_cur(30); 8 loop 9 fetch emp_cur into l_emp; 10 exit when emp_cur%notfound; 11 dbms_output.put('Employee id ' || l_emp.employee_id || ' is'); 12 dbms_output.put_line(l_emp.first_name || ' ' || l_emp.last_name); 13 end loop; 14 close emp_cur; 15 16 dbms_output.put_line('Getting employees from department 90'); 17 open emp_cur(30); 18 loop 19 fetch emp_cur into l_emp; 20 exit when emp_cur%notfound; 21 dbms_output.put('Employee id ' || l_emp.employee_id || ' is'); 22 dbms_output.put_line(l_emp.first_name || ' ' || l_emp.last_name); 23 end loop; 24 close emp_cur; 25 end; 26 / Getting employees from department 30 Employee id 114 isDen Raphaely Employee id 115 isAlexander Khoo Employee id 116 isShelli Baida Employee id 117 isSigal Tobias Employee id 118 isGuy Himuro Employee id 119 isKaren Colmenares Getting employees from department 90 Employee id 114 isDen Raphaely Employee id 115 isAlexander Khoo Employee id 116 isShelli Baida Employee id 117 isSigal Tobias Employee id 118 isGuy Himuro Employee id 119 isKaren Colmenares PL/SQL 过程已成功完成。
10.6.2 隐式游标
隐式游标是作为用户PL/SQL代码中的一些操作结果建立的游标,在那里您没有明确建立游标变量,但是Oracle可以提供在PL/SQL中使用的结果集。在这里,我们将会分析2种不同的隐式游标:一个是当用户在用户的PL/SQL中使用数据操作语言(DML)时,由Oracle预先定义的名为SQL的隐式游标,还有CURSOR FOR LOOP。我们会依次讨论它们。
执行UPDATE语句,它表明了SQL所代表的隐式游标的用处:
SQL> declare 2 begin 3 update departments set department_name=department_name where 1=2; 4 dbms_output.put('An update with a WHERE clause 1 = 2 effects '); 5 dbms_output.put_line(sql%rowcount || ' records.'); 6 7 update departments set department_name=department_name; 8 dbms_output.put('No WHERE clause in an update effects '); 9 dbms_output.put_line(sql%rowcount || ' records.'); 10 end; 11 / An update with a WHERE clause 1 = 2 effects 0 records. No WHERE clause in an update effects 27 records.
另一种形式的隐式游标称为CURSOR FOR LOOP。CURSOR FOR LOOP可以用于用户使用FOR LOOP语句,以及生成用户结果集的查询的情况。循环会为所返回的每条记录执行循环中的一组PL/SQL语句,来隐式处理结果集。在以下的代码示例中,我们将要看到实际的CURSOR FOR LOOP。
SQL> conn hr/hr; 已连接。 SQL> set serveroutput on; SQL> declare 2 begin 3 for my_dept_rec in(select department_id,department_name from departments order by 1) 4 loop 5 dbms_output.put('Department #'|| my_dept_rec.department_id); 6 dbms_output.put_line(' is named '|| my_dept_rec.department_name); 7 end loop; 8 end; 9 / Department #10 is named Administration Department #20 is named Marketing Department #30 is named Purchasing Department #40 is named Human Resources Department #50 is named Shipping PL/SQL 过程已成功完成。
10.6.3 游标属性
游标属性可以返回SQL语句执行的元数据。例如,我们可能想要知道结果集中有多少记录。游标是打开还是关闭,我们最后一次从结果集中获取行的时候是否找到了记录。为了访问这些元数据,用户要使用用户游标名称上的操作符告诉Oracle,应该为哪个SQL语句返回元数据。这里要讨论4个游标属性,见表10-5。
表10-5 游标属性
属性 |
定义 |
%FOUND |
指出了当PL/SQL代码最后从游标的结果集中获取记录的时候,找到了记录 |
%NOTFOUND |
%FOUND操作符的对立属性。%NOTFOUND指出在PL/SQL代码最后从游标中获取记录的时候,在结果集中没有记录 |
%ROWCOUNT |
返回当前时刻还没有从游标中获取的记录数量 |
%ISOPEN |
当游标已经打开,还没有关闭的时候,就会返回TRUE。这个操作符适用于用户代码要基于逻辑,有条件地打开和关闭游标的情况 |
以下的示例展示了一个从HR EMPLOYEES表中选取数据的显式游标。我们将要使用刚才定义的各个属性,根据所发生的情况修改游标的行为:
SQL> declare 2 cursor emps is select * from employees where rownum < 6 order by 1; 3 emp employees%rowtype; 4 row number:=1; 5 begin 6 open emps; 7 fetch emps into emp; 8 loop 9 if emps%FOUND then 10 dbms_output.put_line('Looping over record '|| row || ' of '|| emps%rowcount); 11 fetch emps into emp; 12 row:=row+1; 13 elsif emps%NOTFOUND then 14 exit; 15 end if; 16 end loop; 17 if emps%ISOPEN THEN 18 close emps; 19 end if; 20 end; 21 / Looping over record 1 of 1 Looping over record 2 of 2 Looping over record 3 of 3 Looping over record 4 of 4 Looping over record 5 of 5 PL/SQL 过程已成功完成。
试验:隐式和显式游标
我们将要再次使用HR EMPLOYEES表中的数据。这次我们要分析雇员的薪金。首先,我们要分析各个工作,找到各个工作的最低、最高和平均薪金。然后,我们要分析具有特定工作头衔的雇员,找到他们各自的收入:
SQL> set serveroutput on SQL> declare 2 cursor programmers is 3 select e.first_name || ' ' || e.last_name name, e.salary 4 from employees e,jobs j 5 where e.job_id=j.job_id and j.job_title='Programmer' 6 order by salary; 7 8 name varchar2(200); 9 salary number(9,2); 10 begin 11 for c1 in (select j.job_title,j.min_salary,j.max_salary,avg(e.salary) avg_salary 12 from employees e,jobs j 13 where e.job_id=j.job_id 14 group by j.job_title,j.min_salary,j.max_salary 15 order by j.job_title) loop 16 dbms_output.put_line(c1.job_title || 's,average $'||c1.avg_salary); 17 end loop; 18 19 open programmers; 20 fetch programmers into name,salary; 21 dbms_output.put_line(chr(13)||chr(13)); 22 dbms_output.put_line('PROGRAMMERS'); 23 dbms_output.put_line('-----------'); 24 while programmers%FOUND loop 25 dbms_output.put_line(name || ' makes $'||salary); 26 fetch programmers into name,salary; 27 end loop; 28 close programmers; 29 end; 30 / Accountants,average $7920 Accounting Managers,average $12000 Administration Assistants,average $4400 Administration Vice Presidents,average $17000 Finance Managers,average $12000 Human Resources Representatives,average $6500 Marketing Managers,average $13000 Marketing Representatives,average $6000 Presidents,average $24000 Programmers,average $5760 Public Accountants,average $8300 Public Relations Representatives,average $10000 Purchasing Clerks,average $2780 Purchasing Managers,average $11000 Sales Managers,average $12200 Sales Representatives,average $8350 Shipping Clerks,average $3215 Stock Clerks,average $2785 Stock Managers,average $7280 PROGRAMMERS -----------Diana Lorentz makes $4200 David Austin makes $4800 Valli Pataballa makes $4800 Bruce Ernst makes $6000 Alexander Hunold makes $9000 PL/SQL 过程已成功完成。
工作原理
这个特殊的示例表明了CURSOR FOR LOOPS的效率,说明了我们更倾向于使用隐性游标而不是显性游标。为了管理SQL集成,用户必须显式编写附加的游标控制代码,所以用户应该尝试最小化显式游标的用法。隐匿游标更容易使用;需要管理的代码更少(它会为用户控制游标声明、打开、获取和关闭),而且它们也更快!PL/SQL在运行的时候需要解释的代码也更少。
例如,显式游标代码量:
2 cursor programmers is 3 select e.first_name || ' ' || e.last_name name, e.salary 4 from employees e,jobs j 5 where e.job_id=j.job_id and j.job_title='Programmer' 6 order by salary; 19 open programmers; 20 fetch programmers into name,salary; 21 dbms_output.put_line(chr(13)||chr(13)); 22 dbms_output.put_line('PROGRAMMERS'); 23 dbms_output.put_line('-----------'); 24 while programmers%FOUND loop 25 dbms_output.put_line(name || ' makes $'||salary); 26 fetch programmers into name,salary; 27 end loop; 28 close programmers;
隐式游标代码量:
11 for c1 in (select j.job_title,j.min_salary,j.max_salary,avg(e.salary) avg_salary 12 from employees e,jobs j 13 where e.job_id=j.job_id 14 group by j.job_title,j.min_salary,j.max_salary 15 order by j.job_title) loop 16 dbms_output.put_line(c1.job_title || 's,average $'||c1.avg_salary); 17 end loop;
10.6.4 REF CURSORS和游标变量
显式游标和隐式游标都是静态定义的。当用户使用它们的时候,在建立游标的时候就要定义查询。然而,有很多时候用户需要为游标使用的查询直到运行的时候才能够定,在这样的情况下,用户就需要为查询打开一个游标,以处理正确的结果集。Oracle可以使用REF CURSOR和游标变量来满足这个要求。
有2种类型的REF CURSOR需要理解:
- l 强类型(限制)REF CURSOR
- l 弱类型(非限制)REF CURSOR
强类型REF CURSOR要求用户声明构成用户所使用查询结果集的返回类型。弱类型REF CURSOR没有定义返回类型,能够用于获取任何结果集。
为了声明REF CURSOR,可以使用如下语法:
TYPE ref_cursor_name IS REF CURSOR [RETURN return_type];
这里展示了如何声明我们已经讨论过的2种类型的REF CURSOR:
SQL> declare 2 type refcur_t is ref cursor; 3 type emp_refcur_t is ref cursor return employees%rowtype; 4 begin 5 null; 6 end; 7 / PL/SQL 过程已成功完成。
当拥有了经过定义的REF CURSOR之后,我们就必须声明游标变量。游标变量就是一些REF CURSOR类型的变量。
试验:强类型REF CURSOR
首先,我们要声明具有雇员信息和工作头衔的定制记录类型:
SQL> declare 2 type emp_job_rec is record( 3 employee_id number, 4 employee_name varchar2(56), 5 job_title varchar2(35) 6 ); 7 type emp_job_refcur_type is ref cursor return emp_job_rec; 8 emp_refcur emp_job_refcur_type; 9 emp_job emp_job_rec; 10 begin 11 open emp_refcur for 12 select e.employee_id,e.first_name || ' ' || e.last_name "employee_name",j.job_title 13 from employees e,jobs j 14 where e.job_id=j.job_id and rownum<11 order by 1; 15 16 fetch emp_refcur into emp_job; 17 while emp_refcur%FOUND loop 18 dbms_output.put(emp_job.employee_name || '"s job is '); 19 dbms_output.put_line(emp_job.job_title); 20 fetch emp_refcur into emp_job; 21 end loop; 22 end; 23 / Steven King"s job is President Neena Kochhar"s job is Administration Vice President Lex De Haan"s job is Administration Vice President Alexander Hunold"s job is Programmer Bruce Ernst"s job is Programmer David Austin"s job is Programmer Valli Pataballa"s job is Programmer Diana Lorentz"s job is Programmer Nancy Greenberg"s job is Finance Manager Daniel Faviet"s job is Accountant PL/SQL 过程已成功完成。
试验:弱类型REF CURSOR
对于这个例子,可以将如下代码放入名为refex.sql的文件,代码如下:
set echo off; set serveroutput on; set verify off; set define '&'; prompt prompt 'What table would you like to see?' accept tab prompt '(L)ocations,(D)epartments,or (E)mployees :' prompt declare type refcur_t is ref cursor; refcur refcur_t; type sample_rec_type is record( id number, description varchar2(200) ); sample sample_rec_type; selection varchar2(1):=upper(substr('&tab',1,1)); begin if selection ='L' then open refcur for select location_id,street_address || ' '|| city from locations where rownum<11 order by 1; dbms_output.put_line('Sample LOCATION data:'); elsif selection ='D' then open refcur for select department_id,department_name from departments where rownum<11 order by 1; dbms_output.put_line('Sample DEPARTMENT data:'); elsif selection ='E' then open refcur for select employee_id,first_name || ' '||last_name from employees where rownum<11 order by 1; dbms_output.put_line('Sample EMPLOYEE data:'); else dbms_output.put_line('Please enter "L","D",or "E".'); return; end if; dbms_output.put_line('--------------'); fetch refcur into sample; while refcur%FOUND loop dbms_output.put_line('#'||sample.id||' is '||sample.description); fetch refcur into sample; end loop; close refcur; end; /
当用户建立了文件之后,就可以从SQL*Plus执行它,并且输入字母L、D或者E来获取用户的结果,如下所示:
SQL> @d:\001\begin_oracle\refex 'What table would you like to see?' (L)ocations,(D)epartments,or (E)mployees :L Sample LOCATION data: --------------#1000 is 1297 Via Cola di Rie Roma #1100 is 93091 Calle della Testa Venice #1200 is 2017 Shinjuku-ku Tokyo #1300 is 9450 Kamiya-cho Hiroshima #1400 is 2014 Jabberwocky Rd Southlake #1500 is 2011 Interiors Blvd South San Francisco #1600 is 2007 Zagora St South Brunswick #1700 is 2004 Charade Rd Seattle #1800 is 147 Spadina Ave Toronto #1900 is 6092 Boxwood St Whitehorse PL/SQL 过程已成功完成。
10.6.5 单独SELECT
通过向PL/SQL代码中标准的SELECT语句增加单独的INTO子句,就可以将从表或者一些数据源中查询的单独行赋予变量(或者变量列表):
SQL> conn scott/tiger; 已连接。 SQL> set serveroutput on; SQL> declare 2 l_empno emp.empno%type; 3 l_ename emp.ename%type; 4 begin 5 select empno,ename into l_empno,l_ename from emp 6 where rownum=1; 7 dbms_output.put_line(l_empno||':'||l_ename); 8 end; 9 / 7369:SMITH PL/SQL 过程已成功完成。
10.7 控制语句
10.7.1 条件
条件(Conditions)是可以评估为布尔值true、false或者NULL的表达式。如果条件评估为true,那么就要处理条件的代码。
- IF … THEN … ELSE语句
示例:
IF expression1 THEN plsql_statement1; END IF;
IF expression1 THEN plsql_statement1; ELSE plsql_statement2; END IF;
IF expression1 THEN plsql_statement1; ELSIF expression2 THEN plsql_statement2; ELSIF expression3 THEN plsql_statement3; …… ELSE plsql_statement; END IF;
- CASE语句
有2种形式的PL/SQL CASE语句。第一种形式会获取一个值,然后将其与每个WHEN子句进行比较:
CASE value WHEN expression1 THEN plsql_statement1; [WHEN expression2 THEN plsql_statement2;] … [ELSE plsql_statement3;] END CASE;
第二种形式不会在CASE上获取值,而是要评估WHEN子句中的各个表达式,找到第一个评估为True的表达式:
CASE WHEN condition1 THEN plsql_statement1; [WHEN condition2 THEN plsql_statement2;] … [ELSE plsql_statement3;] END CASE;
10.7.2 循环
- 无条件循环
最基本的循环称为无限制循环(unconstrained loop)。无限制是指如果没有EXIT语句,循环将一直运行下去。用户向PL/SQL发出的停止执行循环语句的命令是EXIT。
SQL> declare 2 l_loops number:=0; 3 begin 4 dbms_output.put_line('Before my loop '); 5 loop 6 if l_loops>4 then 7 exit; 8 end if; 9 dbms_output.put_line('Looped '||l_loops||' times'); 10 l_loops:=l_loops+1; 11 end loop; 12 dbms_output.put_line('After my loop'); 13 end; 14 / Before my loop Looped 0 times Looped 1 times Looped 2 times Looped 3 times Looped 4 times After my loop PL/SQL 过程已成功完成。 SQL> declare 2 l_loops number:=0; 3 begin 4 dbms_output.put_line('Before my loop '); 5 loop 6 exit when l_loops>4; 7 dbms_output.put_line('Looped '||l_loops||' times'); 8 l_loops:=l_loops+1; 9 end loop; 10 dbms_output.put_line('After my loop'); 11 end; 12 / Before my loop Looped 0 times Looped 1 times Looped 2 times Looped 3 times Looped 4 times After my loop PL/SQL 过程已成功完成。
- FOR循环
在一些情况下,用户循环的次数可以由一些数值评估来确定。循环语句自己也要做一些轻微发动,如下所示:
SQL> set serveroutput on SQL> declare 2 l_names dbms_sql.varchar2_table; 3 begin 4 l_names(1):='Whitney'; 5 l_names(2):='Jordan'; 6 l_names(3):='Cameron'; 7 8 for idx in 1 .. l_names.count loop 9 dbms_output.put_line('Name ('||idx||') is '||l_names(idx)); 10 end loop; 11 end; 12 / Name (1) is Whitney Name (2) is Jordan Name (3) is Cameron PL/SQL 过程已成功完成。
- WHILE循环
用户可以在循环定义中规定的EXIT子句。例如,我们最初带有EXIT WHEN语句的无限制循环可以重写为以下形式:
SQL> declare 2 loops number:=0; 3 begin 4 dbms_output.put_line('Before my loop'); 5 while loops<5 loop 6 dbms_output.put_line('Looped '||loops||' times'); 7 loops:=loops+1; 8 end loop; 9 dbms_output.put_line('After my loop'); 10 end; 11 / Before my loop Looped 0 times Looped 1 times Looped 2 times Looped 3 times Looped 4 times After my loop PL/SQL 过程已成功完成。
10.7.3 控制语句概要
当用户需要的循环次数已经预先确定,或者不会在循环语句执行期间发生改变时,FOR循环要比使用条件IF…THEN…ELSE循环或者EXIT WHEN语句更有效率。
10.8 错误处理
10.8.1 异常部分
就如我们在本章开始提到的那样,您可以选择在开发的各个PL/SQL块中包含一个异常部分。这种类型的部分由关键词EXCEPTION开始,并且是PL/SQL程序块的最后部分。每个异常部分都能够进一步分解为单独的异常处理器(exception handlers)。
异常处理器非常类似于我们可以在IF…THEN…ELSE语句或者CASE语句中找到的评估。语法如下所示:
exception when <EXCEPTION_EXPRESSION> then <plsql_statements> [when<EXCEPTIOIN_EXPRESSION> then <plsql_statements> […]
这里的每个EXCEPTIOIN_EXPRESSION都可以是如下内容之一:
- l 预定义表达式(或者是由布尔逻辑运算符分隔的Oracle预定义异常组合)。
- l 用户定义异常。
- l PRAGMA EXCEPTION_INIT异常。
10.8.2 预定义异常
Oracle为用户提供了大量在用户的PL/SQL中使用的预定义异常,检查可能导致用户代码失败的一般条件。它们都定义的Oracle的核心PL/SQL库中,用户可以在用户的PL/SQL异常处理器中使用名称对其进行标识。
如果我们使用HR模式,试着使用已经存在的主键向DEPARTMENTS表中插入一个记录,插入就会失败:
SQL> declare 2 l_dept departments%rowtype; 3 begin 4 l_dept.department_id:=100; 5 l_dept.department_name:='Tech Dudes'; 6 insert into departments(department_id,department_name) 7 values(l_dept.department_id,l_dept.department_name); 8 end; 9 / declare * ERROR 位于第 1 行: ORA-00001: 违反唯一约束条件 (HR.DEPT_ID_PK) ORA-06512: 在line 6
如果我们知道了异常是什么,就可以很容易地在代码块中的异常处理器中俘获它,并且更加自如地处理它:
SQL> set serverout on SQL> declare 2 l_dept departments%rowtype; 3 begin 4 l_dept.department_id:=100; 5 l_dept.department_name:='Tech Dudes'; 6 insert into departments(department_id,department_name) 7 values(l_dept.department_id,l_dept.department_name); 8 exception 9 when DUP_VAL_ON_INDEX then 10 dbms_output.put_line('We encountered the DUP_VAL_ON_INDEX exception.'); 11 dbms_output.put_line('This is where we‘d write out own handler code.'); 12 end; 13 / We encountered the DUP_VAL_ON_INDEX exception. This is where we‘d write out own handler code. PL/SQL 过程已成功完成。
用户可以采用以上所做的相同方式使用它们:
- l CURSOR_ALREADY_OPEN 当用户试图在已经打开的游标上执行OPEN语句的时候,就会产生这个异常。用户可以使用%ISOPEN游标属性,检查游标是否已经打开,或者也可以在调用OPEN之前关闭游标。
- l DUP_VAL_N_INDEX 当表中插入记录的时候,如果一列上具有唯一性索引,当违法了唯一性索引的时候就会产生这个异常。
- l INVALID_NUMBER 在将字符串隐式转换为数值变量期间,字符串不能等于合法的数值。(换句话说,’1’=1、’2’=2,但是three不能表示3)。
- l NO_DATA_FOUND 就如用户所见,当用户使用没有返回记录的SELECT INTO语句填充变量的时候,就会出现NO_DATA_FOUND异常。另外,引用不存在的PL/SQL表的记录也会抛出NO_DATA_FOUND。
- l TOO_MANY_ROWS 使用返回多个记录的SELECT INTO语句将会抛出TOO_MANY_ROWS异常,因为没有地方存储所返回的记录。
10.8.3 用户定义异常
试验:用户麦片粥的温度如何
我们要编写一些使用所定义异常的PL/SQL,来告诉用户它们的麦片粥是否太热、太冷还是正好。
(1) 将以下代码放入文件中,并且将其保存为temps.sql:
set echo off set verify off set define '&' set serverout on prompt 'How hot is your bowl of porridge (numberically in degrees F)?:' accept temp default '100' declare porridge_too_hot exception; porridge_too_cold exception; begin case when '&temp'<90.00 then raise porridge_too_cold; when '&temp'>140.00 then raise porridge_too_hot; else null; end case; dbms_output.put_line('The porridge temperature is just right'); exception when VALUE_ERROR then dbms_output.put_line('Please enter a numeric temperature(like 100)'); when porridge_too_hot then dbms_output.put_line('The porridge is way too hot...'); when porridge_too_cold then dbms_output.put_line('The porridge is way too cold...'); end; /
(2) 现在,我们来利用不同的输入运行几次这个脚本,来观察其反应:
SQL> @d:\001\begin_oracle\temps 'How hot is your bowl of porridge (numberically in degrees F)?:' 120 The porridge temperature is just right PL/SQL 过程已成功完成。 SQL> @d:\001\begin_oracle\temps 'How hot is your bowl of porridge (numberically in degrees F)?:' 50 The porridge is way too cold... PL/SQL 过程已成功完成。 SQL> @d:\001\begin_oracle\temps 'How hot is your bowl of porridge (numberically in degrees F)?:' 999999 The porridge is way too hot... PL/SQL 过程已成功完成。 SQL> @d:\001\begin_oracle\temps 'How hot is your bowl of porridge (numberically in degrees F)?:' ONE HUNDRED AND TWENTY Please enter a numeric temperature(like 100) PL/SQL 过程已成功完成。
10.8.4 PRAGMA EXCEPTION_INIT
PRAGMA是通知编译器完成某些工作的方式。它们是从Ada语言延续而来。可以将PRAGMA看作是伪指令。PRAGMA EXCEPTION_INIT是PL/SQL开发者将用户定义异常与Oracle错误条件相关联的方式。例如,如果用户打算编写PL/SQL程序块,而它可能会在Oracle中遇到一些不会抛出预定义Oracle异常的错误,但是尽管如此它也是错误,用户将会得到一个不是很友好的指出用户问题的错误栈下面就是这种类型的错误示例:
SQL> conn hr/hr; 已连接。 SQL> set define '&' SQL> set verify off SQL> declare 2 l_update_text varchar2(100):= 3 'update &table_name set &updated_column_name='':a'' 4 where &key_column_name=:a'; 5 begin 6 execute immediate l_update_text using '&update_column_value',&key_column_value; 7 end; 8 / 输入 table_name 的值: employees 输入 updated_column_name 的值: first_name 输入 key_column_name 的值: employee_number 输入 update_column_value 的值: Sean 输入 key_column_value 的值: 1000 declare * ERROR 位于第 1 行: ORA-00904: "EMPLOYEE_NUMBER": 无效的标识符 ORA-06512: 在line 6
这个脚本将会使用SQL*Plus替代字符(set define ‘&’),提示用户输入要在SQL语句中使用的适当值。给定L_UPDATE_TEXT变量中的SQL语句,以及我们以上进行的输入,就可以生成如下SQL:
update employees set first_name=:a where employee_number=:b;
通过使用PRAGMA EXCEPTION_INIT,我们就可以完成这项工作。我们能够建立称为INVALID_COLUMN_NAME的用户定义异常,并且告诉编译器将其与我们以上得到的错误想关联。然后,我们就可以在代码中获取并且处理这个异常,如下所示:
SQL> set verify off SQL> set define '&' SQL> set serverout on SQL> declare 2 invalid_column_name exception; 3 pragma exception_init(invalid_column_name,-904); 4 l_update_text varchar2(100):= 5 'update &&table_name set &&updated_column_name='':a'' 6 where &&key_column_name=:a'; 7 begin 8 execute immediate l_update_text using '&update_column_value',&key_column_value; 9 exception 10 when INVALID_COLUMN_NAME then 11 dbms_output.put('ERROR!You entered an invalid column name'); 12 dbms_output.put('(&updated_column_name or &key_column_name).Please '); 13 dbms_output.put_line('check your table definition and try again'); 14 end; 15 / 输入 update_column_value 的值: Sean 输入 key_column_value 的值: 1000 ERROR!You entered an invalid column name(first_name or Sean).Please check your table definition and try again PL/SQL 过程已成功完成。
在我们以上的代码中可以看到,我们已经声明了一个异常(INVALID_COLUMN_NAME),来俘获在我们前面的救命中抛出的异常。我们使用PRAGMA EXCEPTION_INIT将其与我们所运行的第一个程序块中接收到的异常代码(-904)进行关联,并且在抛出它的时候,对其进行处理。
异常代码可以通过查看用户接收到的错误栈,并且寻找ORA-#####错误来找到,#####就是用户需要使用的数值代码。
Oracle的预定义异常,例如NO_DATA_FOUND、VALUE_ERROR或者DUP_VAL_ON_INDEX,都是程序包STANDARD中的PRAGMA EXCEPTION_INIT调用。所以事实上,Oracle会简单使用PRAGMA EXCEPTION_INIT设置PL/SQL开发者通常会遇到的一些最常见的错误。
10.8.5 异常传播
异常传播(Exception propagation)这种方式可以确保或者让PL/SQL处理错误,或者将错误条件通知运行应用的用户。对于嵌套PL/SQL程序块,不能在所嵌套PL/SQL程序块的异常块中处理,而从嵌套块中抛出的异常将会传播给父程序块进行处理。这种异常传播将会一直持续到适当的异常处理器获取了异常,或者可执行代码停止处理,将错误传递回运行PL/SQL的主环境为止。
试验:异常传播
(1) 将以下PL/SQL代码写入用户工作目录的文件中excprop.sql,并运行文件
begin begin begin begin begin declare fname employees.first_name%type; begin select first_name into fname from employees where 1=2; exception when NO_DATA_FOUND then dbms_output.put_line('block #6'); end; exception when NO_DATA_FOUND then dbms_output.put_line('block #5'); end; exception when NO_DATA_FOUND then dbms_output.put_line('block #4'); end; exception when NO_DATA_FOUND then dbms_output.put_line('block #3'); end; exception when NO_DATA_FOUND then dbms_output.put_line('block #2'); end; exception when NO_DATA_FOUND then dbms_output.put_line('block #1'); end; /
(2) 建立文件后,我们就可以在SQL*Plus运行它:
SQL> conn hr/hr; 已连接。 SQL> set serverout on SQL> set echo off SQL> @D:\001\begin_oracle\excpror block #6 PL/SQL 过程已成功完成。
- 用户定义异常的作用域和可视性
应用于用户定义异常的作用域和可视性规则与应用于变量和常量的规则相同。在父程序块中声明的异常可以在嵌套块中可见,但是反过来就不行。只要程序控制离开嵌套块,进入父程序块,在嵌套块中声明的异常就不能再提供可视性。如果没有处理的用户定义异常从它的定义程序块中传播出来,那么除非使用OTHERS处理所有异常,否则就没有办法获取异常。例如:
SQL> begin 2 declare 3 NESTED_EXCEPTION exception; 4 begin 5 raise NESTED_EXCEPTION; 6 end; 7 exception 8 when NESTED_EXCEPTION then 9 dbms_output.put_line('NESTED_EXCEPTION caught!'); 10 end; 11 / when NESTED_EXCEPTION then * ERROR 位于第 8 行: ORA-06550: 第 8 行, 第 7 列: PLS-00201: 必须说明标识符 'NESTED_EXCEPTION' ORA-06550: 第 0 行, 第 0 列: PL/SQL: Compilation unit analysis terminated
就如用户所见,父程序块的异常部分不能够看到嵌套程序块中的异常声明。这并不是暗示父程序块应该声明要在嵌套程序块的代码中使用的所有异常。更确切地讲,嵌套程序块应该负责处理在自己的可执行部分出现的所有异常。有3种方式来处理我们刚才遇到的问题:
- l 通过在父程序块中声明异常,这在一些条件下很适用,而且另外一些情况下不太合理。
- l 通过确保在定义异常的程序块中,任何用户定义异常都有相应的异常处理器来解决程序块中的异常错误条件。
- l 通过使用WHEN OTHERS THEN子句。这意味着如果没有在异常部分显式命名异常,WHEN OTHERS THEN子句就可以获取其余的异常。
SQL> set serverout on SQL> declare 2 PARENT_ERROR exception; 3 begin 4 declare 5 CHILD_ERROR exception; 6 begin 7 raise CHILD_ERROR; 8 exception 9 when CHILD_ERROR then 10 dbms_output.put_line('nested block exception handler'); 11 raise; 12 end; 13 exception 14 when PARENT_ERROR then 15 dbms_output.put_line('parent block exception handler'); 16 when OTHERS then 17 dbms_output.put_line('Caught the OTHERS exception'); 18 raise; 19 end; 20 / nested block exception handler Caught the OTHERS exception declare * ERROR 位于第 1 行: ORA-06510: PL/SQL: 无法处理的用户自定义异常事件 ORA-06512: 在line 18
- 声明中产生的异常
在嵌套程序块声明部分招聘的异常不会由程序块的异常部分获取。这些异常会被抛出,并且只能够由父程序块的异常处理器获取。抛出一个异常:
SQL> declare 2 l_number number default 'MY NUMBER'; 3 begin 4 null; 5 exception 6 when OTHERS then 7 dbms_output.put_line('Exception caught'); 8 raise; 9 end; 10 / declare * ERROR 位于第 1 行: ORA-06502: PL/SQL: 数字或值错误 : 字符到数值的转换错误 ORA-06512: 在line 2
有2种方式来处理这种情况。首先,如果用户需要在相同的程序块中获取异常,那么就要在可执行部分进行变量赋值。我并不是建议用户不要为变量使用DEFAULT值,而是如果用户确实需要在相同的程序块中获取异常时,用户就可以使用如下方式编写以上代码(可以注意到,PL/SQL程序块之后的“Exception caught”消息):
SQL> declare 2 l_number number; 3 begin 4 l_number:='MY NUMBER'; 5 exception 6 when OTHERS then 7 dbms_output.put_line('Exception caught'); 8 raise; 9 end; 10 / Exception caught declare * ERROR 位于第 1 行: ORA-06502: PL/SQL: 数字或值错误 : 字符到数值的转换错误 ORA-06512: 在line 8
就如我们所说的,在嵌套块中,我们可以在父程序块的异常处理器中处理这些类型的异常。由于嵌套程序块是父程序块的执行部分,所以将错误处理过程放入父程序块的异常处理器就很合理:
SQL> begin 2 declare 3 l_number number default 'MY NUMBER'; 4 begin 5 null; 6 exception 7 when OTHERS then 8 dbms_output.put_line('Exception caught in inner block'); 9 end; 10 exception 11 when OTHERS then 12 dbms_output.put_line('Exception caught in outer block'); 13 raise; 14 end; 15 / Exception caught in outer block begin * ERROR 位于第 1 行: ORA-06502: PL/SQL: 数字或值错误 : 字符到数值的转换错误 ORA-06512: 在line 13
- SQLCODE和SQLERRM
在异常处理器有2个新“变量”可以使用。它们是SQLCODE和SQLERRM。
- l SQLCODE是标识接收到错误的错误代码。
- l SQLERRM是用来标识错误内容的纯文本消息。
所以,使用SQLCODE和SQLERRM可以提供“自描述”错误机制。例如:
SQL> set serverout on SQL> declare 2 l_emp emp%rowtype; 3 begin 4 select * into l_emp from emp; 5 dbms_output.put_line('EMPNO:'|| l_emp.empno); 6 dbms_output.put_line('ENAME:'|| l_emp.ename); 7 exception 8 when others then 9 dbms_output.put('Exception encountered!('); 10 dbms_output.put_line(sqlcode||'):'||sqlerrm); 11 raise; 12 end; 13 / Exception encountered!(-1422):ORA-01422: 实际返回的行数超出请求的行数 declare * ERROR 位于第 1 行: ORA-01422: 实际返回的行数超出请求的行数 ORA-06512: 在line 11
10.9 小结
在本章中,我们以介绍性的概念分析了在Oracle中进行PL/SQL编程。讨论了基于程序块的编程、以及PL/SQL怎样从逻辑上将代码块划分为职责区域,例如变量和常量声明、执行代码、以及错误控制代码。我们还讨论了PL/SQL中数据类型与SQL中数据类型之间的区别。
游标可以让我们将SQL查询引入到PL/SQL代码中,有效地集成程序的结构与数据库中存储的数据。有大量内建的功能可以帮助操作我们的数据、字符、和数值变量。另外,我们还可以将PL/SQL编程结构划分为条件逻辑、循环语句、以及错误控制代码等。
文章根据自己理解浓缩,仅供参考。
摘自:《Oracle编程入门经典》 清华大学出版社 http://www.tup.com.cn