ORACLE游标详解

Posted 是JF啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ORACLE游标详解相关的知识,希望对你有一定的参考价值。

文章目录


前言:本文为本人学习笔记,所用到的例子均来自oracle11g自带的scott模式,参考书籍清华大学出版社Oracle从入门到精通,还请大家支持购买正版书籍,话不多说,直接开始!

一、概述

什么是游标

  • 游标提供了一种从表中检索数据并进行操作的灵活手段
  • 处理客服端发送到服务器端的sql语句,或者是批处理、存储过程、触发器中的数据处理请求

游标的作用

  • 通过游标PL/SQL程序可以一次处理查询结果集其中的一行,对该数据进行特定操作

基本原理

  • PL/SQL块在执行SELECT、INSERT、UPDATE、DELETE语句时,Oracle会在内存中为其分配上下文区(缓冲区),游标是指向该区的一个指针。
  • 游标为应用程序提供了一种具有对多行数据查询结果集中的每一行数据分班进行单独处理的方法
  • 游标分显示游标、隐式游标、REF游标

二、显示游标

  • 显示游标是用户声明和操作的一种游标,通常用于操作查询结果集(SELECT语句返回的查询结果)
  • 使用显示游标处理数据的步骤:声明游标、打开游标、读取游标、关闭游标
  • 读取游标操作每次只能读取一行数据,对于多条记录需要反复读取直到读取不到为之,可使用for循环遍历
  • 声明游标需要在声明部分进行,其他在执行或异常处理部分进行

1、声明游标

cursor 游标名(游标输入参数)
is select语句
-- 游标参数可以有多个

示例:声明游标读取emp表中job='SALEMAN’的职员

declare
 cursor cur_emp(var_job in varchar2:='SALEMAN') -- varchar不可以指定长度,其他类型也一样
 is select empno,ename,sal
    from emp where job = var_job;

2、打开游标

OPEN 游标名(参数) -- 参数如果为空,则为默认参数

示例:用上面声明的游标查找job='MANAGER’的人

open cur_emp('MANAGER'); -- 如果没有输入参数,则默认查找SALEMAN

3、读取游标

FETCH CUR_NAME INTO VARIABLE; -- VARIABLE 一个变量列表或“记录”变量
  • 游标中包含一个数据行指针用来指向当前数据行
  • 打开游标时指针指向第一行,使用 FETCH … INTO 语句后指向下一行
  • 指针指到最后一条记录为止(实际上指针最后一条记录之后是不存在的,为空,这里只是表示遍历完所有数据),此时游标的%FOUND属性值为FALSE

示例:声明一个检索emp表的游标,搜索职务为 MANAGER 的员工信息,使用 FETCH…INTO 语句和 WHILE 语句来读取游标中所有员工信息,最后输出读取到的信息

-- 去cmd - sqlplus执行
set serveroutput on
declare
    cursor cur_emp(var_job in varchar2:='SALMANE')  -- 声明游标,检索员工信息
    is select empno,ename,sal from emp  where job = var_job;
    type record_emp is record(  -- 声明一个记录类型(RECORD 类型)
    -- 定义当前成员变量
    var_empno emp.empno%type,
    var_ename emp.ename%type,
    var_sal emp.sal%type
       );
    emp_row record_emp; -- 声明一个 record_enp 类型的变量
begin
    open cur_emp('MANAGER');   -- 打开游标
    fetch cur_emp into emp_row;  -- 让游标指向第一行,并将值保存到emp_row中
    while cur_emp%found loop
      dbms_output.put_line(emp_row.var_ename||'的编号是'||emp_row.var_empno||',工资是'||emp_row.var_sal);
      fetch cur_emp into emp_row;  -- 让指针指向下一行,并将值保存到emp_row中
    end loop;
    close cur_emp; -- 关闭游标
end;      
/

使用while之前,先试用 FETCH … INTO 将游标指针指向第一行,保证 %FOUND 值为true,从而保证循环条件成立。

4、关闭游标

-- close 游标名
close cur_emp;

关闭游标后,系统会释放被占用的内存

三、隐式游标

  • 执行一个SQL语句时,系统会自动创建一个隐式游标
  • 隐式游标主要用来处理数据操作语句(如UPDATE、DELETE等)
  • 隐式游标也有属性,因此在使用隐式游标的属性时,需要在属性前加上隐式游标的默认名称 – SQL
  • 实际开发中,常使用隐式游标来判断更新数据行或删除数据行的情况

