第8章 游标

target

了解什么是游标
掌握显示游标的使用
掌握隐士游标的使用

1. 什么是游标

游标的使用可以让用户像操作数组一样操作査询出来的数据集,这使得使用PL/SQL编程 更加方便。

1.1 游标的概念

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

初始时它指向首记录。这种模型很像编程语言中的数组。

可以简单地理解游标为指向结果集记录的指针,利用游标可以返回它当前指向的行记录 (只能返回一行记录)。如果要返回多行,那么需要不断地滚动游标,把想要的数据査询一遍。

1.2游标的种类

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

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

  • 显式游标
  • 隐式游标

2. 显示游标

2.1 语法

CURSOR cursor_name
    [(parameter_name datatype,...))
IS select_statement;

【语法说明】

  • CURSOR cursor_name:声明游标,cursor_name是游标的名称。
  • parameter_name:参数名称。
  • datatype:参数类型。
  • select_statement:游标关联的SELECT语句,但该语句不能是SELECT...INTO...语句。

2.2 显式游标的属性

利用游标属性可以得到游标执行的相关信息。显式游标有以下4个属性:

  • %ISOPEN:用于判断游标是否打开,如果已经打开则返回TRUE,如果游标未打开则 返回FALSE。
  • %FOUND:此属性可用来检测行数据是否有效。如果有效属性返回TRUE,否则返回FALSE。
  • %NOTFOUND:与%FOUND属性恰好相反,如果没有提取出数据则返回TRUE,否则返回FALSE。
  • %ROWCOUNT:累计到当前为止使用FETCH提取数据的行数。

:%ISOPEN的使用

declare 
    cursor product_isopen_cur is 
        select * from productinfo;
        
    cur_product_cd productinfo%rowtype;
        
begin
    if product_isopen_cur%isopen then
        fetch product_isopen_cur into cur_product_cd;
        dbms_output.put_line('产品名称' || cur_product_cd.productname || '产品价格:'|| cur_product_cd.productprice );
    else
        dbms_output.put_line('游标没有打开!');
    end if;
end;
/   

输出:

游标没有打开!

PL/SQL 过程已成功完成。

2.3 游标的使用步骤

(1) 声明游标

声明游标主要用来给游标命名并且使得游标关联一个査询。具体语句如下:

declare 
  cursor cursor_Name is select_statement;

(2) 打开游标

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

OPEN cursor_Name

(3) 读取数据

读取数据要利用FETCH语句完成,它可以把游标指向位置的记录放入到PL/SQL声明的变量当中。

它只能取出指针当前行的记录。

正常情况下,FETCH要和循环语句一起使用,这样指针会不断前进,直到某个条件不符合要求而退出。

使用FETCH时游标属性%ROWCOUNT会不断累加。具体语句如下:

FETCH cursor_Name INTO Record_Name;

(4) 关闭游标

关闭某个名称的游标。此时释放资源,结果集中的数据将不能做任何操作。

:创建一个简单的游标

declare 
    cursor pdct_cur
    is select * from productinfo;
    
    product_cd productinfo%rowtype;
    
begin
    open pdct_cur;
        fetch pdct_cur into product_cd;
        dbms_output.put_line(product_cd.productid || '-' || product_cd.productname || '-' || product_cd.productprice);
    close pdct_cur;
end;
/

输出:

1-华为Mate40Pro-7999

PL/SQL 过程已成功完成。

只输出了一条记录,也就是前面说到的游标指针默认指向第一条记录,若想获取其他记录,要用循环去移动指针,然后读取记录。

2.4 游标中的loop语句

通常游标提取的数据不会是一条,而是多条记录。这样就需要一个遍历结果集的方式--loop语句。

:使用loop循环获取多条记录

declare 
    cursor product_loop_cur
    is select productid,productname,productprice from productinfo
         where productprice > 3000;
         
    cur_productid productinfo.productid%type;
    cur_productname productinfo.productname%type;
    cur_productprice productinfo.productprice%type;
    
begin
    open product_loop_cur;
        loop
            fetch product_loop_cur into cur_productid,cur_productname,cur_productprice;
            exit when product_loop_cur%notfound;
            dbms_output.put_line('产品id:' || cur_productid || '  产品名称:' || cur_productname || '  产品价格:' || cur_productprice);
        end loop;
    close product_loop_cur;
