暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle之游标

数据库DBA 2021-04-13
429

提示:公众号展示代码会自动换行,建议横屏阅读或左右滑动代码


游标的使用可以让用户像操作数组一样操作查询出来的数据集,这使得使用PL/SQL编程更加方便,实际上,它提供了一种从集合性质的结果中提取单条记录的方法。


游标概念



可以将游标(Cursor)形象地看成一个变动地光标。它实际上是一个指针,它在一段Oracle存放数据查询结果集或数据操作结果集地内存中,这个指针可以指向结果集中地任何一条记录。这样就可以得到它所指向数据了,但初始时它指向记录首记录。

可以简单地理解游标为指向结果集记录的指针,利用游标可以返回它当前指向的行记录(只能返回一行记录)。如果要返回多行记录,那么需要不断地滚动游标,把想要地数据查询一遍。用户可以操作游标所在行位置的记录,例如,把返回记录作为另一个查询的条件。


游标的种类



Oracle中游标分为静态游标和REF游标两种。其中,静态游标就像一个数据快照,打开游标后的结果集是对数据库数据的一个备份,数据不随着对表执行DML操作后而改变。


静态游标包含如下两种类型:


1.显式游标:是指在使用之前必须有明确的游标声明和定义,这样的游标定义后关联数据查询语句,通常会返回一行或多行。打开游标后,用户可以利用游标的位置对结果集进行检索,使之返回单一的行记录,用户可以操作此记录。关闭游标后,就不能再对结果集进行任何操作。显式游标需要用户自己写代码完成,一切由用户控制。


2.隐式游标:和显式游标不同,它被PL/SQL自动管理,也被称为SQL游标,用于处理多行查询返回的数据记录。由Oracle自动管理。该游标用户无法控制,但能得到它的属性信息。



显式游标



当使用显式游标时,需要使用游标属性确定显式游标的执行信息,显式游标包括%ISOPEN%FOUND%NOTFOUND%ROWCOUNT四种属性。

游标的使用步骤主要分为四个步骤。


1.声明游标,声明游标主要用来给游标命名并且使得游标关联一个查询,具体语法如下:

1DECLARE CURSOR cursor_name  IS  SELECT_statement;


2.打开游标,游标中任何对数据的操作都是建立在游标被打开的前提下,打开游标初始化了游标指针,游标一旦打开,其结果集都是静态的。也就是说,结果集此时不会反映出数据库中对数据进行的增加,删除,修改操作,具体语法如下:

1OPEN cursor_name;


3.读取数据,读取数据要使用FETCH语句完成,它可以把游标指向位置的记录放入到PL/SQL声明的变量中,它只能取出指针当前行的记录。正常情况下,FETCH要和循环语句一起使用,这样指针会不断前进,直到某个条件不符合要求而退出,使用FETCH时游标属性%ROWCOUNT会不断累加,具体语法如下:

1FETCH cursor_name INTO record_name;


4.关闭游标,当关闭某个游标时意味着释放资源,结果集中的数据将不能做任何操作,具体语法如下:

1CLOSE cursor_name;


注意,当引用显式游标属性时,需要带有游标名作为前缀,例如: emp_cursor%ROWCOUNT。下面介绍这四种游标的属性。


1.%ISOPEN

该属性用于检测游标是否已经打开。如果游标已经打开,则返回TURE,否则返回FALSE。想要获取游标的数据,首先一点就是游标是打开的,如果游标在关闭状态下操作数据则会出现错误。下面以检测是否打开游标pdc_isopen_cur,并打开游标为例,说明使用%ISOPEN属性的方法:

 1DECLARE
2CURSOR pdc_isopen_cur IS SELECT*FROM productinfo;
3cur_prodrcd productinfo%ROWTYPE;
4BEING 
5   IF NOT pdc_isopen_cur %ISOPEN  THEN
6      OPEN pdc_isopen_cur ;
7   ELSE  
8      FETCH pdc_isopen_cur INTO cur_prodrcd;
9      DBMS_OUTPUT.PUT_LINE(‘产品ID:’||cur_prodrcd.productid||’产品名称:’||cur_prodrcd.productname||’产品价格:’||cur_prodrcd.productprice);
10    END IF;
11END;


2.%FOUND

该属性用于检测游标结果集是否存在数据。如果存在数据,则返回TRUE,否则返回FALSE。下面以检测是否提取到游标数据,并且在未提取到数据时退出循环为例:

 1DECLARE
