一、PL/SQL支持的SQL一览
PL/SQL扩展了SQL,变得更加强大和易用。我们可以用PL/SQL灵活安全地操作Oracle数据,因为它支持所有的SQL数据操作语句(除 了EXPLAIN PLAN),事务控制语句,函数,伪列和操作符。PL/SQL还支持动态SQL,能让我们动态地执行SQL数据定义,数据控制和会话控制语句。除此之 外,PL/SQL还遵循ANSI/ISO的SQL标准。
1、数据操作
我们可以使用INSERT、UPDATE、DELETE、SELECT和LOCK TABLE来操作Oracle数据。INSERT用于向数据表中插入新行;UPDATE能修改行;DELETE可以删除不需要的行;SELECT可以按照 我们给定的查询标准检索出满足条件的行;LOCK TABLE可以临时限制对一个数据表的访问。
2、事务控制
Oracle是面向事务的,它使用事务来保证数据的完整性。数据库事务是指作为单个逻辑工作单元执行的一系列SQL操作。
同时,Oracle还可以通过事务将我们的操作持久化或取消。如果我们的程序在事务中有一步执行失败,Oracle就会发现错误并回滚整个事务。这样,数据库就会自动恢复到先前的状态。
我们可以使用COMMIT、ROLLBACK、SAVEPOINT和SET TRANSACTION命令来控制事务。COMMIT会提交事务,将数据库的变化永久性地提交;ROLLBACK会终止当前事务,并放弃自事务开启后所有 发生变化的内容;SAVEPOINT能够标记当前事务的处理进度;一并使用ROLLBACK和SAVEPOINT就可以回滚部分事务。SET TRANSACTION可以设置事务的属性,如读写访问和隔离级别。
3、SQL函数
PL/SQL能让我们使用所有的SQL聚合函数:AVG、COUNT、GROUPING、MAX、MIN、STDDEV、SUM和VARIANCE。除了COUNT(*)之外,所有的聚合函数都会忽略空值。
我们可以在SQL语句中使用聚合函数,但不能在过程化语句中使用。聚合函数会作用于整个字段,除非我们使用SELECT GROUP BY语句对返回的结果行进行分组排序。如果我们忽略GROUP BY子句,聚合函数就会把所有的结果行当作一个整体来处理。我们可以使用下面的语法来调用聚合函数:
function_name([ALL |DISTINCT ]expression)
expression会引用一个或多个数据库字段。如果我们指定ALL(默认的),聚合函数就会考虑所有的行,也包括重复的行内容。如果我们指定的是DISTINCT,聚合函数只会考虑有区别的值。例如,下面的语句只返回数据表emp中不同的职别的个数:
SELECT COUNT(DISTINCT job)INTO job_countFROM emp;
函数COUNT可以让我们使用星号(*)操作符返回一个数据表中行的个数。例如,下面的语句返回表emp中行的个数:
SELECT COUNT(*)INTO emp_countFROM emp;
如果使用默认的ALL,聚合函数会考虑所有的列值,包括重复项;如果改换DISTINCT的话,聚合函数就只会考虑重复项中的一个值。
4、SQL伪列
PL/SQL可以辨识CURRVAL、LEVEL、NEXTVAL、ROWID和ROWNUM这样能返回特殊的数据项的SQL伪列。伪列并不是真实 存在于数据表中的字段,但它们的行为看起来跟真实字段一样。例如,我们在查询时就可以选取伪列。但是,我们不允许在SQL语句中向伪列中插入值。伪列只能 在SQL语句中使用,不能用在过程化语句中。
序列(sequence)是一个能产生顺序编号的模式对象。创建序列时,我们可以指定它的初始值和增量值。CURRVAL能返回指定序列的当前值。
在使用序列前,我们必须先调用NEXTVAL,NEXTVAL的作用是生成并返回序列的下一个顺序值。想要得到序列中的当前值或是下一个值,我们必须使用点标志,方法如下:
sequence_name.CURRVAL
sequence_name.NEXTVAL
创建序列后,我们就可以在事务处理中用它产生唯一的顺序编号了。但是,我们只能在用SELECT列表、VALUES子句和SET子句中使用CURRVAL和NEXTVAL。在下面的例子中,我们使用序列向两个数据表中插入相同的雇员编号:
INSERT INTO empVALUES (empno_seq.NEXTVAL ,my_ename,...);
INSERT INTO salsVALUES (empno_seq.CURRVAL ,my_sal,...);
在事务中调用NEXTVAL时,序列的值会立即发生改变,不管我们是否使用提交或回滚语句。
我们可以用LEVEL配合SELECT CONNECT BY语句把数据表中的行组织成一个树形结构。LEVEL能返回树形结构中节点的级数。根节点的级数是1,根节点的子节点级数是2,孙子节点级数是3,依次类推。
在START WITH子句中,我们指定辨识根节点的条件。我们可以使用PRIOR操作符指定树的遍历方向(从根向下或从枝干向上)。
ROWID能够返回数据表中行的ROWID(二进制地址)。我们可以使用UROWID类型变量来存放具有可读性的ROWID,下例中,我们声明了一个名为row_id的变量:
DECLARE
row_idUROWID;
当我们把物理rowid放到UROWID变量中时,就要使用函数ROWIDTOCHAR,它能把二进制值转成一个长度为18字节的字符串。然后我们 就可以在UPDATE或DELETE语句的WHERE子句中,把UROWID变量的值同ROWID伪列值进行比较并找出从游标中取得的最新行。
ROWNUM能够返回从数据表中选出的行的标识。被选取的第一行的ROWNUM是1,第二行是2,依次类推。如果SELECT语句包含一个ORDER BY子句,ROWNUM会在排序操作之前被指定。
我们可以在UPDATE语句中用ROWNUM为每一行数据赋上一个唯一的值。同样,也可以在SELECT语句的WHERE子句中用ROWNUM限定选取的行的个数,如下例:
DECLARE
CURSOR c1IS
SELECT empno,sal
FROM emp
WHERE sal>2000AND ROWNUM <=10;--returns10rows
ROWNUM的值只是在行被检索出来的时候才会增长,所以,只有在WHERE子句中的ROWNUM才有意义:
...WHERE ROWNUM <constant;
...WHERE ROWNUM <=constant;
5、SQL操作符
PL/SQL能让我们在SQL语句中使用所有的SQL比较操作符、集合操作符和行操作符。这里简要描述一下其中几个操作符的功能。
我们常常需要在一个数据操作语句的WHERE子句中使用比较操作符来判断一个表达式的值是TRUE、FALSE还是NULL。下面的表格描述了各个操作符的用途:
ALL | 把同一个列表中的一个值与其他值或是子查询返回的值进行比较,如果所有的比较结果都为TRUE,那么结果值就为TRUE。 |
ANY, SOME | 把同一个列表中的一个值与其它值或是子查询返回的值进行比较,只要其中有一个结果值为TRUE,那么结果值就为TRUE。 |
BETWEEN | 测试一个值是否在指定的区间范围内。 |
EXISTS | 如果子查询至少返回一行数据,那么EXISTS的结果就为TRUE。 |
IN | 测试指定的值是否是集合成员。 |
IS | 测试指定的值是否是NULL。 |
LIKE | 测试一个字符串是否满足一个指定的样式,其中包含通配符。 |
集合操作符的作用是把两个查询的结果合并成一个结果。INTERSECT返回两个结果的交集;MINUS把目标结果集与另外一个结果集比较,并把存 在于另一个结果集中的记录从目标结果集中去除,然后返回目标结果集;UNION把两个结果集的内容合并,在有重复项的情况下,只保留其中一项;UNION ALL同UNION的作用类似,但它会保留所有记录,不管是否有相同的记录。
行操作符能返回或引用特定的行。ALL会保留查询或聚合表达式中的重复行;DISTINCT的作用与ALL相反,用于消除重复行;PRIOR能引用树形查询中返回的当前行的父级行。
二、管理游标
PL/SQL有两种类型的游标:隐式游标和显式游标。PL/SQL为所有的SQL数据操作语句都声明了一个隐式的游标,其中也包括只返回一条记录的 查询。但是,对于返回多于一条记录的查询来说,我们必须声明显式的游标,然后使用游标FOR循环或使用BULK COLLECT子句。
1、显式游标一览
查询返回的行集合可能是由零行、一行或多行组成,这个结果取决于我们的检索条件。当一个查询返回多行记录时,我们可以显式地声明一个游标来处理每一行数据。游标的声明可以放到PL/SQL块、子程序或包的声明部分。
我们可以使用三种命令来控制游标:OPEN、FETCH和CLOSE。首先用OPEN语句初始化一个游标,然后重复执行FETCH语句取出已检索到 的数据,或是使用BULK COLLECT批量选取数据。当处理完结果集中最后一行数据时,就可以用CLOSE语句关闭游标。我们还可以同时打开多个游标并发处理多个查询操作。
在PL/SQL中,向前引用是不允许的。所以我们必须在其它语句引用游标之前声明它。声明游标时,我们需要为它命名,并用下面的语法把它和一个查询相关联:
CURSOR cursor_name[(parameter[,parameter]...)]
[RETURN return_type]IS select_statement;
return_type必须是记录或是数据表的行类型,parameter的含义如下:
cursor_parameter_name[IN ]datatype[{:=|DEFAULT }expression]
例如,我们可以像下面这样声明游标c1和c2:
DECLARE
CURSOR c1IS
SELECT empno,ename,job,sal
FROM emp
WHERE sal>2000;
CURSOR c2RETURN dept%ROWTYPE IS
SELECT *
FROM dept
WHERE deptno=10;
游标名是一个未声明的标识符,而不是PL/SQL变量名。我们不能把值赋给游标名或是在一个表达式中使用它。但是,游标和变量有着同样的作用域规则。虽然在数据表为游标命名是允许的,但并不推荐这样做。
游标是可以接收参数的,这些参数可以在关联查询中允许出现常量的地方使用。游标的形式参数都必须是IN模式的。因此,它们不能把值赋给实际参数。并且,我们不能给游标的参数添加NOT NULL约束。
如下例所示,我们为游标参数初始化一个默认值。这样,我们就能给游标传递不同个数的实参。并且,我们还可以在不改变游标引用的情况下添加新的参数。
DECLARE
CURSOR c1(lowINTEGER DEFAULT 0,highINTEGER DEFAULT 99)IS
SELECT *
FROM ...
游标参数作用域对于游标来说是本地的,这也就意味着它们只能在游标声明时所指定的查询语句中使用。游标参数的值只在游标被打开的时候被使用。
游标只能在打开之后才能执行查询操作。对于使用了FOR UPDATE子句的游标来说,OPEN语句会把满足查询条件的行琐住,如下例所示:
DECLARE
CURSOR c1IS
SELECT ename,job
FROM emp
WHERE sal<3000;
...
BEGIN
OPEN c1;
...
END ;
OPEN语句执行时结果集中的行并不被选取,只有在FETCH语句执行的时候数据才被取得。
我们可以用OPEN语句来为游标传递参数。如果在没有使用参数默认值的情况下,我们就必须在OPEN语句中为游标声明中的每一个形式参数指定一个对应的实际参数。比如下面的游标声明:
DECLARE
emp_nameemp.ename%TYPE ;
salaryemp.sal%TYPE ;
CURSOR c1(NAMEVARCHAR2 ,salaryNUMBER )IS
SELECT *
FROM ...
下面三个语句都能打开游标:
OPEN c1(emp_name,3000);
OPEN c1('ATTLEY' ,1500);
OPEN c1(emp_name,salary);
在上面的例子中,标识符salary用在游标声明的时候,它引用的是形式参数。但是,当用在OPEN语句中时,它引用的就是PL/SQL变量,为了避免混乱,最好使用唯一的标识。
有默认值的形参不需要有对应的实参,在OPEN语句执行时程序会自动使用它们的默认值。
我们可以用位置标识法或名称标识法把OPEN语句中的实参和游标声明中的形参关联起来。每个实参的数据类型和它对应的形参数据类型必须兼容。
除非在FETCH时使用BULK COLLECT子句,否则FETCH语句每次只会从结果集中取出一条记录,并把游标向下移动,指向当前记录的下一条记录。下面演示了FETCH语句的用法:
FETCH c1INTO my_empno,my_ename,my_deptno;
对于游标查询返回的每一个字段值,在INTO列表中都必须有一个与之对应且类型兼容的变量。通常,我们可以像下面这样使用FETCH语句:
LOOP
FETCH c1INTO my_record;
EXIT WHEN c1%NOTFOUND;
--processdatarecord
END LOOP ;
查询可以在它的作用域范围内引用PL/SQL变量,但是,在查询中的任何变量只有在游标被打开时才计算它的值。在下面的例子中,每个被检索出来的salary只和2相乘,即使factor值在后续的操作中已经发生了改变:
DECLARE
my_salemp.sal%TYPE ;
my_jobemp.job%TYPE ;
factorINTEGER :=2;
CURSOR c1IS
SELECT factor*sal
FROM emp
WHERE job=my_job;
BEGIN
...
OPEN c1;--herefactorequals2
LOOP
FETCH c1
INTO my_sal;
EXIT WHEN c1%NOTFOUND;
factor:=factor+1;--doesnotaffectFETCH
END LOOP ;
END ;
要想改变查询结果集或查询中的变量值,我们就必须关闭并重新打开含有输入变量的游标,这样才能使用新的输入变量值。
但是,我们可以为同一游标每次取得的数据指定一个不同的INTO列表。这样每次取得的数据就会被放到目标变量中去,如下例所示:
DECLARE
CURSOR c1IS
SELECT ename
FROM emp;
name1emp.ename%TYPE ;
name2emp.ename%TYPE ;
name3emp.ename%TYPE ;
BEGIN
OPEN c1;
FETCH c1
INTO name1;--thisfetchesfirstrow
FETCH c1
INTO name2;--thisfetchessecondrow
FETCH c1
INTO name3;--thisfetchesthirdrow
...
CLOSE c1;
END ;
如果游标的指向超过了结果集中的最后一行,那么目标变量的值就无法确定。
注意:在结果集的最后一次执行的FETCH语句一定取不到数据,而且没有异常发生。想要发现这种情况,我们就必须得使用游标的%FOUND或%NOTFOUND属性。
BULK COLLECT子句能让我们批量的绑定数据。这样我们就能一次性从结果集中取得所有的行。在下面的例子中,我们从游标中批量取得数据分别放到两个集合中去:
DECLARE
TYPE numtabIS TABLE OF emp.empno%TYPE ;
TYPE nametabIS TABLE OF emp.ename%TYPE ;
numsnumtab;
namesnametab;
CURSOR c1IS
SELECT empno,ename
FROM emp
WHERE job='CLERK' ;
BEGIN
OPEN c1;
FETCH c1
BULK COLLECT INTO nums,names;
...
CLOSE c1;
END ;
CLOSE语句可以关闭游标,游标被关闭后,我们还可以重新打开它。操作一个已经关闭了的游标会抛出预定义异常INVALID_CURSOR。
子查询就是出现在另外一个SQL数据操作语句中的查询(通常用圆括号封闭)。计算时,子查询能为语句提供一个值或是一个值集合。通常,子查询用在WHERE子句中。例如,下面的查询就用于返回不在芝加哥居住的雇员:
DECLARE
CURSOR c1IS
SELECT empno,ename
FROM emp
WHERE deptnoIN (SELECT deptno
FROM dept
WHERE loc<>'CHICAGO' );
再举一个在FROM子句中使用子查询的例子,下面语句能够返回含有五个或五个以上雇员的部门编号和部门名称:
DECLARE
CURSOR c1IS
SELECT t1.deptno,dname,"STAFF"
FROM deptt1,(SELECT deptno,COUNT(*)"STAFF"
FROM emp
GROUP BY deptno)t2
WHERE t1.deptno=t2.deptnoAND "STAFF">=5;
针对一个数据表的子查询只执行一次,而具有关联关系的子查询会为每一行结果执行一次。如下面的查询,它的作用是找出工资超过所在部门的平均工资的雇员姓名和工资。对于emp表中的每一行,关联子查询都会为它计算一次部门的平均工资。
DECLARE
CURSOR c1IS
SELECT deptno,ename,sal
FROM empt
WHERE sal>(SELECT AVG (sal)
FROM emp
WHERE t.deptno=deptno)
ORDER BY deptno;
2、隐式游标一览
Oracle会隐式地打开一个游标处理所有那些不与显式游标相关联的SQL语句。我们可以引用这个隐式的SQL游标,虽然不能使用OPEN、FETCH和CLOSE语句来控制SQL游标,但可以利用它的属性来获取与最近执行的SQL语句的相关信息。
三、用包将游标的声明和游标体分离
我们可以将游标说明从游标体中分离出来并放到包中。这样做的话就可以在不改变游标说明的条件下修改游标体。我们可以在包说明中用下面语法编写游标说明:
CURSOR cursor_name[(parameter[,parameter]...)]RETURN return_type;
在下面的例子中,我们可以使用%ROWTYPE属性来代表数据表emp中的行类型:
CREATE PACKAGE emp_stuffAS
CURSOR c1RETURN emp%ROWTYPE ;--declarecursorspec
...
END emp_stuff;
CREATE PACKAGE BODY emp_stuffAS
CURSOR c1RETURN emp%ROWTYPE IS
SELECT *
FROM emp
WHERE sal>2500;--definecursorbody
...
END emp_stuff;
游标说明部分并不含有SELECT语句,因为后面RETURN子句中指明了返回值的数据类型。并且,游标体中含有的SELECT语句列表中的每一项,必须和说明部分的RETURN子句相匹配。
打包游标有着更好的灵活性。例如,我们可以任意修改上例中声明的游标的游标体而不用修改游标说明:
CREATE PACKAGE BODY emp_stuffAS
CURSOR c1RETURN emp%ROWTYPE IS
SELECT *
FROM emp
WHERE deptno=20;--newWHEREclause
...
END emp_stuff;
我们可以使用点标志从一个PL/SQL块或子程序中引用一个打包游标,如下例所示:
DECLARE
emp_recemp%ROWTYPE ;
...
BEGIN
...
OPEN emp_stuff.c1;
LOOP
FETCH emp_stuff.c1
INTO emp_rec;
EXIT WHEN emp_suff.c1%NOTFOUND;
...
END LOOP ;
CLOSE emp_stuff.c1;
END ;
打包游标的作用域并不局限于某个特定的PL/SQL块。所以,打开一个打包游标后,它会一直保持打开状态直到我们关闭它或是退出Oracle会话。
四、使用游标FOR循环
在大多数需要使用显式游标的情况下,我们都可以用一个游标FOR循环来代替OPEN、FETCH和CLOSE语句。游标FOR循环隐式地声明了一 个%ROWTYPE类型的记录作为它的循环索引,打开游标,然后反复执行把结果集中的行放到索引中去,最后在所有行都被处理完成后关闭游标。
思考下面PL/SQL块例子,它能从一个实验中计算出结果,然后把结果保存在一张临时表中。FOR循环的索引c1_rec是被隐式声明的记录。它的每一个域都保存来自游标c1中取出的值。对独立的域的引用可以使用点标志。
DECLARE
RESULTtemp.col1%TYPE ;
CURSOR c1IS
SELECT n1,n2,n3
FROM data_table
WHERE exper_num=1;
BEGIN
FOR c1_recIN c1LOOP
/*calculateandstoretheresults*/
RESULT:=c1_rec.n2/(c1_rec.n1+c1_rec.n3);
INSERT INTO temp
VALUES (RESULT,NULL ,NULL );
END LOOP ;
COMMIT ;
END ;
当进入游标FOR循环时后,游标的名称就不属于用OPEN语句打开的游标,也不属于封闭游标FOR循环。在每个循环之前,PL/SQL会把数据放到隐式声明的记录中去。记录的有效作用范围只在循环内,所以我们不能在循环的外部引用它。
循环内的语句序列会为每一个满足条件的结果行执行一次,当游标离开循环时,游标会被自动地关闭,这包括正常地使用EXIT或GOTO语句来结束循环,或是因异常抛出而退出循环的情况。
1、使用子查询代替显式游标
有时候我们并不需要声明游标,因为PL/SQL允许我们使用子查询来进行替代。下面的游标FOR循环先计算奖金值,然后把结果插入数据表中:
DECLARE
bonusREAL ;
BEGIN
FOR emp_recIN (SELECT empno,sal,comm
FROM emp)LOOP
bonus:=(emp_rec.sal*0.05)+(emp_rec.comm*0.25);
INSERT INTO bonuses
VALUES (emp_rec.empno,bonus);
END LOOP ;
COMMIT ;
END ;
2、使用游标子查询
我们可以使用游标子查询(又称游标表达式)把一个查询结果集作为参数传递给函数。如下例:
SELECT *FROM TABLE (StockPivot(CURSOR (SELECT *FROM StockTable)));
游标子查询通常可以用在表函数中,这将在第八章详细讨论。
3、在游标FOR循环中为表达式定义别名
隐式声明的记录中每个域存放着最近取得的数据。记录的域名称和SELECT列表中的字段相对应。但是,如果SELECT中含有表达式时会发生什么呢?看一下下面的例子:
CURSOR c1IS
SELECT empno,sal+NVL(comm,0)wages,job
FROM ...
这样的情况下,我们就必须为表达式起一个别名。如下例,wages就是表达式sal+NVL(comm,0)的一个别名:
CURSOR c1IS
SELECT empno,sal+NVL(comm,0)wages,job
FROM ...
如果要引用对应的域,就得使用别名进行代替,如下例所示:
IF emp_rec.wages<1000THEN ...
4、为游标FOR循环传递参数
我们可以在游标FOR循环中把参数传递给游标。下例中,我们传递一个部门编号。然后计算出该部门应付给它的雇员的工资数额。并且,我们可以判断出有多少雇员的工资超过2000和/或他们的佣金大于他们的工资。
DECLARE
CURSOR emp_cursor(dnumNUMBER )IS
SELECT sal,comm
FROM emp
WHERE deptno=dnum;
total_wagesNUMBER (11,2):=0;
high_paidNUMBER (4):=0;
higher_commNUMBER (4):=0;
BEGIN
/*Thenumberofiterationswillequalthenumberofrows
returnedbyemp_cursor.*/
FOR emp_recordIN emp_cursor(20)LOOP
emp_record.comm:=NVL(emp_record.comm,0);
total_wages:=total_wages+emp_record.sal+emp_record.comm;
IF emp_record.sal>2000.00THEN
high_paid:=high_paid+1;
END IF ;
IF emp_record.comm>emp_record.salTHEN
higher_comm:=higher_comm+1;
END IF ;
END LOOP ;
INSERT INTO temp
VALUES (high_paid,higher_comm,
'TotalWages:' ||TO_CHAR(total_wages));
COMMIT ;
END ;
五、使用游标变量
跟游标一样,游标变量也是指向多行查询的结果集中的当前行。但是,游标与游标变量不同之处就和常量与变量不同之处类似。游标是静态的,而游标变量是动态的,因为游标变量并不与某个特定的查询相绑定。所以,游标变量可以打开任何类型兼容的查询,灵活性很大。
并且,我们还可以为游标变量赋新值,把它作为参数传递给本地和存储子程序。这就很容易地让我们把数据检索集中化处理。
游标变量可以在每个PL/SQL客户端使用。例如,我们可以在OCI或Pro*C这样的主环境中声明游标变量,然后把它作为输入主变量(绑定变量) 传给PL/SQL。并且,像Oracle Forms和Oracle Reports这样的含有PL/SQL引擎的开发工具,完全可以在客户端使用游标变量。Oracle服务器也有一个PL/SQL引擎。所以,我们可以在应 用程序和服务器之间通过远程调用(RPC)来回传递游标变量。
1、什么是游标变量
游标变量同C或Pascal语言中的指针类似,它指向一块内存地址,而不是地址中的内容本身。所以,声明一个游标变量可以创建一个指针,而不是具体 的内容。在PL/SQL中,指针是一个REF X类型,REF是REFERENCE的缩写,而X代表对象的类型。因此,游标变量的数据类型是REF CURSOR。
为了执行多行查询,Oracle会开启一个未命名的工作区来存放处理信息。我们可以用显式游标为工作区命名然后访问相关的信息;或者声明指向工作区 的一个游标变量。无论在什么地方使用游标,它总是指向同一个查询工作区,而游标变量则可以指向不同的工作区。所以,游标和游标变量不能交互使用;也就是 说,我们不能在该使用游标的地方使用游标变量,不能在该使用游标变量的地方使用游标。
2、为什么使用游标变量
我们主要是在PL/SQL存储子程序和各种客户端之间用游标变量来传递查询结果。PL/SQL和其他客户端程序都不拥有结果集,它们只是共享一个指 向存放结果集工作区的指针而已。例如,一个OCI客户端,一个Oracle Forms应用程序和Oracle服务器可以引用同一个工作区。
只要有游标变量指向查询工作区,我们就可以引用它。因此,我们可以把游标变量的值自由地从一个作用域传递到另一个。例如,我们把主游标变量传递到嵌套在Pro*C程序中的PL/SQL块,游标变量指向的工作区就可以被访问。
如果客户端含有PL/SQL引擎,那么从客户端调用服务器端就不会有什么约束。假如我们在客户端声明游标变量,在服务器端打开并取得数据,然后把取得的结果返回给客户端。这些操作都是在服务器端完成,从而也减少了网络流量。
3、定义REF CURSOR类型
创建游标变量分为两个步骤。首先,我们得定义REF CURSOR类型,然后声明该类型的游标变量。我们可以在任何PL/SQL块、子程序或包内使用下面语法来定义REF CURSOR类型:
TYPE ref_type_nameIS REF CURSOR [RETURN return_type];
ref_type_name是类型区分符,return_type必须是记录类型或是代表数据表中的行类型。在下面的例子中,我们把返回的数据类型指定为数据表dept的行类型:
DECLARE
TYPE DeptCurTypIS REF CURSOR RETURN dept%ROWTYPE ;
REF CURSOR类型可以是强类型也可以是弱类型。如下例所示,强类型REF CURSOR需要指定返回类型,而弱类型没有这个要求:
DECLARE
TYPE EmpCurTypIS REF CURSOR RETURN emp%ROWTYPE ;--strong
TYPE GenericCurTypIS REF CURSOR ;--weak
强类型REF CURSOR在编译时会检查类型兼容,这样能更好的避免发生错误。但是,弱类型REF CURSOR具有更大的灵活性,因为它允许我们把游标变量与任何相兼容的查询关联起来。
4、声明游标变量
定义了REF CURSOR后,就可以在PL/SQL块或子程序中声明该类型的游标变量了。在下面的程序中,我们声明了游标变量dept_cv:
DECLARE
TYPE DeptCurTypIS REF CURSOR RETURN dept%ROWTYPE ;
dept_cvDeptCurTyp;--declarecursorvariable
注意:不能在包里声明游标变量。与打包变量不同,游标变量没有一个持久的状态。记住,声明游标变量就是创建了一个指针,它只是指向一个内容,本身并 不存在,所以,游标变量是不能存放到数据库中的。变量的作用域规则也同样适用于游标变量。本地的PL/SQL游标变量在我们进入块或子程序时被初始化,在 退出时被销毁。
在REF CURSOR定义的RETURN子句中,我们可以使用%ROWTYPE指定返回类型,如下例:
DECLARE
TYPE tmpcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
tmp_cvtmpcurtyp;--declarecursorvariable
TYPE empcurtypIS REF CURSOR
RETURN tmp_cv%ROWTYPE ;
emp_cvempcurtyp;--declarecursorvariable
同样,也可以使用%TYPE指定返回值类型,如下例:
DECLARE
dept_recdept%ROWTYPE ;--declarerecordvariable
TYPE deptcurtypIS REF CURSOR
RETURN dept_rec%TYPE ;
dept_cvdeptcurtyp;--declarecursorvariable
最后一个例子,我们把RETURN子句的返回类型指定为用户自定义的RECORD类型:
DECLARE
TYPE emprectypIS RECORD (
empnoNUMBER (4),
enameVARCHAR2 (1O),
salNUMBER (7,2)
);
TYPE empcurtypIS REF CURSOR
RETURN emprectyp;
emp_cvempcurtyp;--declarecursorvariable
我们可以把游标变量声明为函数和过程的形式参数。在下面例子中,我们定义REF CURSOR类型的EmpCurTyp,然后把该类型的游标变量作为过程的形式参数:
DECLARE
TYPE empcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
PROCEDURE open_emp_cv(emp_cvIN OUT empcurtyp)IS ...
注意:跟所有的指针一样,游标变量也能增加参数别名出现的可能性。
5、控制游标变量
我们可以使用三个语句来控制游标变量:OPEN-FOR、FETCH和CLOSE。首先,使用OPEN打开游标变量,然后从结果集中FETCH数据,当完成所有的处理后,就可以使用CLOSE语句关闭游标变量。
OPEN-FOR语句把一个游标变量和一个多行查询关联起来并执行查询。语法如下:
OPEN {cursor_variable|:host_cursor_variable}FOR
{select_statement
|dynamic_string[USINGbind_argument[,bind_argument]...]};
host_cursor_variable是声明在PL/SQL主环境中的游标变量,dynamic_string代表多行查询的字符串表达式。
注意,这里只讨论使用静态SQL的情况。
与游标不同,游标变量是没有参数的。但这个并不影响灵活性,因为我们可以把整个查询传递给游标变量。被传递的查询语句可以使用主变量、PL/SQL变量、参数和函数。
下例中,我们首先打开游标变量emp_cv。与游标相似,我们也可以在游标变量上使用%FOUND、%NOTFOUND、%ISOPEN和%ROWCOUNT:
IF NOT emp_cv%ISOPENTHEN
/*Opencursorvariable.*/
OPEN emp_cvFOR SELECT *FROM emp;
END IF ;
其它的OPEN-FOR语句可以使用不同的查询打开同样的游标变量。在重新打开游标变量之前是不需要关闭它的(但对一个静态游标使用多次OPEN操 作时,系统会抛出预定义异常CURSOR_ALREADY_OPEN)。为执行一个不同的查询而重新打开游标变量时,前面的查询结果就会丢失。
一般地,我们可以把游标变量传递给过程,然后由过程负责打开它,如下例,打包过程打开游标变量emp_cv:
CREATE PACKAGE emp_dataAS
...
TYPE empcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
PROCEDURE open_emp_cv(emp_cvIN OUT empcurtyp);
END emp_data;
CREATE PACKAGE BODY emp_dataAS
...
PROCEDURE open_emp_cv(emp_cvIN OUT empcurtyp)IS
BEGIN
OPEN emp_cvFOR
SELECT *
FROM emp;
END open_emp_cv;
END emp_data;
当我们把游标变量声明为一个打开游标变量的子程序的形式参数时,就必须要指定参数模式为IN OUT模式。那样的话,子程序就可以把一个打开的游标变量返回给调用者。
另外,我们还可以使用独立的过程来打开游标变量。只要简单的在包里定义REF CURSOR类型,然后在一个独立的过程中引用它就行了。例如,如果我们创建了下面的无体包,我们就能在独立的过程中引用包中所定义的REF CURSOR了:
CREATE PACKAGE cv_typesAS
TYPE genericcurtypIS REF CURSOR ;
TYPE empcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
TYPE deptcurtypIS REF CURSOR
RETURN dept%ROWTYPE ;
...
END cv_types;
下例中,我们创建一个引用REF CURSOR类型EmpCurTyp的过程,这个类型是在包cv_types中定义的。
CREATE PROCEDURE open_emp_cv(emp_cvIN OUT cv_types.empcurtyp)AS
BEGIN
OPEN emp_cvFOR
SELECT *
FROM emp;
END open_emp_cv;
为把数据检索集中化处理,我们可以把一个存储过程中类型兼容的查询进行分组。在下面的例子中,打包过程声明了一个选择器作为它的形势参数。调用时,过程会为选定的查询打开游标变量:
CREATE PACKAGE emp_dataAS
TYPE empcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
PROCEDURE open_emp_cv(emp_cvIN OUT empcurtyp,choiceINT);
END emp_data;
CREATE PACKAGE BODY emp_dataAS
PROCEDURE open_emp_cv(emp_cvIN OUT empcurtyp,choiceINT)IS
BEGIN
IF choice=1THEN
OPEN emp_cvFOR
SELECT *
FROM emp
WHERE commIS NOT NULL ;
ELSIF choice=2THEN
OPEN emp_cvFOR
SELECT *
FROM emp
WHERE sal>2500;
ELSIF choice=3THEN
OPEN emp_cvFOR
SELECT *
FROM emp
WHERE deptno=20;
END IF ;
END ;
END emp_data;
为了获取更大的灵活性,我们可以把游标变量和选择器传递给过程,让它执行查询然后返回不同的查询结果。如下例所示:
CREATE PACKAGE admin_dataAS
TYPE gencurtypIS REF CURSOR ;
PROCEDURE open_cv(generic_cvIN OUT gencurtyp,choiceINT);
END admin_data;
CREATE PACKAGE BODY admin_dataAS
PROCEDURE open_cv(generic_cvIN OUT gencurtyp,choiceINT)IS
BEGIN
IF choice=1THEN
OPEN generic_cvFOR
SELECT *
FROM emp;
ELSIF choice=2THEN
OPEN generic_cvFOR
SELECT *
FROM dept;
ELSIF choice=3THEN
OPEN generic_cvFOR
SELECT *
FROM salgrade;
END IF ;
END ;
END admin_data;
我们可以在OCI或Pro*C程序这样的PL/SQL主环境中声明游标变量。在使用游标变量之前,我们需要把它作为主变量传递给PL/SQL。在下面的Pro*C例子中,我们把主游标变量和选择器一并传递给PL/SQL块,然后为被选择的查询打开游标变量:
EXECSQL BEGIN DECLARE SECTION;
...
/*Declarehostcursorvariable.*/
SQL_CURSORgeneric_cv;
intchoice;
EXECSQL END DECLARE SECTION;
...
/*Initializehostcursorvariable.*/
EXECSQL ALLOCATE:generic_cv;
...
/*PasshostcursorvariableandselectortoPL/SQLblock.*/
EXECSQL EXECUTE
BEGIN
IF :choice=1THEN
OPEN :generic_cvFOR
SELECT *
FROM emp;
ELSIF :choice=2THEN
OPEN :generic_cvFOR
SELECT *
FROM dept;
ELSIF :choice=3THEN
OPEN :generic_cvFOR
SELECT *
FROM salgrade;
END IF ;
END ;
END -EXEC;
主游标变量与任何查询的返回类型都兼容,它们就像PL/SQL中的弱类型游标变量一样。
FETCH语句能从多行查询的结果集中取得数据,语法如下:
FETCH {cursor_variable_name|:host_cursor_variable_name}
[BULK COLLECT ]
INTO {variable_name[,variable_name]...|record_name};
下面的例子中,我们每次都从游标变量emp_cv中取出一条数据放到用户定义的记录emp_rec中:
LOOP
/*Fetchfromcursorvariable.*/
FETCH emp_cv
INTO emp_rec;
EXIT WHEN emp_cv%NOTFOUND;--exitwhenlastrowisfetched
--processdatarecord
END LOOP ;
我们可以使用BULK COLLECT子句批量地从游标变量中取得数据放到一个或多个集合中。如下例所示:
DECLARE
TYPE empcurtypIS REF CURSOR
RETURN emp%ROWTYPE ;
TYPE namelistIS TABLE OF emp.ename%TYPE ;
TYPE sallistIS TABLE OF emp.sal%TYPE ;
emp_cvempcurtyp;
namesnamelist;
salssallist;
BEGIN
OPEN emp_cvFOR
SELECT ename,sal
FROM emp;
FETCH emp_cv
BULK COLLECT INTO names,sals;
...
END ;
当游标变量被打开时,关联查询中的所有变量都会被计算。如果要改变查询中的结果集或要使用变量的最新值,我们就必须重新打开游标变量。不过我们可以为每一个从游标变量中取得数据使用不同的INTO子句。
PL/SQL能保证游标变量的返回类型与FETCH语句中的INTO子句后面的类型相兼容。对于游标变量的关联查询返回的每一个字段,INTO子句 后面都必须有一个与之相对应的、类型兼容的域或变量。同样,字段的个数和域的个数也应该相同。否则的话,就会产生错误。如果游标变量是强类型的话,这个错 误在编译期就会发生;如果是弱类型,错误会在运行时发生。在运行时,PL/SQL会在第一次取得数据之前抛出预定义异常 ROWTYPE_MISMATCH。所以,如果我们捕获到错误,并使用一个不同的INTO子句再次执行FETCH语句,就不会丢失数据。
如果我们把游标变量声明为从游标变量中取得数据的子程序的形式参数,那么我们必须指定参数模式为IN或IN OUT模式。但是,如果在子程序中还需要打开游标变量的话,就必须使用IN OUT模式。
如果我们从一个已经关闭了的或是一个未打开的游标变量中选取数据,PL/SQL就会抛出预定义异常INVALID_CURSOR。
CLOSE语句会关闭游标变量。如果执行了关闭操作,相关的结果集就不确定了。关闭操作的语法如下:
CLOSE {cursor_variable_name|:host_cursor_variable_name);
在下面的例子中,当最后一行数据也被处理完毕时,我们就可以关闭游标变量emp_cv:
LOOP
FETCH emp_cv
INTO emp_rec;
EXIT WHEN emp_cv%NOTFOUND;
--processdatarecord
END LOOP ;
/*Closecursorvariable.*/
CLOSE emp_cv;
当把游标变量作为用于关闭游标变量的子程序的形式参数时,我们必须指定它的参数模式为IN或IN OUT模式。
如果我们从一个已经关闭了的或是一个未打开的游标变量中选取数据,PL/SQL就会抛出预定义异常INVALID_CURSOR。
6、游标变量示例:主从表
思考下面的存储过程,它的作用是搜索图书馆数据库中的图书、期刊和磁带。主表存放标题和类别编号(其中1=书,2=期刊,3=磁带)。三个详细表分别保存特定类别的信息。在调用时,存储过程会按照标题来搜索主表,然后利用主表提供的类别编号到从详细表中检索详细内容。
CREATE PACKAGE cv_typesAS
TYPE libcurtypIS REF CURSOR ;
...
END cv_types;
CREATE PROCEDURE FIND_ITEM(titleVARCHAR2 ,
lib_cvIN OUT cv_types.libcurtyp)AS
codeBINARY_INTEGER ;
BEGIN
SELECT item_code
INTO code
FROM titles
WHERE item_title=title;
IF code=1THEN
OPEN lib_cvFOR
SELECT *
FROM books
WHERE book_title=title;
ELSIF code=2THEN
OPEN lib_cvFOR
SELECT *
FROM periodicals
WHERE periodical_title=title;
ELSIF code=3THEN
OPEN lib_cvFOR
SELECT *
FROM tapes
WHERE tape_title=title;
END IF ;
END FIND_ITEM;
7、游标变量示例:客户端PL/SQL块
一个客户端应用程序可能会使用下面的PL/SQL块来显示检索出来的信息:
DECLARE
lib_cvcv_types.libcurtyp;
book_recbooks%ROWTYPE ;
periodical_recperiodicals%ROWTYPE ;
tape_rectapes%ROWTYPE ;
BEGIN
get_title(:title);--titleisahostvariable
FIND_ITEM(:title,lib_cv);
FETCH lib_cv
INTO book_rec;
display_book(book_rec);
EXCEPTION
WHEN ROWTYPE_MISMATCHTHEN
BEGIN
FETCH lib_cv
INTO periodical_rec;
display_periodical(periodical_rec);
EXCEPTION
WHEN ROWTYPE_MISMATCHTHEN
FETCH lib_cv
INTO tape_rec;
display_tape(tape_rec);
END ;
END ;
8、游标变量示例:Pro*C程序
下面的Pro*C程序让用户选择一张数据表,然后使用游标变量进行查询,并返回查询结果:
#include<stdio.h>
#include<sqlca.h>
voidsql_error();
main()
{
chartemp[32];
EXECSQL BEGIN DECLARE SECTION;
char*uid="scott/tiger" ;
SQL_CURSORgeneric_cv;/*cursorvariable*/
inttable_num;/*selector*/
struct/*EMPrecord*/
{
intemp_num;
charemp_name[11];
charjob_title[10];
intmanager;
charhire_date[10];
floatsalary;
floatcommission;
intdept_num;
}emp_rec;
struct/*DEPTrecord*/
{
intdept_num;
chardept_name[15];
charlocation[14];
}dept_rec;
struct/*BONUSrecord*/
{
charemp_name[11];
charjob_title[10];
floatsalary;
}bonus_rec;
EXECSQL END DECLARE SECTION;
/*HandleOracleerrors.*/
EXECSQL WHENEVER SQLERRORDO sql_error();
/*ConnecttoOracle.*/
EXECSQL CONNECT :uid;
/*Initializecursorvariable.*/
EXECSQL ALLOCATE:generic_cv;
/*Exitloopwhendonefetching.*/
EXECSQL WHENEVER NOT FOUNDDO break;
for(;;)
{
printf("\n1=EMP,2=DEPT,3=BONUS" );
printf("\nEntertablenumber(0toquit):" );
gets(temp);
table_num=atoi(temp);
if(table_num<=0)break;
/*Opencursorvariable.*/
EXECSQL EXECUTE
BEGIN
IF :table_num=1THEN
OPEN :generic_cvFOR
SELECT *
FROM emp;
ELSIF :table_num=2THEN
OPEN :generic_cvFOR
SELECT *
FROM dept;
ELSIF :table_num=3THEN
OPEN :generic_cvFOR
SELECT *
FROM bonus;
END IF ;
END ;
END -EXEC;
for(;;)
{
switch(table_num)
{
case1:/*FetchrowintoEMPrecord.*/
EXECSQL FETCH :generic_cvINTO :emp_rec;
break;
case2:/*FetchrowintoDEPTrecord.*/
EXECSQL FETCH :generic_cvINTO :dept_rec;
break;
case3:/*FetchrowintoBONUSrecord.*/
EXECSQL FETCH :generic_cvINTO :bonus_rec;
break;
}
/*Processdatarecordhere.*/
}
/*Closecursorvariable.*/
EXECSQL CLOSE :generic_cv;
}
exit(0);
}
voidsql_error()
{
/*HandleSQLerrorhere.*/
}
9、游标变量示例:SQL*Plus中操作主变量
主变量就是一个声明在主环境中的变量,它会被传递到一个或多个PL/SQL程序中,在程序中可以跟其他的变量一样使用。在SQL*Plus环境里,可以使用命令VARIABLE来声明主变量。例如,我们可以像下面这样声明一个NUMBER类型的主变量:
VARIABLEreturn_codeNUMBER
SQL*Plus和PL/SQL都能引用主变量,SQL*Plus还可以显示主变量的值。但是,在PL/SQL中引用主变量的时候,我们必须加上冒号(:)前缀,如下例所示:
DECLARE
...
BEGIN
:return_code:=0;
IF credit_check_ok(acct_no)THEN
:return_code:=1;
END IF ;
...
END ;
在SQL*Plus环境里,我们可以使用PRINT命令来显示主变量的值,例如
评论