游标(学习笔记)
--游标分2种类型:
--静态游标:结果集已经存在(静态定义)的游标,分为隐式和显示游标
- 隐式游标:所有DML语句为隐式游标,通过隐式游标属性可以获取SQL语句信息
- 显示游标:用户显示声明的游标,即指定结果集,当查询返回结果超过一定行时,就需要一个显示游标
--REF 游标:动态关联结果集的临时对象
- -强类型:带return类型
- -弱类型: 不带return类型
--隐式游标
--在PL/SQL中编写的每条SQL 语句实际上都是隐匿游标。通过在DML操作后使用SQL%ROWCOUNT属性,可以
--知道语句所改变的行数(INSERT ,UPDATE,DELETE)返回理新行数,SELECT 返回查询行数.
--显示游标
--语法:CURSOR 游标名称 ([参数列表,]) [RETURN 返回值类型]
IS 子查询(SELECT _statement)
--第一步:声明游标:
CURSOR 游标名 IS SELECT 。。使用CURSOR定义
--第二步:打开游标
使用OPEN
OPEN 游标名
--第三步:提取游标
使用FETCH 游标 INTO 变量
--第四步:关闭游标
CLOSE 游标名
--显式游标属性:
%FOUND 找到是否找到数据,有数据TRUE,没有则FALSE
%ISOPEN 判断游标是否打开,打开则返回TRUE,没有打开则返回FALSE
%NOTFOUND 返回FETCH ...INTO...是否有数据如果没有返回TRUN,有则为FALSE
%ROWCOUNT 返回执行FETCH 语句所返回的行数,初始为0,每执行一行则%ROWCOUNT增加1
--隐式游标属性:
SQL%FOUND 找到是否找到数据,有数据TRUE,没有则FALSE
SQL%ISOPEN 判断游标是否打开,打开则返回TRUE,没有打开则返回FALSE
SQL%NOTFOUND 返回FETCH ...INTO...是否有数据如果没有返回TRUN,有则为FALSE
SQL%ROWCOUNT 返回执行FETCH 语句所返回的行数,初始为0,每执行一行则%ROWCOUNT增加1
隐式游标:
--验证SQL%ROWCOUNT
DECLARE v_count NUMBER; BEGIN SELECT COUNT(*) INTO v_count FROM dept; --只返回一行结果 dbms_output.put_line('SQL%ROWCOUNT= '|| SQL%ROWCOUNT); END;
结果:SQL%ROWCOUNT= 1
--验证SLQ%ROWCOUNT并返回行数
DECLARE BEGIN INSERT INTO dept(deptno,dname,loc)VALUES(90,'qqqq','北京'); dbms_output.put_line('SQL%ROWCOUNT= '|| SQL%ROWCOUNT); END;
结果:SQL%ROWCOUNT= 1
--单行隐式游标
DECLARE v_empRow emp%ROWTYPE; BEGIN SELECT * INTO v_empRow FROM emp WHERE empno=7369; IF SQL%FOUND THEN --发现数据 dbms_output.put_line('员工姓名: '|| v_empRow.ename||'职位: '||v_empRow.job); END IF; END;
结果:员工姓名: SMITH职位: CLERK
--多行隐式游标
DECLARE BEGIN UPDATE EMP SET SAL = SAL * 1.2; IF SQL%FOUND THEN --发现数据 DBMS_OUTPUT.PUT_LINE('更新行数' || SQL%ROWCOUNT); ELSE DBMS_OUTPUT.PUT_LINE('更新行数' || SQL%ROWCOUNT); END IF; END;
结果:更新行数14
--显示游标
--定义游标例1:
DECLARE CURSOR emp_cur IS SELECT empno,ename FROM emp; --定义游标 v_id emp.empno%TYPE; --定义变量ID v_name emp.ename%TYPE; BEGIN OPEN emp_cur ; --打开游标 FETCH emp_cur INTO v_id,v_name; ---提示取游标 LOOP EXIT WHEN emp_cur%notFOUND; --判断是否还有数据 dbms_output.put_line('员工编号'||v_id||',员工姓名:'||v_name); FETCH emp_cur INTO v_id,v_name; ---提示取游标 END LOOP; CLOSE emp_cur; --关闭游标 END;
结果:
员工编号7369,员工姓名:SMITH
员工编号7499,员工姓名:ALLEN
员工编号7521,员工姓名:WARD
员工编号7566,员工姓名:JONES
员工编号7654,员工姓名:MARTIN
员工编号7698,员工姓名:BLAKE
员工编号7782,员工姓名:CLARK
员工编号7788,员工姓名:SCOTT
员工编号7839,员工姓名:KING
员工编号7844,员工姓名:TURNER
员工编号7876,员工姓名:ADAMS
员工编号7900,员工姓名:JAMES
员工编号7902,员工姓名:FORD
员工编号7934,员工姓名:MILLER
--定义游标例2:
DECLARE V_NAME VARCHAR2(50); --定义变量姓名 V_DNAME VARCHAR2(50); --定义变量部门名称 CURSOR CUR_E IS --定义游标 SELECT ENAME, DNAME FROM EMP, DEPT WHERE EMP.DEPTNO = DEPT.DEPTNO; BEGIN OPEN CUR_E; --打开游标 LOOP --使用循环来读取游标 FETCH CUR_E INTO V_NAME, V_DNAME; --提取游标 EXIT WHEN CUR_E%NOTFOUND; --判断游标是否还有内容 DBMS_OUTPUT.PUT_LINE(CUR_E%ROWCOUNT || ' 员工姓名:' || V_NAME || ' 部门名称:' || V_DNAME); --输出内容 END LOOP; CLOSE CUR_E; --关闭游标 END;
结果:
程序结果: 1 员工姓名:SMITH 部门名称:RESEARCH 2 员工姓名:ALLEN 部门名称:SALES 3 员工姓名:WARD 部门名称:SALES 4 员工姓名:JONES 部门名称:RESEARCH 5 员工姓名:MARTIN 部门名称:SALES 6 员工姓名:BLAKE 部门名称:SALES 7 员工姓名:CLARK 部门名称:ACCOUNTING 8 员工姓名:SCOTT 部门名称:RESEARCH 9 员工姓名:KING 部门名称:ACCOUNTING 10 员工姓名:TURNER 部门名称:SALES 11 员工姓名:ADAMS 部门名称:RESEARCH 12 员工姓名:JAMES 部门名称:SALES 13 员工姓名:FORD 部门名称:RESEARCH 14 员工姓名:MILLER 部门名称:ACCOUNTING
另一种指定变量类型:
DECLARE V_EMPNAME EMP.ENAME%TYPE; V_DNAME DEPT.DNAME%TYPE; CURSOR EMP_CUR IS SELECT E.ENAME, D.DNAME FROM EMP E, DEPT D WHERE E.DEPTNO = D.DEPTNO; BEGIN OPEN EMP_CUR; LOOP FETCH EMP_CUR INTO V_EMPNAME, V_DNAME; EXIT WHEN EMP_CUR%NOTFOUND; DBMS_OUTPUT.PUT_LINE('员工姓名 :' || V_EMPNAME || ',部门名称 :' || V_DNAME); END LOOP; CLOSE EMP_CUR; END;
结果同上
--定义游标例3:
DECLARE CURSOR CUR_EMP IS SELECT * FROM EMP; V_EMPROW EMP%ROWTYPE; BEGIN IF CUR_EMP%ISOPEN THEN NULL; ELSE OPEN CUR_EMP; END IF; FETCH CUR_EMP INTO V_EMPROW; WHILE CUR_EMP%FOUND LOOP DBMS_OUTPUT.PUT_LINE('员工姓名: ' || V_EMPROW.ENAME || ',职位: ' || V_EMPROW.JOB || ' ,工资' || V_EMPROW.SAL); FETCH CUR_EMP INTO V_EMPROW; END LOOP; CLOSE CUR_EMP; END;
结果:
员工姓名: SMITH,职位: CLERK ,工资800
员工姓名: ALLEN,职位: SALESMAN ,工资1600
员工姓名: WARD,职位: SALESMAN ,工资1250
员工姓名: JONES,职位: MANAGER ,工资2975
员工姓名: MARTIN,职位: SALESMAN ,工资1250
员工姓名: BLAKE,职位: MANAGER ,工资2850
员工姓名: CLARK,职位: MANAGER ,工资2450
员工姓名: SCOTT,职位: ANALYST ,工资3000
员工姓名: KING,职位: PRESIDENT ,工资5000
员工姓名: TURNER,职位: SALESMAN ,工资1500
员工姓名: ADAMS,职位: CLERK ,工资1100
员工姓名: JAMES,职位: CLERK ,工资950
员工姓名: FORD,职位: ANALYST ,工资3000
员工姓名: MILLER,职位: CLERK ,工资1300
--使用FOR循环
DECLARE CURSOR cur_emp IS SELECT * FROM emp; BEGIN FOR emp_row IN cur_emp LOOP DBMS_OUTPUT.PUT_LINE('员工姓名: ' || emp_row.ENAME || ',职位: ' || emp_row.JOB || ' ,工资' || emp_row.SAL); END LOOP; END;
结果同上
--使用FOR循环操作游标不仅代码简单,而且可以将游标的状态交给系统去完成,尽量使用FOR循环为主
--定义游标例4:使用游标UPDATE数据
--公司上市,决定给员工涨工资,入职年限超过1年加100,1000元封顶
--第一种 直接将计算的结果进行判断
DECLARE V_ID EMP.EMPNO%TYPE; V_HIREDATE EMP.HIREDATE%TYPE; CURSOR EMP_CUR IS SELECT EMPNO, HIREDATE FROM EMP; BEGIN OPEN EMP_CUR; LOOP FETCH EMP_CUR INTO V_ID, V_HIREDATE; EXIT WHEN EMP_CUR%NOTFOUND; IF (TO_CHAR(SYSDATE, 'yyyy') - TO_CHAR(V_HIREDATE, 'yyyy')) * 100 < 1000 THEN UPDATE EMP SET SAL = SAL + (TO_CHAR(SYSDATE, 'yyyy') - TO_CHAR(V_HIREDATE, 'yyyy')) * 100 WHERE EMPNO = V_ID; DBMS_OUTPUT.PUT_LINE('工资增加成功'); COMMIT; ELSE UPDATE EMP SET SAL = SAL + 1000 WHERE EMPNO = V_ID; DBMS_OUTPUT.PUT_LINE('工资增加成功'); COMMIT; END IF; END LOOP; CLOSE EMP_CUR; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('数据异常'); ROLLBACK; END;
--第二种通过一个变量判断
DECLARE V_ID EMP.EMPNO%TYPE; --定义员工编号ID V_HIREDATE EMP.HIREDATE%TYPE; --定义员工入职日期变量 V_SAL EMP.SAL%TYPE; --定义计算每个员工要涨工资的总数变量 CURSOR CUR_EMP IS SELECT EMPNO, HIREDATE --定义游标查询员工ID和入职日期 FROM EMP; BEGIN IF CUR_EMP%ISOPEN THEN --判断游标是否打开 NULL; --打开了就什么也不做 ELSE OPEN CUR_EMP; --没有打开就打开游标 END IF; LOOP FETCH CUR_EMP INTO V_ID, V_HIREDATE; EXIT WHEN CUR_EMP%NOTFOUND; V_SAL := (TO_CHAR(SYSDATE, 'yyyy') - TO_CHAR(V_HIREDATE, 'yyyy')) * 100; IF V_SAL < 1000 THEN --判断是否小于1000 UPDATE EMP SET SAL = SAL + V_SAL WHERE EMPNO = V_ID; COMMIT; ELSE --大于1000 UPDATE EMP SET SAL = SAL + 1000 WHERE EMPNO = V_ID; COMMIT; END IF; END LOOP; CLOSE CUR_EMP; --关闭游标 EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('数据异常'); ROLLBACK; --出现异常 就回滚 END;
--定义游标例5
--在动态SELECT中使用游标
DECLARE V_LOWSAL EMP.SAL%TYPE := &LOWSAL; V_HISAL EMP.SAL%TYPE := &HISSAL; CURSOR CUR_EMP IS SELECT * FROM EMP WHERE SAL BETWEEN V_LOWSAL AND V_HISAL; BEGIN FOR EMP_ROW IN CUR_EMP LOOP DBMS_OUTPUT.PUT_LINE('员工姓名: ' || EMP_ROW.ENAME || ',职位: ' || EMP_ROW.JOB || ' ,工资' || EMP_ROW.SAL); END LOOP; END;
--REF动态游标
TYPE 类型名 IS REF CURSOR [RETURN]数据类型
游标名 类型名
OPEN 游标名 FOR 查询语句
--强类型:带RETURN
DECLARE TYPE REF_EMP IS REF CURSOR RETURN EMP%ROWTYPE; --定义一个REF动态游标,并返回类型 CUR_EMP REF_EMP; --定义一个变量类型是上面的REF动态游标也称游标变量 V_EMP EMP%ROWTYPE; --定义一个变量,类型和REF游标返回类型相同,行类型 BEGIN OPEN CUR_EMP FOR SELECT * FROM EMP; --打开游标,并关联查询语句 LOOP FETCH CUR_EMP INTO V_EMP; --提取游标数据 EXIT WHEN CUR_EMP%NOTFOUND; DBMS_OUTPUT.PUT_LINE(CUR_EMP%ROWCOUNT||' 员工编号:' || V_EMP.EMPNO || ' 员工姓名:' || V_EMP.ENAME); END LOOP; CLOSE CUR_EMP; END;
--弱类型:不带RETURN
DECLARE TYPE REF_EMP IS REF CURSOR; --定义一个REF动态游标,并返回类型 CUR_EMP REF_EMP; --定义一个变量类型是上面的REF动态游标也称游标变量 V_EMP EMP%ROWTYPE; --定义一个变量,类型和REF游标返回类型相同,行类型 V_DEPT DEPT%ROWTYPE; --定义一个变量,类型和REF游标返回类型相同,行类型 BEGIN --员工表 OPEN CUR_EMP FOR SELECT * FROM EMP; --打开游标,并关联查询语句 LOOP FETCH CUR_EMP INTO V_EMP; --提取游标数据 EXIT WHEN CUR_EMP%NOTFOUND; DBMS_OUTPUT.PUT_LINE(CUR_EMP%ROWCOUNT || ' 员工编号:' || V_EMP.EMPNO || ' 员工姓名:' || V_EMP.ENAME); END LOOP; CLOSE CUR_EMP; ------------下面是部门表 OPEN CUR_EMP FOR SELECT * FROM DEPT; --打开游标,并关联查询语句 LOOP FETCH CUR_EMP INTO V_DEPT; --提取游标数据 EXIT WHEN CUR_EMP%NOTFOUND; DBMS_OUTPUT.PUT_LINE(CUR_EMP%ROWCOUNT || ' 部门编号:' || V_DEPT.DEPTNO || ' 部门名称:' || V_DEPT.DNAME); END LOOP; CLOSE CUR_EMP; END;
在Oracle9i之后为了方便用户使用弱类型游标变量,可以使用 SYS_REFCURSOR 来替代 TYPE REF_EMP IS REF CURSOR 上面的声明可以换为: CUR_EMP SYS_REFCURSOR; --定义一个变量类型是上面的REF动态游标也称游标变量 V_EMP EMP%ROWTYPE; --定义一个变量,类型和REF游标返回类型相同,行类型 V_DEPT DEPT%ROWTYPE; --定义一个变量,类型和REF游标返回类型相同,行类型
--根据用户输入,来输出内容
DECLARE --TYPE REFC_T IS REF CURSOR; REFC SYS_REFCURSOR; V_ID NUMBER; V_NAME VARCHAR2(50); V_INPUT VARCHAR(1) := UPPER(SUBSTR('&input', 1, 1)); BEGIN IF V_INPUT = 'E' THEN OPEN REFC FOR SELECT EMPNO, ENAME FROM EMP; DBMS_OUTPUT.PUT_LINE('=====员工表信息======'); ELSIF V_INPUT = 'D' THEN OPEN REFC FOR SELECT DEPTNO, DNAME FROM DEPT; DBMS_OUTPUT.PUT_LINE('=====部门表信息======'); ELSE DBMS_OUTPUT.PUT_LINE('=====员工表信息(E)或者部门表信息(D)======='); RETURN; END IF; FETCH REFC INTO V_ID, V_NAME; WHILE REFC%FOUND LOOP DBMS_OUTPUT.PUT_LINE(REFC%ROWCOUNT || '# ' || V_ID || ' ' || V_NAME); FETCH REFC INTO V_ID, V_NAME; END LOOP; CLOSE REFC; END;