示例:吧emp表中的 SALESMAN 的工资上调20%,然后使用隐式游标的sql的%ROWCOUNT属性输出上调工资的员工数量

set serveroutput on
begin
  update emp set sal=sal*(1+0.2) where job = 'SALESMAN';
  if sql%notfound then
    dbms_output.put_line('0'); --如果没有数据行被影响
  else
    dbms_output.put_line(sql%rowcount); --输出受影响的数据行
  end if;  
end;      
/

四、游标的属性

  • 游标属性只能用在PL/SQL的流程控制语句内,而不能用在SQL语句内
  • 显示游标与隐式游标都具有以下四个属性,通过这些属性可以获知SQL语句的执行结果以及该游标的状态信息

1、%FOUND(是否找到游标)

  • 表示当前游标是否指向有效一行,是 -> true ; 否 -> false
  • 在隐式游标中%FOUND属性的引用方法是SQL %FOUND

示例:

delete from emp where empno = emp_id;  -- emp_id为一个有值变量
if sql %FOUND then
  insert into success valuse(empno);   -- 删除成功,写进SUCCESS表里
else
  insert into fail valuse(empno);      -- 删除失败,写进Fail表里
end if;  

2、%NOTFOUND(是否没找到游标)

与%FOUND相反

3、%ROWCOUNT(游标行数)

  • 记录了游标抽取过的记录行数,也可以理解为当前游标所在的行号

示例:

loop
  fetch cur_emp into var_empno,var_ename,var_job;
  exit when cur_emp%rowcount = 10                 -- 只抽取10条记录
  ...
end loop;  

4、%ISOPEN(游标是否打开)

示例:

if cur_emp%isopen tneh
  fetch cur_emp into var_empno,var_ename,var_job;
else
  open cur_emp;
end if;    

5、参数化游标

定义游标时带上参数,使得在使用游标时根据参数不同所选中的数据行也不同,达到动态使用的目的

示例:声明一个游标检索指定员工的信息.

set serveroutput on
declare 
 var_ename varchar2(50);      -- 声明变量,用来存储员工名称
 var_job varchar2(50);        -- 声明变量,用来存储员工职务
 cursor cur_emp               -- 定义游标,检索指定编号的信息
 is select ename,job from emp where empno = 7499;
begin
 open cur_emp;                -- 打开游标  
 fetch cur_emp into var_ename,var_job; --读取游标,并存储员工名称和职务
 if cur_emp%found then
   dbms_output.put_line(var_ename || var_job);
 else
    dbms_output.put_line('无数据');
 end if;
end; 
/

五、游标变量

  • 游标变量是动态的,可以在运行时与不同的语句相关联
  • 被用于处理多行的查询结果集
  • 不同于特定的查询绑定,游标变量在打开游标时才能确定对于的查询

1、声明游标变量

  • 游标变量是一种引用类型,程序运行时可以指向不同的存储单元
  • 游标变量的返回类型必须是一个记录类型

定义一个游标变量的完整语法

-- 类型名是新的引用类型的名字
-- 返回类型是一个记录类型
TYPE<类型名> IS REF CURSOR
RETURN<返回类型>

2、打开游标变量

OPEN <游标变量> FOR <SELECT语句>

如果游标变量是受限的,则SELECT语句返回的类型必须与游标变量所受限的记录类型匹配

3、关闭游标变量

  • 与关闭静态游标类似,均使用close语句直接关闭,释放查询所使用的内存空间
  • 关闭已关闭的游标是非法的

六、通过for循环遍历游标

  • 使用游标处理结果集时,可以配合使用FOR语句来完成
  • 使用FOR语句遍历游标中的数据时,可以把它的计时器看做一个自动的RECORD类型变量

(1)FOR语句遍历隐式游标中的数据时,通常在关键字 IN 后面提供由 SELECT 查询出的结果集,在检索结果集的过程中,ORACL 会自动提供一个隐式的游标SQL。
示例:使用隐式游标查询除JOB为 SALESMAN 的员工并输出

set serveroutput on
begin
  for emp_record in(select empno,ename,sal from emp where job = 'SALESMAN') -- 遍历隐式游标中的记录
  loop
    dbms_output.put_line(emp_record.empno); --输出员工编号
    dbms_output.put_line(emp_record.ename); --输出员工姓名
    dbms_output.put_line(emp_record.sal);   --输出员工薪资
  end loop;
end ;
/   

(2)使用显示游标时,IN 关键字后面提供游标名称
示例:检索emp表部门编号为30的员工并输出

set serveroutput on
declare
    cursor cur_emp is select * from emp where deptno = 30;