2CURSOR pdc_found_cur IS SELECT*FROM productinfo;
3cur_prodrcd productinfo%ROWTYPE;
4BEING 
5   OPEN pdc_isopen_cur ;
6   LOOP
7       IF pdc_found_cur%FOUND THEN
8          FETCH pdc_isopen_cur INTO cur_prodrcd;
9          DBMS_OUTPUT.PUT_LINE(‘产品ID:’||cur_prodrcd.productid||’产品名称:’||cur_prodrcd.productname||’产品价格:’||cur_prodrcd.productprice);
10        ELSE
11          DBMS_OUTPUT.PUT_LINE(‘没有数据被提取’);
12          EXIT;
13       END IF;
14    END LOOP;
15  CLOSE pdc_found_cur;
16END;


3.%NOTFOUND

该属性与%FOUND的含义正好相反,用于检测结果集是否不存在数据。如果不存在则返回TURE,否则返回FALSE。它的使用方法可以参考%FOUND的例子。


4.%ROWCOUNT

它也是比较常用的属性,利用它可以知道当前已经返回了多少行数据。具体使用方法如下:

 1DECLARE
2CURSOR  pdc_rowcount_cur IS SELECT*FROM  productinfo;
3TYPE  pdct_tab  IS TABLE OF productinfo%ROWTYPE;
4pdc_count_rd  pdct_tab;
5BEING 
6   OPEN pdc_rowcount_cur ;
7   LOOP
8        FETCH pdc_rowcount_cur BULK COLLECT INTO  pdc_count_rd  LIMIT 2;
9            FOR  i  IN  pdc_count_rd.first..pdc_count_rd.last  LOOP
10               DBMS_OUTPUT.PUT_LINE(‘产品ID:’|| pdc_count_rd(i).productid||’产品名称:’||pdc_count.rd(i).productname||’产品价格:’||pdc_count_rd(i).productprice);
11            END LOOP;
12                IF  MOD(pdc_rowcount_cur%ROWCOUNT,2) = 0 THEN
13                   DBMS_OUTPUT.PUT_LINE(‘读取到了第’||pdc_rowcount_cur%ROWCOUNT||’条记录’);
14                ELSE
15                   DBMS_OUTPUT.PUT_LINE(‘读取到单条记录为第’||pdc_rowcount_cur%ROWCOUNT||’条记录’);
16                END IF;
17                EXIT WHEN pdc_rowcount_cur%NOTFOUND;
18     END LOOP;
19  CLOSE pdc_rowcount_cur;
20END;


显式游标中的循环



1.游标中的LOOP 语句

通常显式游标提取数据不会只有一条,而是多条记录,这样就需要一个遍历结果集的方式,而LOOP语句就能实现该功能,下面示例将演示在游标中任何使用LOOP语句:

 1DECLARE
2CURSOR pdct_loop_cur  IS SELECT productid,productname,productprice FROM productinfo WHERE productprice>2400;
3cur_productid  productinfo.productid%TYPE;
4cur_productname  productinfo.productname%TYPE;
5cur_productprice  productinfo.productprice%TYPE;
6BEGIN
7    OPEN pdct_loop_cur;
8         LOOP 
9            FETCH pdct_loop_cur INTO cur_productid, cur_productname, cur_productprice;
10            EXIT WHEN pdct_loop_cur%NOTFOUND;
11            DBMS_OUTPUT.PUT_LINE(‘产品ID:’|| cur_productid ||’产品名称:’|| cur_productname ||’产品价格:’|cur_productprice);
12        END LOOP;
13    CLOSE pdct_loop_cur;
14END;


2.使用BULK COLLECTFOR语句的游标

游标中通常使用FETCH…INTO…语句提取数据,这种方法是单条数据提取,在数据量很大的情况下执行效率不是很理想,而FETCH…BUKK  COLLECT  INTO 语句可以批量提取数据,在数据量大的情况下,它的执行效率比单条提取数据的高。此语句的用法可参考如下示例:

 1DECLARE
