1、游标的概念

游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。

游标有两种类型:显式游标和隐式游标。在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,系统都会使用一个隐式游标。但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。

2、隐式游标

如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是:
* 插入操作:INSERT。
* 更新操作:UPDATE。
* 删除操作:DELETE。
* 单行查询操作:SELECT ... INTO ...。

隐式游标由 PL/SQL自动定义、打开和关闭

当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。游标的属性有四种,如下所示。

  1. 隐式游标的属性 返回值类型   意    义  
  2. SQL%ROWCOUNT    整型  代表DML语句成功执行的数据行数  
  3. SQL%FOUND   布尔型 值为TRUE代表插入、删除、更新或单行查询操作成功  
  4. SQL%NOTFOUND    布尔型 与SQL%FOUND属性返回值相反  
  5. SQL%ISOPEN  布尔型 DML执行过程中为真,结束后为假 
--隐示游标
declare                                              
 flag char:='U';                                     
begin                                                
  update dept                                        
   set dname='研发部'                                
  where deptno=50;                                   
if SQL%NOTFOUND then                                 
  flag:='Z';                                         
insert into dept(deptno, dname) values(50,'研发部'); 
end if;                                              
if flag='U' then                                     
   dbms_output.put_line('记录已更新');               
else                                                 
   dbms_output.put_line('记录已插入');               
end if;                                              
end;                                                 
/

3、显示游标

显示游标需要声明、打开、提取、关闭。

显式游标的属性如下所示。

    游标的属性   返回值类型   意    义  
    %ROWCOUNT   整型  获得FETCH语句返回的数据行数  
    %FOUND  布尔型 最近的FETCH语句返回一行数据则为真,否则为假  
    %NOTFOUND   布尔型 与%FOUND属性返回值相反  
    %ISOPEN 布尔型 游标已经打开时值为真,否则为假 

set serveroutput on  
--显示游标fetch...into
declare 
  cursor emp_cursor is select emp.ename,emp.sal from emp; 
  v_ename emp.ename%type; 
  v_sal emp.sal%type;
begin 
  open emp_cursor; 
    loop fetch emp_cursor into v_ename,v_sal; 
      exit when emp_cursor%notfound; 
      dbms_output.put_line(v_ename||':'||v_sal); 
    end loop; 
  close emp_cursor;
end;
/

--显示游标fetch...bulk collect into
declare 
  cursor emp_cursor is select emp.ename from emp; 
  type ename_table_type is table of varchar2(10); 
  ename_table ename_table_type;
begin 
  open emp_cursor; 
    fetch emp_cursor bulk collect into ename_table; 
    for i in 1..ename_table.count loop 
      dbms_output.put_line(ename_table(i)); 
    end loop; 
  close emp_cursor;
end;
/

--游标使用记录变量,使用参数
declare 
  cursor emp_cursor(no number) is select * from emp where emp.deptno=no; 
  emp_record emp_cursor%rowtype;
begin 
  open emp_cursor(&no); 
    loop fetch emp_cursor into emp_record; 
      exit when emp_cursor%notfound; 
      dbms_output.put_line(emp_record.ename||'='||emp_record.sal); 
    end loop; 
  close emp_cursor;
end;
/

--使用游标删除数据
declare 
  cursor emp_cursor is select * from bonus where rownum<=10 for update nowait; 
  emp_record emp_cursor%rowtype;
begin 
  open emp_cursor; 
    loop fetch emp_cursor into emp_record; 
      exit when emp_cursor%notfound; 
      delete from bonus where current of emp_cursor; 
    end loop; 
  close emp_cursor; 
  commit;
end;
/

--游标使用for循环
declare 
  cursor emp_cursor is select * from emp;
begin 
  for emp_record in emp_cursor loop 
    dbms_output.put_line(emp_cursor%rowcount||':'||emp_record.ename); 
  end loop;
end;
/

4、动态游标

--动态游标(REF游标)
declare 
  type emp_cursor_type is ref cursor; 
  emp_cursor emp_cursor_type; 
  emp_record emp%rowtype;
begin 
  open emp_cursor for select * from emp; 
    loop fetch emp_cursor into emp_record; 
      exit when emp_cursor%notfound; 
      dbms_output.put_line(emp_record.ename||'='||emp_record.sal); 
    end loop; 
  close emp_cursor;
end;
/

--动态游标(REF游标)
declare                                                
  type emp_type is ref cursor;                           
  cur emp_type;                                          
  name varchar2(20);                                     
  salary number(7,2);                                    
begin                                                    
  open cur for 'select ename,sal from emp where job=:1'  
  using 'SALESMAN';                                     
  loop                                                     
    fetch cur into name,salary;                            
     exit when cur%notfound;                               
     dbms_output.put_line(name||':'||salary);              
    end loop;                                              
  close cur;                                            
end;       
/