begin
  for emp_record in cur_emp -- 遍历显示游标中的记录
  loop
    dbms_output.put_line(emp_record.empno); --输出员工编号
    dbms_output.put_line(emp_record.ename); --输出员工姓名
    dbms_output.put_line(emp_record.sal);   --输出员工薪资
  end loop;
end ;
/   

Oracle游标 CURSOR实例详解

作者:gqk


游标 CURSOR:

一、游标概述:

游标(cursor)是数据库系统在内存中开设的一个数据缓冲区,存放SQL语句的执行结果。

 

每个游标都有一个名字,用户可以用SQL语句逐一从游标中获取记录,并赋给变量做进一步处理。

作用:用于定位结果集的行 和 遍历结果集。

二、游标分类:

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

技术分享图片

三:显示游标:

处理显示游标需要的四个步骤:

  • 定义游标:就是定义一个游标的名称,以及与其对应的SELECT语句,形式如下
      • CURSOR 游标名称 IS select_statement
  • 打开游标 :open 游标变量(不能重复打开游标)
      • 打开游标时,SELECT语句的查询结果就被传送到了游标工作区。 
  • 提取数据 :fetch 游标变量 into 变量1,变量2,……
      • 提取操作必须在打开游标之后进行。
  • 关闭游标:close 游标变量 

  显式游标打开后,必须显式地关闭。游标一旦关闭,游标占用的资源就被释放,游标变成无效,必须重新打开才能使用。 

--查询输出所有员工的编号,姓名,工资:(当执行fetch时提取数据指针下移一行)

DECLARE
       v_empid employees.employee_id%TYPE;
       v_name  employees.last_name%TYPE;
       v_salary employees.salary%TYPE;
       
       CURSOR emp_cursor IS 
       SELECT employee_id,last_name,salary 
       FROM employees 
       WHERE department_id=90;
BEGIN       
       OPEN emp_cursor;
             
       FETCH emp_cursor INTO v_empid,v_name,v_salary;--默认指向第一行
       FETCH emp_cursor INTO v_empid,v_name,v_salary;
       FETCH emp_cursor INTO v_empid,v_name,v_salary;
             
       dbms_output.put_line(‘编号:‘ || v_empid);
       dbms_output.put_line(‘姓名:‘ || v_name);
       dbms_output.put_line(‘工资:‘ || v_salary);  
       
       CLOSE emp_cursor;             
END;

四:游标的四个属性

  游标变量%found:当最近一次读记录时成功返回,则值为TRUE
   游标变量%notfound:同上,求反
   游标变量%isopen:判断游标是否已经打开
   游标变量%rowcount:返回已从游标中读取的记录数
  

  --查询输出所有员工的编号,姓名,工资

  loop循环

DECLARE
       v_empid employees.employee_id%TYPE;
       v_name  employees.last_name%TYPE;
       v_salary employees.salary%TYPE;
       
       CURSOR emp_cursor IS 
       SELECT employee_id,last_name,salary 
       FROM employees;  
BEGIN       
       OPEN emp_cursor;
       
       LOOP      
           FETCH emp_cursor INTO v_empid,v_name,v_salary;
           EXIT WHEN emp_cursor%NOTFOUND;--读取最后一次失败后跳出循环
                       
           dbms_output.put_line(‘编号:‘ || v_empid);
           dbms_output.put_line(‘姓名:‘ || v_name);
           dbms_output.put_line(‘工资:‘ || v_salary);  
           dbms_output.put_line(‘--------------------‘); 
       END LOOP;
       
       CLOSE emp_cursor;             
END;

 

  while 循环

DECLARE
       v_empid employees.employee_id%TYPE;
       v_name  employees.last_name%TYPE;
       v_salary employees.salary%TYPE;
       
       CURSOR emp_cursor IS 
       SELECT employee_id,last_name,salary 
       FROM employees;  
BEGIN       
       OPEN emp_cursor;
       
       FETCH emp_cursor INTO v_empid,v_name,v_salary;
       
       WHILE emp_cursor%FOUND LOOP         
           dbms_output.put_line(‘编号:‘ || v_empid);
           dbms_output.put_line(‘姓名:‘ || v_name);
           dbms_output.put_line(‘工资:‘ || v_salary);  
           dbms_output.put_line(‘--------------------‘);
           
           FETCH emp_cursor INTO v_empid,v_name,v_salary; 
       END LOOP;
       
       CLOSE emp_cursor;             
END;

  --查询输出所有员工的编号,姓名,工资(PLSQL表类型)

