游标(概念、优点、分类、静态游标的使用(显示游标(显示游标的属性、遍历显示游标、显示游标的FOR循环)、隐式游标(隐式游标的属性))、动态游标的使用、静态游标和动态游标的区别、更新或删除当前游标数据

文章目录

  • 游标
    • 游标概念
    • 游标优点
    • 游标分类
    • 静态游标的使用
      • 显示游标
        • 显示游标的属性
        • 遍历显示游标
        • 显示游标的FOR循环
        • 接收显式游标数据的数据类型(普通变量、记录变量、集合变量)
        • 通过游标更新、删除表的数据
        • 显式游标在开发中不多见的应用
      • 隐式游标
        • 隐式游标的属性
    • 动态游标的使用
      • 静态游标和动态游标的区别
    • 更新或删除当前游标数据


游标

游标概念

在 PL/SQL 块执行 SELECT/INSERT/UPDATE/DELETE 语句时,Oracle 会在内存中为其分配上下文区,而游标是指向该区域的指针

游标为应用程序提供了一种对具有多行数据查询结果集中的每一行单独处理的方案,是设计交互式应用程序的编程接口。

在每个用户会话中,可以同时打开多个游标,其数量由数据库初始化参数文件中的OPEN_CURSORS参数定义。

对于不同的 SQL 语句,游标的使用情况不同:

SQL语句 游标
非查询语句 隐式的
结果是单行的查询语句 隐式的或显示的
结果是多行的查询语句 显示的

游标优点

  • 游标允许应用程序对查询语句返回的行结果集中的每一行进行操作
  • 游标提供了对基于游标位置而对表中数据行进行修改或者更新的能力。

游标分类

  • 隐式游标:非用户显式声明的游标。
    • PL/SQL为SELECT INTO语句、INSERT语句、DELETE语句和UPDATE语句自动声明了隐式游标
  • 显式游标:是由用户声明和操作的游标。
  • 动态游标:用于处理运行时才确定的动态查询语句的结果

静态游标的使用

显示游标

显示游标的使用步骤:定义游标,打开游标,使用游标,关闭游标

  • 在 DECLARE 中定义显式游标:CURSOR cursor_name [(参数名 参数数据类型,…)] [RETURN 数据类型] IS 查询语句
  • 打开游标:OPEN 游标名(输入变量列表)
    • 打开游标语句执行与游标关联的查询,绑定输入变量,识别活动集并将游标指针置于第一行
    • PL/SQL程序不能用OPEN语句重复打开一个游标。
  • 取出数据:FETCH 游标名 INTO 变量列表
  • 关闭游标:CLOSE 游标名
--无参数无返回值游标 例子
DECLARE
    CURSOR c_emp IS SELECT empno, ename FROM emp;
    v_empno emp.empno%TYPE;
    v_ename emp.ename%TYPE;
BEGIN 
    IF not c_emp %ISOPEN THEN
        OPEN c_emp;
    END IF;
    --抓取游标中的第一条记录
    FETCH c_emp into v_empno, v_ename;
    --对游标进行循环操作: 判断游标中是否有下一条记录
    WHILE c_emp %FOUND LOOP
        DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
      FETCH c_emp into v_empno, v_ename;
    END LOOP; 
    --游标使用完毕后关闭游标
    CLOSE c_emp;
END;
--有参数无返回值游标 例子
DECLARE
   CURSOR c4(dept_id NUMBER, j_id VARCHAR2) --1、声明游标,有参数没有返回值
   IS
      SELECT first_name f_name, hire_date FROM employees
      WHERE department_id = dept_id AND job_id = j_id;
 
    --基于游标定义记录变量,比声明记录类型变量要方便,不容易出错
    v_emp_record c4%ROWTYPE;
BEGIN
   OPEN c4(90, 'AD_VP');             --2、打开游标,传递参数值
   LOOP
      FETCH c4 INTO v_emp_record;    --3、提取游标fetch into
      IF c4%FOUND THEN
         DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||'的雇佣日期是'
                            ||v_emp_record.hire_date);
      ELSE
         DBMS_OUTPUT.PUT_LINE('已经处理完结果集了');
         EXIT;
      END IF;
   END LOOP;
   CLOSE c4;                         --4、关闭游标
END;