5、总结

Cursor与 Ref Cursor区别


从技术底层看,两者是相同的。普通plsql cursor在定义时是“静态”的。而Ref cursors可以动态打开。


Ref cursor根据逻辑动态打开;而游标cursor定义好了就无法修改了


ref cursor可以返回给客户端,cursor则不行。


cursor可以是全局的global ,ref cursor则必须定义在过程或函数中。


ref cursor可以在子程序间传递,cursor则不行。


cursor中定义的静态sql比ref cursor效率高,所以ref cursor通常用在:向客户端返回结果集。



6、游标示例

显示游标
15-1:使用标量变量接受游标数据
     DECLARE
       CURSOR emp_cursor IS
         SELECT ename,job,sal FROM emp WHERE deptno=&dno;
       v_ename emp.ename%TYPE;
       v_sal emp.sal%TYPE;
       v_job emp.job%TYPE;
     BEGIN
       OPEN emp_cursor;
       LOOP
         FETCH emp_cursor INTO v_ename,v_job,v_sal;
         EXIT WHEN emp_cursor%NOTFOUND;
         dbms_output.put_line('姓名:'||v_ename||',岗位:'||v_job||',工资:'||v_sal);
       END LOOP;
       CLOSE emp_cursor;
     END;
     /
     
 15-2:使用PL/SQL记录接受游标数据
     DECLARE
       CURSOR emp_cursor IS
         SELECT ename,sal FROM emp ORDER BY sal DESC;
       emp_record emp_cursor%ROWTYPE;
     BEGIN
       OPEN emp_cursor;
       LOOP
         FETCH emp_cursor INTO emp_record;
         EXIT WHEN emp_cursor%NOTFOUND OR emp_cursor%ROWCOUNT>&n;
         dbms_output.put_line('姓名:'||emp_record.ename||
          ',工资:'||emp_record.sal);
       END LOOP;
       CLOSE emp_cursor;
     END;
     /
     
 15-3:使用PL/SQL集合变量接受游标数据
     DECLARE
       CURSOR emp_cursor IS SELECT ename,sal FROM emp
         WHERE lower(job)=lower('&job');
       TYPE emp_table_type IS TABLE OF emp_cursor%ROWTYPE
         INDEX BY BINARY_INTEGER;
       emp_table emp_table_type;
       i INT;
     BEGIN
       OPEN emp_cursor;
       LOOP
         i:=emp_cursor%ROWCOUNT+1;
         FETCH emp_cursor INTO emp_table(i);
         EXIT WHEN emp_cursor%NOTFOUND;
         dbms_output.put_line('姓名:'||emp_table(i).ename||
           ',工资:'||emp_table(i).sal);
       END LOOP;
       CLOSE emp_cursor;
     END;
     /
     
 15-4:在FOR循环中引用已定义游标
     DECLARE
       CURSOR emp_cursor IS SELECT ename,hiredate FROM emp
         ORDER BY hiredate DESC;
     BEGIN
       FOR emp_record IN emp_cursor LOOP
         dbms_output.put_line('姓名:'||emp_record.ename
           ||',工作日期:'||emp_record.hiredate);
         EXIT WHEN emp_cursor%ROWCOUNT=&n;
       END LOOP;
     END;
     /
     
 15-5:在FOR循环中直接引用子查询
     BEGIN
       FOR emp_record IN (SELECT ename,hiredate,rownum FROM emp
         ORDER BY hiredate) LOOP
         dbms_output.put_line('姓名:'||emp_record.ename
          ||',工作日期:'||emp_record.hiredate);
         EXIT WHEN emp_record.rownum=&n;
       END LOOP;
     END;
     /
     
 15-6:参数游标
     DECLARE
       CURSOR emp_cursor(dno NUMBER) IS
         SELECT ename,job FROM emp WHERE deptno=dno;
     BEGIN
       FOR emp_record IN emp_cursor(&dno) LOOP
         dbms_output.put_line('姓名:'||emp_record.ename
           ||',岗位:'||emp_record.job);
       END LOOP;
     END;
     /
     
 15-7:更新游标行
     DECLARE
       CURSOR emp_cursor IS
         SELECT ename,sal,deptno FROM emp FOR UPDATE;
       dno INT:=&no;
     BEGIN
       FOR emp_record IN emp_cursor LOOP
         IF emp_record.deptno=dno THEN
            dbms_output.put_line('姓名:'||emp_record.ename
             ||',原工资:'||emp_record.sal);
            UPDATE emp SET sal=sal*1.1 WHERE CURRENT OF emp_cursor;
         END IF;
       END LOOP;
     END;
     /
     
 15-8:删除游标行
     DECLARE
       CURSOR emp_cursor IS
         SELECT ename FROM emp FOR UPDATE;
       name VARCHAR2(10):=lower('&name');
     BEGIN
       FOR emp_record IN emp_cursor LOOP
         IF lower(emp_record.ename)=name THEN
            DELETE FROM emp WHERE CURRENT OF emp_cursor;
         ELSE
            dbms_output.put_line('姓名:'||emp_record.ename);
         END IF;
       END LOOP;
     END;
     /
     
 15-9:OF子句在特定表上加共享锁(只在EMP表上加锁)
     DECLARE
       CURSOR emp_cursor IS
         SELECT a.dname,b.ename FROM dept a JOIN emp b
         ON a.deptno=b.deptno
         FOR UPDATE OF b.deptno;
       name VARCHAR2(10):=LOWER('&name');
     BEGIN
       FOR emp_record IN emp_cursor LOOP
         IF LOWER(emp_record.dname)=name THEN
           dbms_output.put_line('姓名:'||emp_record.ename);
           DELETE FROM emp WHERE CURRENT OF emp_cursor;
         END IF;
       END LOOP;
     END;
     /


    