DECLARE
       TYPE emp_table_type IS TABLE OF employees%ROWTYPE
       INDEX BY BINARY_INTEGER;

       e emp_table_type;
       
       CURSOR emp_cursor IS 
       SELECT * 
       FROM employees;  
BEGIN       
       OPEN emp_cursor;
       
       FETCH emp_cursor BULK COLLECT INTO e;
       
       FOR i IN 1..e.COUNT LOOP
           dbms_output.put_line(‘编号:‘ || e(i).employee_id);
           dbms_output.put_line(‘姓名:‘ || e(i).last_name);
           dbms_output.put_line(‘工资:‘ || e(i).salary); 
           dbms_output.put_line(‘--------------------‘);
       END LOOP;
              
       CLOSE emp_cursor;             
END;

 --查询输出所有员工的编号,姓名,工资(通过limit控制提取的数据量)每次提取6条数据

DECLARE
       TYPE emp_table_type IS TABLE OF employees%ROWTYPE
       INDEX BY BINARY_INTEGER;
       e emp_table_type;
       
       v_count NUMBER := 1;
       
       CURSOR emp_cursor IS 
       SELECT * 
       FROM employees;  
BEGIN       
       OPEN emp_cursor;
       
       LOOP
           dbms_output.put_line(‘第‘ || v_count || ‘次FETCH‘);
           
           FETCH emp_cursor BULK COLLECT INTO e LIMIT 6;
           
           FOR i IN 1..e.COUNT LOOP
               dbms_output.put_line(‘编号:‘ || e(i).employee_id);
               dbms_output.put_line(‘姓名:‘ || e(i).last_name);
               dbms_output.put_line(‘工资:‘ || e(i).salary); 
               dbms_output.put_line(‘--------------------‘);
           END LOOP;
           
           EXIT WHEN emp_cursor%NOTFOUND;
           
           v_count := v_count + 1;       
       END LOOP;
              
       CLOSE emp_cursor;             
END;

 --查询输出某个部门的员工的编号,姓名,工资(参数化游标) 

DECLARE
       v_empid employees.employee_id%TYPE;
       v_name  employees.last_name%TYPE;
       v_salary employees.salary%TYPE;
       
       CURSOR emp_cursor(p_deptid employees.department_id%TYPE) IS 
       SELECT employee_id,last_name,salary 
       FROM employees
       WHERE department_id=p_deptid;  
BEGIN       
       OPEN emp_cursor(50);
       
       LOOP      
           FETCH emp_cursor INTO v_empid,v_name,v_salary;
           EXIT WHEN emp_cursor%NOTFOUND;
                       
           dbms_output.put_line(‘编号:‘ || v_empid || ‘,姓名:‘ || v_name || ‘,工资:‘ || v_salary);                      
       END LOOP;
       
       CLOSE emp_cursor; 
       
       dbms_output.put_line(‘----------------------‘);  
       
       OPEN emp_cursor(90);
       
       LOOP      
           FETCH emp_cursor INTO v_empid,v_name,v_salary;
           EXIT WHEN emp_cursor%NOTFOUND;
                       
           dbms_output.put_line(‘编号:‘ || v_empid || ‘,姓名:‘ || v_name || ‘,工资:‘ || v_salary);                      
       END LOOP;
              
       CLOSE emp_cursor;          
END;

  五:游标for循环:

 PL/SQL中提供了游标for循环语句,可以自动的执行游标的open,fetch,close语句和循环语句的功能,当进入循环时,游标for循环语句自动打开游标,并提取第一行数据,提取

 

  提取完成后自动进入下一个提取,提取完成后自动关闭游标:


--查询输出所有员工的编号,姓名,工资

DECLARE 
       CURSOR emp_cursor IS 
       SELECT *
       FROM employees;  
BEGIN       
       FOR e IN emp_cursor LOOP
           dbms_output.put_line(e.employee_id||‘,‘||e.last_name||‘,‘||e.salary);
       END LOOP;              
END;

--查询输出所有员工的编号,姓名,工资(带参数)

DECLARE 
       CURSOR emp_cursor(p_deptid employees.department_id%TYPE) IS 
       SELECT *
       FROM employees
       WHERE department_id=p_deptid;  
BEGIN       
       FOR e IN emp_cursor(90) LOOP
           dbms_output.put_line(e.employee_id||‘,‘||e.last_name||‘,‘||e.salary);
       END LOOP;              
END;


--查询输出所有员工的编号,姓名,工资(最精简写法)

DECLARE 
       