显示游标的属性

用户可以通过检查游标属性来确定游标的当前状态:

%FOUND:FETCH语句成功返回时,则%FOUND的值为TRUE。

%NOTFOUND:FETCH语句未能提取行时,则%NOTFOUND的值为TRUE。

%ISOPEN:如果游标已经打开,则返回TRUE,否则返回FALSE。

%ROWCOUNT:返回目前为止游标提取的行数,%ROWCOUNT为数值型属性。在第一次获取之前,%ROWCOUNT为0。当FETCH语句返回一行时,则该数加1。

遍历显示游标

DECLARE
    CURSOR c_emp IS SELECT empno,ename FROM emp;
    v_empno emp.empno%TYPE;
    v_ename emp.ename%TYPE;
BEGIN 
    IF not c_emp %isopen THEN
        OPEN c_emp;
    END IF;
    
    --对游标进行循环操作: 判断游标中是否有下一条记录
    LOOP
        FETCH c_emp INTO v_empno, v_ename;
        EXIT WHEN c_emp %notfound;
        DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
    END LOOP;
    
    /* 或者
    FETCH c_emp into v_empno, v_ename;
    WHILE c_emp %FOUND LOOP
        DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
      FETCH c_emp into v_empno, v_ename;
    END LOOP; */
    
    --游标使用完毕后关闭游标
    CLOSE c_emp; 
END;

显示游标的FOR循环

为了简化游标的应用,Oracle提供了游标的for循环语句可以隐式地 OPEN,FETCH,CLOSE 游标以及循环处理结果集。

DECLARE 
    CURSOR c_emp IS SELECT empno, ename FROM emp;
BEGIN  
  --隐式地 OPEN,FETCH,CLOSE 游标以及循环处理结果集
    FOR emp IN c_emp LOOP
        DBMS_OUTPUT.put_line(emp.empno || '-' || emp.ename);
    END LOOP;
END;

或者可以在游标 FOR 循环语句中使用子查询

BEGIN
    FOR c1_rec IN (SELECT department_name, location_id FROM departments) LOOP 
    	DBMS_OUTPUT.PUT_LINE(c1_rec.department_name||'---'||c1_rec.location_id);
    END LOOP;
END;

接收显式游标数据的数据类型(普通变量、记录变量、集合变量)

1、使用普通变量接收游标数据

declare
	v_cnt number :=20;
	cursor emp_cursor is select ename,job,sal from emp where deptno=v_cnt;
	vname emp.ename%type;
	vsal emp.sal%type;
	vjob emp.job%type;
begin
	open emp_cursor;
	loop
		fetch emp_cursor into vname, vjob, vsal;    --使用普通变量接收游标数据
		exit when emp_cursor%notfound;
		dbms_output.put_line('姓名' || vname || '岗顶' || vjob || '工资' || vsal);
	end loop;
	close emp_cursor;
end;

2、使用PL/SQL记录变量接收游标数据:简化单行数据处理

declare
	v_cnt number :=5;
	cursor ecur is select ename,sal from emp order by sal desc; --游标
	erec ecur%rowtype;           --定义声明一个行级的PL/SQL记录变量erec
begin
	open ecur;
	loop
		fetch ecur into erec;    --使用PL/SQL记录变量接收游标数据
		exit when ecur%notfound or ecur%rowcount > v_cnt;
		dbms_output.put_line('姓名' || erec.ename ||  '工资' || erec.sal);
	end loop;
	close ecur;
end;

3、使用PL/SQL集合变量接收游标数据,简化多行多列数据处理

declare
	v_cnt number :=5;
	cursor ec is select ename,sal from emp where lower(job)=lower(v_cnt); --游标
	type eetype is table of ec%rowtype index by binary_intefer; --定义一个复合数据类型PL/SQL表变量eetype
	et eetype; --声明etype
	i int;
begin
	open ec;
	loop
		i:= ec%rowcount + 1;
		fetch ec into et(i);    --使用PL/SQL集合变量接收游标数据
		exit when ec%notfound;
		dbms_output.put_line('姓名' || et(i).ename ||  '工资' || et(i).sal);
	end loop;
	close ec;
end;

通过游标更新、删除表的数据