end;
/

输出:

产品id:1  产品名称:华为Mate40Pro  产品价格:7999
产品id:2  产品名称:华为P40Pro  产品价格:6999
产品id:4  产品名称:华为MatePadPro  产品价格:4999
产品id:5  产品名称:华为智慧屏  产品价格:8999
产品id:6  产品名称:华为智慧屏2代  产品价格:9999
产品id:7  产品名称:华为智慧屏2代  产品价格:9999

PL/SQL 过程已成功完成。

2.5 使用bulk collect和for语句的游标

游标中通常使用FETCH...INTO...语句提取数据,这种方式是单条数据提取,在数据量很大的情况下执行效率不是很理想。而FETCH...BULK COLLECT INTO语句可以批量提取数据,在数据量大的情况下它的执行效率比单条提取数据的高。

declare
    cursor product_collect_cur
    is select * from productinfo;
    
    type prot_table is table of productinfo%rowtype;
    pdct_rd prot_table;
    
begin
    open product_collect_cur;
    loop
        --从游标中提取数据,每次取2条
      fetch product_collect_cur bulk collect into pdct_rd limit 2;
        for i in 1..pdct_rd.count loop
          dbms_output.put_line('产品id:' || pdct_rd(i).productid || '  产品名称:' || pdct_rd(i).productname || ' 产品价格:' || pdct_rd(i).productprice);
        end loop;
        exit when product_collect_cur%notfound;
    end loop;
    close product_collect_cur;
end;
/

2.6 使用 CURSOR FOR LOOP

游标很多机会都是迭代结果集,在PL/SQL这个过程中可以使用更简单的方式实现, CURSOR FOR LOOP不需要特别的声明变量。

declare 
    cursor cfl is 
        select productname,productprice from productinfo
        where productprice > 3000;
begin
    --把游标里面的数据放到curcfl中,该类型是%rowtype
    for curcfl in cfl loop
        dbms_output.put_line('名称:' || curcfl.productname || ' 产品价格:' || curcfl.productprice);
    end loop;
end;
/

输出:

名称:华为Mate40Pro 产品价格:7999
名称:华为P40Pro 产品价格:6999
名称:华为MatePadPro 产品价格:4999
名称:华为智慧屏 产品价格:8999
名称:华为智慧屏2代 产品价格:9999
名称:华为智慧屏2代 产品价格:9999

PL/SQL 过程已成功完成。

2.7 带参数的游标

在使用显式游标时是可以指定参数的,指定的参数包括参数的顺序和参数的类型。参数可 以传递给游标在査询中使用,这样就方便了用户根据不同的査询条件进行査询,也方便了游标 在存储过程中的使用。

declare
    cur_productname productinfo.Productname%TYPE := '华为'; 
    cur_productprice productinfo.Productprice%TYPE := 1200; 
    cur_prodrcd productinfo%ROWTYPE;

    CURSOR pdct_parameter_cur (name VARCHAR,price NUMBER) IS 
    SELECT * FROM PRODUCTINFO
    WHERE productname like name||'%' AND productprice > price;
begin
  OPEN pdct_parameter_cur(cur_productname,cur_productprice);
    Loop
      FETCH pdct_parameter_cur INTO cur_prodrcd;
      EXIT WHEN pdct_parameter_cur%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE('产品ID, ' || cur_prodrcd.productid || ' 产品名称: ' || cur_prodrcd.productname || ' 产品价格:'|| cur_prodrcd.productprice);
    END LOOP;
    CLOSE pdct_parameter_cur;
END;
/

输出:

产品ID, 1 产品名称: 华为Mate40Pro 产品价格:7999
产品ID, 2 产品名称: 华为P40Pro 产品价格:6999
产品ID, 3 产品名称: 华为荣耀10 产品价格:1999
产品ID, 4 产品名称: 华为MatePadPro 产品价格:4999
产品ID, 5 产品名称: 华为智慧屏 产品价格:8999
产品ID, 6 产品名称: 华为智慧屏2代 产品价格:9999
产品ID, 7 产品名称: 华为智慧屏2代 产品价格:9999

PL/SQL 过程已成功完成。

3. 隐式游标

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

