1. 程式人生 > >Oracle儲存過程中游標的簡單使用

Oracle儲存過程中游標的簡單使用

初衷:

儲存過程中查詢語句如何返回多行結果?
我們知道,如果儲存過程中查詢語句有多行結果輸出,會報錯:
ORA-01422: exact fetch returns more than requested number of rows
若想讓儲存過程中的查詢語句返回多行結果不報錯,則需要使用遊標來實現。
本例主要也是用來熟悉儲存過程中游標的簡單使用方法。案例所涉及的資料表使用的是oracle自帶的scott使用者,不熟悉scott的也可使用下列指令碼自行建立。

  1. 建立表

    CREATE TABLE EMP(
     EMPNO NUMBER(4) NOT NULL
    , ENAME VARCHAR2(10), JOB VARCHAR2(9), MGR NUMBER(4), HIREDATE DATE, SAL NUMBER(7, 2), COMM NUMBER(7, 2), DEPTNO NUMBER(2) );
    INSERT INTO EMP VALUES (7369, 'SMITH', 'CLERK', 7902, SYSDATE, 800, NULL, 20); INSERT INTO EMP VALUES (7499, 'ALLEN', 'SALESMAN', 7698, SYSDATE, 1600, 300, 30);
    INSERT INTO EMP VALUES (7521, 'WARD', 'SALESMAN',7698, SYSDATE, 1250, 500, 30); INSERT INTO EMP VALUES (7566, 'JONES', 'MANAGER', 7839,SYSDATE, 2975, NULL, 20); INSERT INTO EMP VALUES (7654, 'MARTIN', 'SALESMAN', 7698, SYSDATE, 1250, 1400, 30); INSERT INTO EMP VALUES (7698, 'BLAKE', 'MANAGER', 7839, SYSDATE, 2850
    , NULL, 30);
    INSERT INTO EMP VALUES (7782, 'CLARK', 'MANAGER', 7839, SYSDATE, 2450, NULL, 10); INSERT INTO EMP VALUES (7788, 'SCOTT', 'ANALYST', 7566, SYSDATE, 3000, NULL, 20); INSERT INTO EMP VALUES (7839, 'KING', 'PRESIDENT', NULL, SYSDATE, 5000, NULL, 10); INSERT INTO EMP VALUES (7844, 'TURNER', 'SALESMAN', 7698, SYSDATE, 1500, 0, 30); INSERT INTO EMP VALUES (7876, 'ADAMS', 'CLERK', 7788, SYSDATE, 1100, NULL, 20); INSERT INTO EMP VALUES (7900, 'JAMES', 'CLERK', 7698, SYSDATE, 950, NULL, 30); INSERT INTO EMP VALUES (7902, 'FORD', 'ANALYST', 7566, SYSDATE, 3000, NULL, 20); INSERT INTO EMP VALUES (7934, 'MILLER', 'CLERK', 7782, SYSDATE, 1300, NULL, 10); COMMIT;
  2. 使用案例

    --案例1、使用遊標查詢部門編號為10的所有人姓名和薪水
    create or replace procedure test2 is
    begin
     declare
       type c is ref cursor;    
       emp_sor c;      
       cname emp.ename%type;     
       csal emp.sal%type;
    begin
      open emp_sor for select ename,sal from emp where deptno=10;       
      loop        
        fetch emp_sor into cname,csal;  --取遊標的值給變數。             
        dbms_output.put_line('ename:'||cname||'sal'||csal);        
        exit when emp_sor%notfound;        
       end loop;         
       close emp_sor;     
    end;
    end test2;
    --案例2、直接定義遊標
    create or replace procedure test3 is
    begin
     declare
       cursor emp_sor  is select ename,sal from emp where deptno=10;
       cname emp.ename%type;
       csal emp.sal%type;
    begin
      open emp_sor;
      loop
        fetch emp_sor into cname,csal;  --取遊標的值給變數。
        dbms_output.put_line('ename:'||cname||'sal'||csal);
        exit when emp_sor%notfound;
       end loop;
       close emp_sor;
    end;
    end test3;
    --案例3、使用記錄變數來接受遊標指定的表的資料
    create or replace procedure test4 is
    begin
     declare
       cursor emp_sor is
         select ename, sal from emp where deptno = 10;
       --使用記錄變數來接受遊標指定的表的資料
       type emp_type is record(
         v_ename emp.ename%type,
         v_sal   emp.sal%type);
       --用emp_type宣告一個與emp_type類似的記錄變數。該記錄有兩列,與emp表的ename,sal同類型的列。
       emp_type1 emp_type;
     begin
       open emp_sor;
       loop
         fetch emp_sor into emp_type1; --取遊標的值給變數。
         dbms_output.put_line(emp_type1.v_ename || ',' || emp_type1.v_sal);
         exit when emp_sor%notfound;
       end loop;
       close emp_sor;
     end;
    end test4;
    案例4、用for遊標取值
    create or replace procedure test5 is
    begin
     declare
       cursor emp_sor is select a.ename from emp a;
       type ename_table_type is table of varchar2(20);
       ename_table ename_table_type;
     begin
      --用for遊標取值
       open emp_sor;
       --通過bulk collect減少loop處理的開銷,使用Bulk Collect提高Oracle查詢效率
       --Oracle8i中首次引入了Bulk Collect特性,該特性可以讓我們在PL/SQL中能使用批查詢,批查詢在某些情況下能顯著提高查詢效率。
       --採用bulk collect可以將查詢結果一次性地載入到collections中。
       --而不是通過cursor一條一條地處理。
       --可以在select into,fetch into,returning into語句使用bulk collect。
       --注意在使用bulk collect時,所有的into變數都必須是collections
         fetch emp_sor bulk collect into ename_table;
         for i in 1 ..ename_table.count loop
           dbms_output.put_line(ename_table(i));
       end loop;
       close emp_sor;
     end;
    end test5;
    --案例5、用for取值,帶隱式遊標會自動開啟和關閉
    create or replace procedure test6 is
    begin
     declare
       cursor emp_sor is select a.ename from emp a;
       type emp_table_type is table of varchar(20);
       begin 
       for emp_record in emp_sor
         loop
           dbms_output.put_line('第'||emp_sor%rowcount||'僱員名:'||emp_record.ename);
         end loop;
     end;
    end test6;
    --案例6、判斷遊標是否開啟
    create or replace procedure test7 is
    begin
     declare
       cursor emp_sor is select a.ename from emp a;
       type emp_table_type is table of varchar(20);
       emp_table emp_table_type;
     begin
      --用for取值,判斷遊標是否開啟
       if not emp_sor%isopen then
         open emp_sor;
       end if;
       fetch emp_sor bulk collect into emp_table;
       dbms_output.put_line(emp_sor%rowcount);
       close emp_sor;
     end;
    end test7;
    --案例7、使用遊標變數取值
    create or replace procedure test8 is
    begin
      --使用遊標變數取值
     declare
       cursor emp_sor is select a.ename,a.sal from emp a;
       emp_record emp_sor%rowtype;
     begin
      open emp_sor;
      loop
        fetch emp_sor into emp_record;
        exit when emp_sor%notfound;
         --exit when emp_sor%notfound放的位置不一樣得到的結果也不一樣。如果放到dbms_....後,
         --結果會多顯示一行資料,即查詢結果的最後一行顯示了兩次。
        dbms_output.put_line('序號'||emp_sor%rowcount||'名稱:'||emp_record.ename||'薪水:'||emp_record.sal);
      end loop;
      close emp_sor;
     end;
    end test8;
    --案例8、帶引數的遊標,在開啟遊標的時候傳入引數
    create or replace procedure test9 is
    begin
      --帶引數的遊標,在開啟遊標的時候傳入引數
     declare
       cursor emp_sor(no number) is select a.ename from emp a where a.deptno=no;
       emp_record emp_sor%rowtype;
     begin
      open emp_sor(10);
      loop
        fetch emp_sor into emp_record;
        exit when emp_sor%notfound;
        dbms_output.put_line('序號'||emp_sor%rowcount||'名稱:'||emp_record.ename);
      end loop;
      close emp_sor;
     end;
    end test9;
    --案例9、使用遊標做更新操作
    create or replace procedure test10 is
    begin
      --使用遊標做更新、刪除操作,必須在定義遊標的時候加上for update
      --當然也可以用for update nowait
     declare
       cursor emp_sor is select a.ename,a.sal from emp a for update;
       cname emp.ename%type;
       csal emp.sal%type;
     begin
      open emp_sor;
      loop
        fetch emp_sor into cname,csal;
        exit when emp_sor%notfound;
        dbms_output.put_line('名稱:'||cname||','||'薪水:'||csal);
        if csal < 2000 then
          update emp set sal = sal+200 where current of emp_sor;
        end if;
      end loop;
      close emp_sor;
      --要檢視更新後的資料,必須得重新開啟遊標去查詢
      open emp_sor;
      loop
        fetch emp_sor into cname,csal;
         exit when emp_sor%notfound;
         dbms_output.put_line('名稱:'||cname||','||'new薪水:'||csal);
      end loop;
      close emp_sor;
     end;
    end test10;
    --案例10、使用遊標做刪除操作
    create or replace procedure test11 is
    begin
      --使用遊標做更新、刪除操作,必須在定義遊標的時候加上for update
     declare
       cursor emp_sor is select a.empno from emp a for update;
       pempno emp.empno%type;
     begin
      open emp_sor;
      loop
        fetch emp_sor into pempno;
        exit when emp_sor%notfound;
        dbms_output.put_line('舊的empno:'||pempno);
        if pempno = 2009 then
           delete emp where current of emp_sor;
        end if;
      end loop;
      close emp_sor;
      --要檢視刪除後的資料,必須得重新開啟遊標去查詢
      open emp_sor;
      loop
        fetch emp_sor into pempno;
        exit when emp_sor%notfound;
        dbms_output.put_line('新的empno:'||pempno);
      end loop;
      close emp_sor;
     end;
    end test11;
    --案例11、直接使用遊標而不用去定義
    create or replace procedure test12 is
     begin
       for emp_record in(select empno,sal,deptno from emp)
         loop
           dbms_output.put_line('員工編號:'||emp_record.empno||',薪水:'||emp_record.sal||',部門編號'||emp_record.deptno);
         end loop;
    end test12;
    --案例12、帶sql的統計查詢
    create or replace procedure test13 is
    begin
     declare
       type test_cursor_type is ref cursor;
       test_cursor test_cursor_type;
       v_name user_tables.TABLE_NAME%type;
       v_count number;
       str_sql varchar2(100);
     begin
      open test_cursor for select table_name from user_tables;
      loop
        fetch test_cursor into v_name;
        if v_name is not null then
          str_sql := 'select count(*) from '|| v_name;
          execute immediate str_sql into v_count;
        end if;
        exit when test_cursor%notfound;
        dbms_output.put_line(v_name||','||v_count);
      end loop;
      close test_cursor;
     end;
    end test13;

    當我們寫完儲存過程之後,我們可以在 command window下執行,oracle預設是不顯示輸出的,
    所以我們要 set serveroutput on 命令來顯示輸出結果,然後exec test1()即可輸出結果。

​ 轉載自@AlanMathisonTuring