2CURSOR pdct_collect_cur IS SELECT*FROM productinfo;
3TYPE pdct_tab IS TABLE OF productinfo%ROWTYPE;
4pdct_rd  pdct_tab;
5BEGIN
6    OPEN pdct_collect_cur;
7    LOOP
8        FETCH  pdct_ collect_cur BULK COLLECT INTO  prdct_rd  LIMIT  2;
9        FOR  i  IN  1..pdct_rd.count LOOP
10            DBMS_OUTPUT.PUT_LINE(‘产品ID:’|| pdct_rd(i).productid||’产品名称:’||pdct.rd(i).productname||’产品价格:’||pdct_rd(i).productprice);
11        END LOOP;
12            EXIT WHEN pdct_collect_cur%NOTFOUND;
13    END LOOP;
14    CLOSE pdct_collect_cur;
15END;


3.使用CURSOR FOR LOOP

游标很多机会都会使用迭代结果集,在PL/SQL这个过程中可以使用更简单的方式实现,CURSOR FOR LOOP不需要特别的声明变量,它可以提出行对象类型的数据。在执行循环之前,Oracle会隐含的打开游标,并且每循环一次自动提取一行数据,在提取了所有数据之后自动退出循环并隐含地关闭游标。可以按照类似示例中提取的方式得到列数据,详细脚本如下:

1DECLARE
2CURSOR cf1 IS SELECT productname, productprice FROM productinfo WHERE productprice>1200;
3BEGIN 
4    FOR curcf1 IN cf1 LOOP
5    LOOP
6        DBMS_OUTPUT.PUT_LINE(‘名称:’||curcf1.productname||’产品价格:’||curcf1.productprice);
7        EXIT WHEN cf1%ROWCOUNT:=&input;
8    END LOOP;
9END;


带参数的显式游标



在使用显式游标时是可以指定参数的,指定的参数包括参数的顺序和参数的类型,参数可以传递给游标在查询中使用,这样方便了用户根据不同的查询条件进行查询,也方便了游标会在存储过程中的使用。注意,定义参数只能指定数据类型,不能指定长度,另外,必须在游标的SELECT语句的WHERE子句中引用游标参数,否则失去了定义参数游标的意义。


此类型的游标语法可以参考如下示例:

 1DECLARE
2cur_productid productinfo.productid%TYPE:=’0204’;
3cur_productprice productinfo.productprice%TYPE:=1200;
4cur_prodrcd productinfo %ROWTYPE;
5CURSOR pdct_parameter_cur (id VARCHAR, price NUMBER) IS SELECT *FROM productinfo WHERE productid  LIKE  id||’%’  AND  productprice>price;
6BEGIN
7    OPEN pdct_parameter_cur(cur_productid, cur_productprice);
8    LOOP
9        FETCH pdct_parameter_cur INTO cur_prodrcd;
10        EXIT WHEN pdct_parameter_cur%NOTFOUND;
11            DBMS_OUTPUT.PUT_LINE(‘产品ID:’|| cur_prodrcd.productid||’产品名称:’|| cur_prodrcd.productname||’产品价格:’|| cur_prodrcd.productprice);
12    END LOOP;
13  CLOSE pdct_parameter_cur;
14END;


更新或删除显式游标行



通过使用显式游标,不仅可以取得游标结果集的数据,而且可以更新或删除游标结果集的当前行。注意,当使用游标更新或删除数据时,定义游标必须带有FOR UPDATE子句,并且在更新或删除游标行时必须带有WHERE CURRENT OF子句。


1.更新游标,下面以使用替代变量输入部门号,输出该部门所有雇员姓名及原工资,并更新雇员工资为例:

 1DECLARE
2CURSOR emp_cursor IS SELECT ename,sal,deptno FORM emp FOR UPDATE;
3dno INT:=&input;
4BEGIN
5    FOR emp_record IN emp_cursor LOOP
6        IF emp_record.deptno=dno THEN
7            DBMS_OUTPUT.PUT_LINE(‘姓名:’||emp_record.ename||’原工资:’||emp_record.sal);
8            UPDATE emp SET sal=sal*1.1 WHERE CURRENT OF emp_cursor;
9        END IF;
10    END LOOP;
11END;


2.删除游标变量,使用替代变量输入雇员姓名,输出其雇员姓名及工资,并删除该雇员说明其用法:

 1DECLARE
2CURSOR emp_cursor IS SELECT ename FROM emp FOR UPDATE;
3name VARCHAR2(10):=LOWER(‘&name’);
4BEGIN
5FOR emp_record IN emp_cursor LOOP
6        IF LOWER(emp_record.ename)=name THEN
7            DELETE FROM emp WHERE CURRENT OF emp_cursor;
8        ELSE
9            DBMS_OUTPUT.PUT_LINE(‘姓名:’||emp_record.ename);
10        END IF;
11    END LOOP;
12END;