3.1隐式游标的特点

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

  • 隐式游标由PL/SQL自动管理,
  • 隐式游标中的%ISOPEN属性永远返回FALSE
  • 隐式游标的默认名称是SQL
  • SELECT或DML操作产生隐式游标:
  • 隐式游标的属性值始终是最新执行的SQL语句的。
DECLARE 
    cur_productname productinfo.Productname%TYPE;
    cur_productprice productinfo.Productprice%TYPE;
BEGIN
    SELECT productname, productprice INTO cur_productname,cur_productprice
    FROM PRODUCTINFO
    where productid =   '1';
    IF SQL%FOUND THEN
        DBMS_OUTPUT.PUT_LINE('产品名称:' || cur_productname || ', 产品价格:' || cur_productprice);
    END IF ;
END;
/

输出:

产品名称:华为Mate40Pro, 产品价格:7999

PL/SQL 过程已成功完成。

3.2 游标中使用异常处理

使用游标时,某些情况下得到的数据超出了控制范围,如果不加处理会出现脚本执行中断的情况。这种情况下,脚本开发者通常会使用异常处理来维护脚本的稳定性。

:在游标中使用异常处理

DECLARE
    cur_productname productinfo.Productname%TYPE;
    cur_productprice productinfo.Productprice%TYPE;
BEGIN   
    SELECT productname, productprice INTO cur_productname,cur_productprice
    FROM PRODUCTINFO
    where productid ='1';

    IF SQL%FOUND THEN
        DBMS_OUTPUT.PUT_LINE('产品名称:' || cur_productname || ' ,产品价格:' || cur_productprice);
    END IF;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('没有数据!');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('数据过多!');
END;
/

输出:

产品名称:华为Mate40Pro ,产品价格:7999

PL/SQL 过程已成功完成。

4. 游标案例

本案例涉及到两张表,分别是EMP(员工表)和DEPT(部门表)。

要求:

利用游标转换这两张表的数据,要求把月薪高于1500的,部门地址为“CHICAGO”和“NEW YORK”的员工放到EMP_TEMP表中,月薪低于2500的上调5%。

分析:

第一步:建表 EMP_TEMP

第二步:创建游标存储符合条件的数据。

第三步:把符合要求的数据放进新表

第四步:把工资上调

操作步骤:

建表

create table EMP_TEMP as 
    select * from emp where 1=0;

编写游标案例:

declare 
    cur_empno emp.empno%type;
    cur_ename emp.ename%type;
    cur_emp_cd emp%rowtype;
    
    --创建游标存储符合条件的数据
    cursor cur_emp is
        select * from emp where sal>1500 and DEPTNO 
        in (select DEPTNO from dept where loc in ('CHICAGO','NEW YORK'));
begin   

    --把符合条件的数据存到表中
    open cur_emp;
    loop
      fetch cur_emp into cur_emp_cd;
      if cur_emp%FOUND then
        insert into emp_temp(empno,ename,job,mgr,hiredate,sal,comm,deptno)
        values(cur_emp_cd.empno,cur_emp_cd.ename,cur_emp_cd.job,cur_emp_cd.mgr,cur_emp_cd.hiredate,cur_emp_cd.sal,cur_emp_cd.comm,cur_emp_cd.deptno);
      else
        dbms_output.put_line('已经取出所有数据!共' || cur_emp%rowcount || '条记录');
        exit;
      end if;
    end loop;
    commit;
    close cur_emp;
    
    --调整工资
    update emp_temp 
    set sal = sal * 1.05
    where sal < 2500;
    
    dbms_output.put_line('工资调整完毕!共调整' || SQL%ROWCOUNT || '条记录');
    commit;
end;
/

输出:

已经取出所有数据!共4条记录
工资调整完毕!共调整2条记录

PL/SQL 过程已成功完成。

习题

一、填空题

1.静态游标包含 ( )和( ) 两种类型。
2.通常使用( )命令遍历游标的数据集。
3.游标的使用步骤包括 ( )、( )、( )、( )
4.游标4个属性是 ( )、( )、( )、( )

二、选择题

  1. 如果游标没有被打开时就调用,会不会提示出错?( )
    A.不会 B.会
  2. 下面有关隐式游标中的%ISOPEN属性,说法正确的是( ).
    A. 隐式游标中的%ISOPEN属性永远返回FALSE
    B. 隐式游标中的%ISOPEN属性可以被人为地控制

你可能感兴趣的:(第8章 游标)