BEGIN       
       FOR e IN (SELECT * FROM employees) LOOP
           dbms_output.put_line(e.employee_id||‘,‘||e.last_name||‘,‘||e.salary);
       END LOOP;              
END;

 --输出每个部门的部门编号,部门名称以及这个部门的下属员工的编号,姓名,工资
/*
10 nec
   100,tom,2400
   101,jack,17000
20 ge
   102,rose,2600
   xxx,xxx,xxx
   xxx,xxx,xxx
40 hsw
   xxx,xxx,xxx
*/

DECLARE
       CURSOR dept_cursor IS
       SELECT * FROM departments;
       
       CURSOR emp_cursor(p_deptid NUMBER) IS
       SELECT * FROM employees
       WHERE  department_id=p_deptid;
BEGIN
       FOR d IN dept_cursor LOOP
           dbms_output.put_line(d.department_id || ‘ ‘ || d.department_name);
           
           FOR e IN emp_cursor(d.department_id) LOOP
               dbms_output.put_line(‘    ‘ || e.employee_id || ‘,‘ || e.last_name || ‘,‘ || e.salary);
           END LOOP;
       END LOOP;
END;
/*
隐式游标:固定名称SQL
游标四个属性:
SQL%FOUND:如果操作有影响的行,就为TRUE,否则为FALSE
SQL%NOTFOUND:求反
SQL%ISOPEN:在隐式游标中,取值永远为FALSE
SQL%ROWCOUNT:操作影响的行数
必须在事务结束之前读取游标属性,只能读取最近的一次DML操作的游标状态
*/
--用户输入一个任意的部门编号,更新这个部门的员工的工资
--如果有员工被更新,输出“更新成功,有XX个员工被更新”
--如果没有任何员工被更新,输出“这个部门不存在,没有任何员工被更新”
 
DECLARE
       v_deptid NUMBER := &input;
BEGIN
        UPDATE employees
        SET    salary = salary + 1
        WHERE  department_id = v_deptid;
                        
        IF SQL%FOUND THEN
           dbms_output.put_line(‘更新成功,有‘ || SQL%ROWCOUNT || ‘个员工被更新‘);
        ELSE
           dbms_output.put_line(‘这个部门不存在,没有任何员工被更新‘);
        END IF;
        
        COMMIT;
END;

  --通过游标去更新员工的工资,如果工资低于5000块,则把工资改为5000

DECLARE
       CURSOR emp_cursor IS
       SELECT * FROM new_emp FOR UPDATE;
BEGIN
       FOR e IN emp_cursor LOOP
           IF e.salary<5000 THEN
              UPDATE new_emp SET salary=5000 WHERE CURRENT OF emp_cursor;
           END IF;
       END LOOP;
END;

  --通过游标删除工资=5000的员工

DECLARE
       CURSOR emp_cursor IS
       SELECT * FROM new_emp FOR UPDATE;
BEGIN
       FOR e IN emp_cursor LOOP
           IF e.salary=5000 THEN
              DELETE FROM new_emp WHERE CURRENT OF emp_cursor;
           END IF;
       END LOOP;
END;

 --游标变量(动态游标)
--用户输入一个字母,输入E,输出所有员工姓名,如果输入D,输出所有部门的名称

DECLARE
       v_cmd CHAR(1) := ‘&input‘;
       v_name VARCHAR2(50);
       
       --声明自定义的游标变量类型
       --TYPE c_type IS REF CURSOR;
       --声明游标变量
       --c c_type;
       
       c SYS_REFCURSOR;--代替TYPE c_type IS REF CURSOR
BEGIN
       IF v_cmd=‘E‘ THEN
          dbms_output.put_line(‘员工姓名‘); 
          OPEN c FOR SELECT last_name FROM employees;
       ELSIF v_cmd=‘D‘ THEN
          dbms_output.put_line(‘部门名称‘); 
          OPEN c FOR SELECT department_name FROM departments;
       ELSE
          dbms_output.put_line(‘输入无效‘); 
          RETURN;
       END IF;
       
       LOOP
           FETCH c INTO v_name;
           EXIT WHEN c%NOTFOUND;
           dbms_output.put_line(v_name);
       END LOOP;
       
       CLOSE c;
END;

 


























以上是关于ORACLE游标详解的主要内容,如果未能解决你的问题,请参考以下文章

Oracle游标使用总结

如何使用Oracle的游标?

快速掌握Oracle数据库游标的使用方法有哪些?

oracle游标的使用

oracle 游标的值如何赋给变量

oracle中游标的使用?