说明:必须要带for update子句。
语法:cursor cursor_name(parameter_name datatype) is select_statement for update [of column_reference] [nowait]
其中,for update用于在游标结果集加共享锁。
当select语句引用多张表示,使用of子句可以确定哪些表要加锁,没有则select语句所引用的全部表加上锁。
nowait用于指定不等待锁,当其他会话已经锁表之后,默认是当前会话一直等待释放,但指定了NOWAIT后,
如果表已经被其它会话加锁,则抛出异常并退出当前块。
注意,为了更新或删除当前游标行数据,必须在update或delete语句中引用where current of子句。
例子:
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update;–全部加共享锁
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update of emp.deptno;–只在emp表加共享锁
cursor c_name is select sal from emp for update nowait;

declare
cursor emp_cursor is select ename,sal,deptno from emp for update;–默认所有表均加上共享锁。
dno int:=1001;
begin
for emp_record in emp_cursor loop
if emp_record.deptno=dno then
update emp set sal=sal*1.1 where current of emp_cursor; --更新操作
delete from emp where current of emp_cursor; --删除操作
end if;
end loop;
end;

显式游标在开发中不多见的应用

1、通过游标更新、删除表的数据对于多表的情况(使用for子句在特定表上加共享锁)

declare
	cursor emp_cursor is select a.dname,b.ename from dept a join emp b on a.deprno=b.deptno;
	name varchar2(20):='sales';
begin
	for emp_record in emp_cursor loop
		if emp_record.dname=name then
			delete from emp where current of emp_cursor;
		end if;
	end loop;
end;

2、使用fetch…bulk collect批量提取所有数据

declare
	v_name varchar2(20):='clerk';
	cursor ec is select * from emp where job=v_name;
	type etype is table of emp%rowtype; --定义一个复合数据类型嵌套表etype(就看成定义了一个集合)
	et etype;
begin
	open ec;
	fetch ec bulk collect into et;    --把游标里的数据都放入到集合中
	close ec;
	for i in 1..et.count loop
		dbms_output.put_line('姓名' || et(i).ename || '工资' || et(i).sal);
	end loop;
end;

3、使用fetch…bulk collect批量提取 + limit子句限制提取行数的应用

declare
	v_cnt number:=4;
	cursor ec is select * from emp;
	type emp_array_type is varray(5) of emp%rowtype; --定义一个复合数据类型varray数组(就看成定义了一个集合)
	ea emp_array_type;
begin
	open ec;
	loop
		fetch ec bulk collect into ea limit v_cnt;
		for i in 1..ea.count loop
			dbms_output.put_line('姓名' || et(i).ename || '工资' || et(i).sal); --结果只会输出4行
		end loop;
		exit when ec%notfound;
	end loop;
	close ec;
end;

4、游标中保存着游标的应用

declare
	v_dno number:=5010;
	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;
	ec ref_cursor_type;
	er emp%rowtype;
	vdname dept.dname%type;
begin
	open dept_cursor(v_dno);
	loop
		fetch dept_cursor into vdname,ec;
		exit when dept_cursor%notfound;
		dbms_output.put_line('部门' || vdname);
		loop
			fetch ec into er;
			exit when ec%notfound;
			dbms_output.put_line('雇员' || er.ename || '岗位' || er.job); --一个部门有多名员工
		end loop;
	end loop;
	close dept_cursor;
end;

隐式游标

显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下

而对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,隐式游标的名字为SQL,这是由ORACLE 系统定义的。

对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据

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

DECLARE
   v_rows NUMBER;
BEGIN
   --更新数据
   UPDATE employees SET salary = 30000
       WHERE department_id = 90 AND job_id = 'AD_VP';
   --获取默认游标的属性值
   v_rows := SQL%ROWCOUNT;
   DBMS_OUTPUT.PUT_LINE('更新了'||v_rows||'个雇员的工资');
   
    --删除指定雇员;如果部门中没有雇员,则删除部门
    DELETE FROM employees WHERE department_id = v_deptno;
    IF SQL%NOTFOUND THEN
        DELETE FROM departments WHERE department_id=v_deptno;
    END IF;
END;

当系统使用一个隐式游标时,也可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程

隐式游标的属性

