关系数据库中的操作是在完整的行集合上执行的。由 SELECT 语句返回的行集合包括满足该语句的 WHERE 子句所列条件的所有行。由该语句返回完整的行集合叫做结果集。应用程序,尤其是互动和在线应用程序,把完整的结果集作为一个单元处理并不总是有效的。这些应用程序需要一种机制来一次处理一行或连续的几行。而游标是对提供这一机制的结果集的扩展。
游标是通过游标库来实现的。游标库是常常作为数据库系统或数据访问 API 的一部分而得以实现的软件,用来管理从数据源返回的数据的属性(结果集)。这些属性包括并发管理、在结果集中的位置、返回的行数,以及是否能够在结果集中向前和/或向后移动(可滚动性)。
游标跟踪结果集中的位置,并允许对结果集逐行执行多个操作,在这个过程中可能返回至原始表,也可能不返回至原始表。换句话说,游标从概念上讲基于数据库的表返回结果集。由于它指示结果集中的当前位置 ,就像计算机屏幕上的光标指示当前位置一样,“游标”由此得名。
在继续向下进行,了解游标在 ADO 中的具体使用之前,熟悉游标的概念是很重要的。
使用游标,您可以:
- 指定结果集中特定行的位置。
- 基于当前的结果集位置检索一行或连续的几行。
- 在结果集的当前位置修改行中的数据。
- 对其他用户所做的数据更改定义不同的敏感性级别。
例如,假设一个应用程序对一名潜在购买者显示可供的产品列表。这名购买者在列表中滚动以查看产品细节和定价,他最后选择购买了一款产品。他又在列表的剩余产品中滚动查看并加以选择。对购买者而言,这些产品只出现一次,但应用程序需要使用可滚动游标在结果集中上下浏览。
可以多种方式使用游标:
- 根本就没有行。
- 某些行或所有行存储在一个单个表中。
- 某些行或所有行来自逻辑上联接的表。
- 作为在游标或字段水平上的只读或可更新游标。
- 作为仅向前或完全可滚动游标。
- 游标键集位于服务器上。
- 对其他应用程序对基本表所做的更改(例如成员、排序、插入、更新和删除)敏感。
- 存在于服务器或客户端。
只读游标帮助用户浏览结果集,读/写游标能够实现单个行的更新。复杂的游标可以使用指回到基本表行的键集来定义。有些游标是只能向前移动的只读游标,而其他游标能够来回移动,并且基于其他应用程序对数据库的更改提供对结果集的动态刷新。
并不是所有的应用程序都需要使用游标来访问或更新数据。某些查询并不需要使用游标直接更新行。游标应当是用来检索数据的最后几项技术选择之一,然后就应当尽可能选择影响最小的游标。当使用存储过程创建结果集时,就不能使用游标编辑或更新方法来更新该结果集。
并发
在某些多用户应用程序中,尽量保持呈现给最终用户的数据是最新数据,这一点至关重要。飞机订票系统就是此类系统的一个经典范例:许多用户会争先预订某个给定航班的同一个座位(这就是一个单个记录)。在这样的情况下,应用程序设计必须处理对某个单个记录的并发操作。
而在另一些应用程序中,并发操作就不那么重要了。在这些情况下,为保持数据始终都是最新数据而产生的费用就不是合理费用。
位置
游标还跟踪结果集中的当前位置。可以把游标位置视为指向当前位置的指针,就像数组索引指向数组中该特定位置的值一样。
可滚动性
应用程序所使用的游标类型还影响着在结果集中向前向后移动行的能力;这种能力有时被称作“可滚动性”。在结果集中向前 and 向后移动的能力增加了游标的复杂性,所以实现起来更为昂贵。因此,只需在必要时才要求具有此功能的游标。
使用游标
这里要做一个声明,我们所说的游标通常是指显式游标,因此从现在起没有特别指明的情况,我们所说的游标都是指显式游标。要在程序中使用游标,必须首先声明游标。
声明游标
语法:
CURSOR cursor_name IS select_statement; |
在PL/SQL中游标名是一个未声明变量,不能给游标名赋值或用于表达式中。
例:
DELCARE CURSOR C_EMP IS SELECT empno,ename,salary FROM emp WHERE salary>2000 ORDER BY ename; ........ BEGIN |
在游标定义中SELECT语句中不一定非要表可以是视图,也可以从多个表或视图中选择的列,甚至可以使用*来选择所有的列 。
打开游标
使用游标中的值之前应该首先打开游标,打开游标初始化查询处理。打开游标的语法是:
OPEN cursor_name |
cursor_name是在声明部分定义的游标名。
例:
OPEN C_EMP; |
关闭游标
语法:
CLOSE cursor_name |
例:
CLOSE C_EMP; |
从游标提取数据
从游标得到一行数据使用FETCH命令。每一次提取数据后,游标都指向结果集的下一行。语法如下:
FETCH cursor_name INTO variable[,variable,...] |
对于SELECT定义的游标的每一列,FETCH变量列表都应该有一个变量与之相对应,变量的类型也要相同。
例:
SET SERVERIUTPUT ON DECLARE v_ename EMP.ENAME%TYPE; v_salary EMP.SALARY%TYPE; CURSOR c_emp IS SELECT ename,salary FROM emp; BEGIN OPEN c_emp; FETCH c_emp INTO v_ename,v_salary; DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); FETCH c_emp INTO v_ename,v_salary; DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); FETCH c_emp INTO v_ename,v_salary; DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); CLOSE c_emp; END |
这段代码无疑是非常麻烦的,如果有多行返回结果,可以使用循环并用游标属性为结束循环的条件,以这种方式提取数据,程序的可读性和简洁性都大为提高,下面我们使用循环重新写上面的程序:
SET SERVERIUTPUT ON DECLARE v_ename EMP.ENAME%TYPE; v_salary EMP.SALARY%TYPE; CURSOR c_emp IS SELECT ename,salary FROM emp; BEGIN OPEN c_emp; LOOP FETCH c_emp INTO v_ename,v_salary; EXIT WHEN c_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE('Salary of Employee'|| v_ename ||'is'|| v_salary); END |
记录变量
定义一个记录变量使用TYPE命令和%ROWTYPE,关于%ROWsTYPE的更多信息请参阅相关资料。
记录变量用于从游标中提取数据行,当游标选择很多列的时候,那么使用记录比为每列声明一个变量要方便得多。
当在表上使用%ROWTYPE并将从游标中取出的值放入记录中时,如果要选择表中所有列,那么在SELECT子句中使用*比将所有列名列出来要安全得多。
例:
SET SERVERIUTPUT ON DECLARE R_emp EMP%ROWTYPE; CURSOR c_emp IS SELECT * FROM emp; BEGIN OPEN c_emp; LOOP FETCH c_emp INTO r_emp; EXIT WHEN c_emp%NOTFOUND; DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary); END LOOP; CLOSE c_emp; END; |
%ROWTYPE也可以用游标名来定义,这样的话就必须要首先声明游标:
SET SERVERIUTPUT ON DECLARE CURSOR c_emp IS SELECT ename,salary FROM emp; R_emp c_emp%ROWTYPE; BEGIN OPEN c_emp; LOOP FETCH c_emp INTO r_emp; EXIT WHEN c_emp%NOTFOUND; DBMS_OUT.PUT.PUT_LINE('Salary of Employee'||r_emp.ename||'is'|| r_emp.salary); END LOOP; CLOSE c_emp; END; |
带参数的游标
与存储过程和函数相似,可以将参数传递给游标并在查询中使用。这对于处理在某种条件下打开游标的情况非常有用。它的语法如下:
CURSOR cursor_name[(parameter[,parameter],...)] IS select_statement; |
定义参数的语法如下:
Parameter_name [IN] data_type[{:=|DEFAULT} value] |
与存储过程不同的是,游标只能接受传递的值,而不能返回值。参数只定义数据类型,没有大小。
另外可以给参数设定一个缺省值,当没有参数值传递给游标时,就使用缺省值。游标中定义的参数只是一个占位符,在别处引用该参数不一定可靠。
在打开游标时给参数赋值,语法如下:
OPEN cursor_name[value[,value]....]; |
参数值可以是文字或变量。
例:
DECALRE CURSOR c_dept IS SELECT * FROM dept ORDER BY deptno; CURSOR c_emp (p_dept VARACHAR2) IS SELECT ename,salary FROM emp WHERE deptno=p_dept ORDER BY ename r_dept DEPT%ROWTYPE; v_ename EMP.ENAME%TYPE; v_salary EMP.SALARY%TYPE; v_tot_salary EMP.SALARY%TYPE; BEGIN OPEN c_dept; LOOP FETCH c_dept INTO r_dept; EXIT WHEN c_dept%NOTFOUND; DBMS_OUTPUT.PUT_LINE('Department:'|| r_dept.deptno||'-'||r_dept.dname); v_tot_salary:=0; OPEN c_emp(r_dept.deptno); LOOP FETCH c_emp INTO v_ename,v_salary; EXIT WHEN c_emp%NOTFOUND; DBMS_OUTPUT.PUT_LINE('Name:'|| v_ename||' salary:'||v_salary); v_tot_salary:=v_tot_salary+v_salary; END LOOP; CLOSE c_emp; DBMS_OUTPUT.PUT_LINE('Toltal Salary for dept:'|| v_tot_salary); END LOOP; CLOSE c_dept; END; |