3.使用OF子句在特定表上加行共享锁,当游标子查询涉及到多张表时,如果在特定表上加行共享锁,那么需要使用OF子句,通过使用输入变量,显示该部门所有员工的姓名,并删除这些雇员为例,说明在显式游标使用OF子句的方法:

 1DECLARE
2CURSOR emp_cursor IS SELECT a.dname, b,ename FROM dept a JOIN emp b ON a.deptno=b.deptno FOR UPDATE OF b.deptno;
3Name VARCHAR2(10):=LOWER(‘&input_name’);
4BEGIN
5    FOR emp_record IN emp_cursor LOOP
6        IF LOWER(emp_record.dname)=name THEN
7            DBMS_OUTPUT.PUT_LINE(‘姓名:’||emp_record.ename);
8            DELETE FROM emp WHERE CURRENT OF emp_cursor;
9        END IF;
10    END LOOP;
11END;



隐式游标



隐式游标和显式游标有所差异,它虽然没有显式游标一样的操作作性,但在实际的工作中也经常使用。


1.隐式游标的特点

每当运行SELECTDML语句时,PL/SQL会打开一个隐式的游标,隐式的游标不受用户的控制,这点和显式的游标有明显的不同,下面列出了两者的不同处:

1)隐式游标由PL/SQL自动管理。

2)隐式游标的默认名称是SQL

3SELECTDML操作产生隐式游标。

4)隐式游标的属性值始终是最新执行的SQL语句的。

具体代码如下:

1DECLARE
2cur_productname prodctinfo.productname%TYPE;
3cur_productprice prodctinfo.productprice%TYPE;
4BEGIN 
5    SELECT productname, productprice INTO cur_productname, cur_productprice FROM productinfo WHERE productid=’&inputID’;
6    IF SQL%FOUND THEN
7        DBMS_OUTPPUT.PUT_LINE(‘产品名称:’||cur_productname||’产品价格:’||cur_productprice);
8    END IF;
9END;


2.隐式游标的属性

隐式游标的属性和显式游标的属性具体表达含义有区别,但属性种类没有变,下面列出了隐式游标的属性:

1%ISOPEN属性,该属性永远返回FALSE,它由Oracle子集控制。

2%FOUND属性,此属性可以反映DML操作是否影响到了数据,当DML操作对数据有了影响时该属性为TRUE,否则为FALSE,也可以反映SELECT INTO语句是否返回了数据。

3%NOTFOUND属性,与%FOUND属性相反,当DML操作没有影响数据以及SELECT INTO没有返回数据时该属性为TRUE,否则返回FALSE

4%ROWCOUNT属性,该属性可以反映出DML操作对数据影响的数量。


游标中使用异常处理



使用游标时,某些情况下得到的数据超出了控制范围,如果不加处理会出现脚本执行中断的情况,这种情况下,脚本开发者通常会使用异常处理来维护脚本的稳定性,前面介绍过当数据库中的数据记录发生变化时使用SELECT INTO语句返回的结果有可能不是单体记录(可能空,也可能多条),这时会出现脚本中断报错的现象,为了避免程序中断则要做异常处理,其代码如下:

 1DECLARE
2cur_productname prodctinfo.productname%TYPE;
3cur_productprice prodctinfo.productprice%TYPE;
4BEGIN 
5    SELECT productname, productprice INTO cur_productname, cur_productprice FROM productinfo WHERE productid=’&inputID’;
6    IF SQL%FOUND THEN
7        DBMS_OUTPPUT.PUT_LINE(‘产品名称:’||cur_productname||’产品价格:’||cur_productprice);
8    END IF;
9    EXCEPTION 
10    WHEN NO_DATA_FOUND THEN
11        DBMS_OUTPPUT.PUT_LINE(‘没有相应数据…’);
12    WHEN TOO_MANY_ROWS THEN
13        DBMS_OUTPPUT.PUT_LINE(‘数据过多…’);
14END;


REF游标



REF游标是一种动态游标,游标变量是基于REF CURSOR类型所定义的变量,它实际是指向内存地址的指针。使用显式游标只能定义静态游标,而通过使用游标变量可以在打开游标时指定其所对应的SELECT语句,从而实现动态游标。注意,不能在远程子程序中使用游标变量,游标变量包括定义游标变量,打开游标变量,提取数据,关闭游标变量,如下所示:


1.定义REF CURSOR类型和游标变量:

1TYPE ref_type_name IS REF CURSOR [ RETURN return_type];
2cursor_variable ref_type_name;


2.打开游标,打开游标变量用于指定游标所对应的SELECT语句。注意,当指定游标子查询时,不能带有FOR UPDATE子句。当打开游标变量时,会执行游标变量所对应的SELECT语句,并将数据存放到游标结果集,语法如下:

1OPEN cursor_variable FOR select_statement;


3.提取数据,提取数据用于将结果集中的行数据存放到PL/SQL变量,语法如下:

1FETCH cursor_variable INTO variable1,variable2,…;


4.关闭游标变量,关闭游标变量用于释放游标结果集,语法如下:

1CLOSE cursor_variable;


5.使用无返回类型的游标变量

当定义REF CURSOR类型时,如果不指定RETURN子句,那么在打开游标时可以指定任何SELECT语句,下面使用输入变量列名,表名,WHERE条件,并输出列值说明:

 1DECLARE
2TYPE ref_cursor_type IS REF CURSOR;
3ref_cursor  ref_curor_type;
4v_empno NUMBER(6);
5v_ename VARCHAR2(10);
6BEGIN
7    OPEN ref_cursor FOR SELECT &empno, &ename FROM &table WHERE &cond;
8       LOOP
9          FETCH ref_cursor INTO v_empno,v_ename;
10          EXIT WHEN ref_cursor%NOTFOUND;
11          DBMS_OUTPUT.PUT_LINE(‘empno=’||v_empno||’ename=’||v_ename);
12       END LOOP;
13    CLOSE ref_cursor;
14END;


6.使用有返回类型的游标变量

定义REF CURSOR类型时,如果指定RETURN子句,那么在打开游标时SELECT语句的返回结果必须与RETURN子句所指定的记录类型匹配。下面以使用替代变量输入部门号,并显式该部门所有雇员的姓名和工资为例,说明使用有返回类型游标变量的方法:

 1DECLARE
2TYPE emp_cursor_type IS REF CURSOR RETURN emp%ROWTYPE;
3emp_cursor  emp_cursor_type;
4emp_record emp%ROWTYPE;
5BEGIN
6    OPEN emp_cursor FOR SELEC*FROM emp WHERE deptno=&dno;
7        LOOP
8            FETCH emp_cursor INTO emp_record;
9            EXIT WHEN emp_cursor%NOTFOUND;
10            DBMS_OUTPUT.PUT_LINE(‘姓名:’||emp_record.ename||’工资:’||emp_record.sal);
11        END LOOP;
12    CLOSE emp_cursor;
13END;


7.使用CURSOR表达式

CURSOR表达式用于实现嵌套游标,通过使用CURSOR表达式,可以在PL/SQL块中处理基于多张表的复杂关联数据,为了在PL/SQL块中取得嵌套游标的数据,需要定义游标变量和嵌套循环,下面以使用替代变量输入部门号,输出部门名称以及该部门所有雇员的姓名和岗位为例,说明使用CURSOR表达式的方法:

 1DECLARE
2CURSOR dept_cursor(no NUMBERIS SELECT a.dname,CURSOR(SELECT*FROM EMP WHERE deptno =a.deptno) FROM dept a WHERE a.deptno=no;
3TYPE ref_cursor_type IS REF CURSOR;
4emp_cursor  ref_cursor_type;
5emp_record  emp%ROWTYPE;
6v_dname  dept.dname%TYPE;
7BEGIN 
8    OPEN dept_cursor(&dno);
9        LOOP
10            FETCH dept_cursor INTO v_dname, emp_cursor;
11            EXIT WHEN dept_cursor%NOTFOUND;
12            DBMS_OUTPUT.PUT_LINE(‘部门名:’||v_dname);
13               LOOP 
14                  FETCH  emp_cursor  INTO emp_record;
15                  EXIT WHEN emp_cursor%NOTFOUND;
16                  DBMS_OUTPUT.PUT_LINE('-----雇员名:'||emp_record.ename||'岗位:'||emp_record.job);
17               END LOOP;
18        END LOOP;
19    CLOSE dept_cursor;
20END;


扫描下方二维码关注公众号,了解相关更新


文章转载自数据库DBA,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论