SQL%ROWCOUNT:代表DML语句成功执行的数据行数
SQL%FOUND:值为TRUE代表插入、删除、更新或单行查询操作成功
SQL%NOTFOUND:与SQL%FOUND属性返回值相反
SQL%ISOPEN:DML执行过程中为真,结束后为假

提示一下:因为隐式游标默认打开游标,但是如果调用%ISOPEN 却返回FALSE

动态游标的使用

在变量声明部分定义的游标是静态的,不能在程序运行过程中修改。虽然可以通过参数传递来取得不同的数据,但还是有很大的局限性。通过采用动态游标,可以在程序运行阶段随时生成一个查询语句作为游标。要使用动态游标需要先定义一个游标类型,然后声明一个游标变量,游标对应的查询语句可以在程序的执行过程中动态地说明。

动态游标用于处理运行时动态执行的SQL查询的结果集
定义游标类型的语句如下:TYPE 游标类型名 IS REF CURSOR [return 返回值类型]
声明游标变量的语句如下:游标变量名 游标类型名;
在可执行部分可以如下形式打开一个动态游标:OPEN 游标变量名 FOR 查询语句字符串;

DECLARE
 TYPE cur_type IS REF CURSOR;   
 cur cur_type;   
 rec scott.emp%ROWTYPE;   
 str VARCHAR2(50);   
 letter CAHR := 'A';   
BEGIN
	LOOP           
		str := 'SELECT ename FROM emp WHERE ename LIKE ''%'||letter||'%''';   
		OPEN cur FOR str;   
		DBMS_OUTPUT.put_line('包含字母'||letter||'的名字:');   
		LOOP   
			FETCH cur INTO rec.ename;   
			EXIT WHEN cur%NOTFOUND;   
			DBMS_OUTPUT.put_line(rec.ename);   
		END LOOP;   
		EXIT WHEN letter = 'Z';   
		Letter := CHR(ASCII(letter) + 1);   
	END LOOP;   
END;
DECLARE
    --强类型的游标类型(有返回值)
    TYPE strong_cursor_type IS REF CURSOR RETURN emp%ROWTYPE;
    --弱类型的游标类型(无返回值)
    TYPE weak_cursor_type IS REF CURSOR;
    
    --变量定义
    strong_cursor strong_cursor_type;
    weak_cursor weak_cursor_type;
    v_emp emp%ROWTYPE;
BEGIN
    OPEN strong_cursor FOR SELECT * FROM emp;
    LOOP
        FETCH strong_cursor INTO v_emp;
        EXIT WHEN strong_cursor%notfound;
        DBMS_OUTPUT.put_line(v_emp.empno || '-' || v_emp.ename);
    END LOOP;
    CLOSE strong_cursor;
    
    OPEN weak_cursor FOR '&sql';
    LOOP
        FETCH weak_cursor INTO v_emp;
        EXIT WHEN weak_cursor%notfound;
        DBMS_OUTPUT.put_line(v_emp.empno || '-' || v_emp.ename);
    END LOOP;
    CLOSE weak_cursor;
END;

静态游标和动态游标的区别

  • 静态游标是静态定义,REF游标是动态关联
  • 静态游标只能处理静态的查询语言,动态游标可以处理动态查询语句的结果集。
  • 动态游标能作为参数进行传递,而静态游标是不能的。

更新或删除当前游标数据

游标查询语句中尽量使用FOR UPDATE选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。

如果另一个会话已对活动集中的行加了锁,那么SELECT FOR UPDATE操作一直等待到其它的会话释放这些锁后才继续自己的操作;对于这种情况,当加上NOWAIT子句时,如果这些行真的被另一个会话锁定,则OPEN立即返回并给出: ORA-0054 :resource busy and acquire with nowait

DECLARE 
	V_deptno employees.department_id%TYPE := &p_deptno;
	CURSOR emp_cursor 
	IS 
	 SELECT employees.employee_id, employees.salary 
	 FROM employees WHERE employees.department_id=v_deptno
	 FOR UPDATE NOWAIT;                    --1、for update
BEGIN
	FOR emp_record IN emp_cursor LOOP
		IF emp_record.salary < 1500 THEN
			UPDATE employees SET salary=1500
			WHERE CURRENT OF emp_cursor; --2、WHERE CURRENT OF cursor_name子句
		END IF;
	END LOOP;
END

你可能感兴趣的:(笔记,oracle)