动态游标
15-10:使用无返回类型的游标变量
     DECLARE
       TYPE ref_cursor_type IS REF CURSOR;
       ref_cursor ref_cursor_type;
       v1 NUMBER(6);
       v2 VARCHAR2(10);
     BEGIN
       OPEN ref_cursor FOR
         SELECT &col1 col1,&col2 col2 FROM &table WHERE &cond;
       LOOP
         FETCH ref_cursor INTO v1,v2;
         EXIT WHEN ref_cursor%NOTFOUND;
         dbms_output.put_line('col1='||v1||',col2='||v2);
       END LOOP;
       CLOSE ref_cursor;
     END;
     /
     
 15-11:使用有返回类型的游标变量
     DECLARE
       TYPE emp_cursor_type IS REF CURSOR RETURN emp%ROWTYPE;
       emp_cursor emp_cursor_type;
       emp_record emp%ROWTYPE;
     BEGIN
       OPEN emp_cursor FOR SELECT * FROM emp
         WHERE deptno=&dno;
       LOOP
         FETCH emp_cursor INTO emp_record;
         EXIT WHEN emp_cursor%NOTFOUND;
         dbms_output.put_line('姓名:'||emp_record.ename||
           ',工资:'||emp_record.sal);
       END LOOP;
       CLOSE emp_cursor;
     END;
     /
批量提取
15-12:使用FETCH ... BULK COLLECT 提取所有数据
     DECLARE
       CURSOR emp_cursor IS
         SELECT * FROM emp WHERE LOWER(job)=LOWER('&job');
       TYPE emp_table_type IS TABLE OF emp%ROWTYPE;
       emp_table emp_table_type;
     BEGIN
       OPEN emp_cursor;
       FETCH emp_cursor BULK COLLECT INTO emp_table;
       CLOSE emp_cursor;
       FOR i IN 1.. emp_table.COUNT LOOP
         dbms_output.put_line('姓名:'||emp_table(i).ename
           ||',工资:'||emp_table(i).sal);
       END LOOP;
     END;
     /
     
 15-13:使用LIMIT子句限制提取行数
     DECLARE
       CURSOR emp_cursor IS SELECT * FROM emp;
       TYPE emp_array_type IS VARRAY(5) OF emp%ROWTYPE;
       emp_array emp_array_type;
     BEGIN
       OPEN emp_cursor;
       LOOP
         FETCH emp_cursor BULK COLLECT INTO emp_array LIMIT &rows;
         FOR i IN 1..emp_array.COUNT LOOP
           dbms_output.put_line('姓名:'||emp_array(i).ename
             ||',工资:'||emp_array(i).sal);
         END LOOP;
         EXIT WHEN emp_cursor%NOTFOUND;
       END LOOP;
       CLOSE emp_cursor;
     END;
     /
嵌套游标    
15-14:
     DECLARE
       CURSOR dept_cursor(no NUMBER) IS
          SELECT a.dname,CURSOR(SELECT * FROM emp
          WHERE deptno=a.deptno)
          FROM dept a WHERE a.deptno=no;
       TYPE ref_cursor_type IS REF CURSOR;
       emp_cursor ref_cursor_type;
       emp_record emp%ROWTYPE;
       v_dname dept.dname%TYPE;
     BEGIN
       OPEN dept_cursor(&dno);
       LOOP
          FETCH dept_cursor INTO v_dname,emp_cursor;
          EXIT WHEN dept_cursor%NOTFOUND;
          dbms_output.put_line('部门名:'||v_dname);
          LOOP
            FETCH emp_cursor INTO emp_record;
            EXIT WHEN emp_cursor%NOTFOUND;
            dbms_output.put_line('----雇员名:'||emp_record.ename
             ||',岗位:'||emp_record.job);
          END LOOP;
       END LOOP;
       CLOSE dept_cursor;
     END;
     /