1. 程式人生 > >PL/SQL遊標使用詳解

PL/SQL遊標使用詳解

每當在PL/SQL中執行一個SQL語時,Oracle資料庫都會為這個語句分一個上下文區域(Context Area)來處理所必需的資訊,其中包括語句處理的行數,一個指向語句被分析以後的表示形式的指標以及查詢的結果集。遊標是指向上下文區域的控制代碼或指標PL/SQL通過遊標可以控制或處理上下文區域。如果按照遊標是否繫結到一個專門的查詢語句來劃分,可以分為靜態遊標動態遊標。其中靜態遊標又分為:隱式遊標和顯示遊標;動態遊標分為:強型別和弱型別

1.隱式遊標

每當我們執行一個DML語句(包括INSERT,UPDATE,MERGE或者DELETE)或者SELECT INTO語句時,PL/SQL都會宣告一個隱式遊標並管理這個遊標。這種遊標之所以叫做隱式遊標,是因為資料庫自動執行遊標相關操作,比如,開啟,提取,關閉等。隱式遊標又叫做SQL遊標。

1.1隱式遊標屬性

隱式遊標屬性返回有關DDL語句和DML語句執行的資訊,該遊標屬性值總是返回最近執行的SQL語句。執行開啟的隱式遊標的屬性值是空的。隱式遊標可用的屬性如下:

隱式遊標屬性

名    字

說    明

SQL%FOUND

如果取到記錄就返回TRUE,否者返回FLASE

SQL%NOTFOUND

如果沒有取得記錄就返回TRUE,否者返回FALSE

SQL%ROWCOUNT

返回從遊標中取出的記錄數

SQL%ISOPEN

由於隱式遊標總是自動開啟和關閉,因此這個屬性中是FALSE

隱式遊標屬性總是返回最後一次執行的SQL語句的屬性值,而不管這個SQL語句時在哪個程式碼塊或者程式中執行的。

declare
  v_empno emp.empno%type := 3792;
begin
  update emp e set e.sal = 5000 where e.empno = v_empno;
  if sql%notfound then
    insert into emp
      (empno, ename, hiredate, sal, deptno)
    values
      (v_empno, 'CHICLEWU', date '2011-07-08', 4000, 50);
  end if;
end;

1.2使用隱式遊標屬性準則

當使用隱式遊標屬性是,需要考慮以下準則:

  • 遊標屬性值總是返回最近執行的SQL語句,它可能是在不同的作用域(例如,在一個子塊)。
  • 在SELECT INTO語句中,%NOTFOUND屬性是不生效的:

    • 如果在SELECT INTO語句沒有返回行,PL/SQL立即丟擲一個預訂的NO_DATA_FOUND異常,在檢查%NOTFOUND之前中斷控制流。

    • SELECT INTO語句呼叫聚合函式總是返回個值或者空值。%NOTFOUNT屬性總是返回FALSE,因此檢查它是沒有必要的。  

2.顯示遊標

當你需要精確地控制查詢處理時,可以在任何PL/SQL塊、子程式或者包的宣告部分顯示地宣告一個遊標。顯示遊標就是在程式碼的宣告部分明確定義的SELECT語句,並同時指定一個名字。然後你可以通過三個語句來控制遊標:OPEN、FETCH和CLOSE。首先,你需要使用OPEN語句初始化遊標,標識結果集。然後,可以反覆地執行FETCH語句直到所有行都被取出,或者使用BULK COLLECT語句一次性取出所有行。最後,只用CLOSE語句釋放遊標。

2.1宣告顯示遊標

要使用顯示遊標,必須先在PL/SQL塊或包的規範部分宣告它。

CURSOR cursor_name
 [ ( cursor_parameter_declaration [, cursor_parameter_declaration ]... )]
   [ RETURN rowtype] IS select_statement ;

其中,cusor_name是顯示遊標的名字;cursor_parameter_declartion是顯示遊標的引數,是可選項;select_statement是顯示遊標指定的SELECT語句。

2.2開啟顯示遊標

OPEN cursor_name
  [ ( cursor_parameter_name [ [,] cursor_parameter_name ]... ) ] ;

其中,cusor_name是顯示遊標的名字;cursor_parameter_name是顯示遊標的引數,是可選項。如果遊標宣告時,指定了遊標引數,則開啟時一定要指定該引數。

2.3提取資料

每次只提取一行資料:

FETCH  cursor_name

  INTO [(variable_name [, variable_name ]... | record_name )];

一次提取多行資料:

FETCH  cursor_name
  BULK COLLECT INTO [(collection_name [,collection_name...])];

2.4關閉顯示遊標

CLOSE cursor_name;

2.5顯示遊標屬性

每個顯示遊標和遊標變數都有四個屬性:%FOUND、%NOTFOUND、%ROWCOUNT和%ISOPEN。可以下面的語法使用這些屬性:

cusor%attribute

其中,cursor就是我們宣告的遊標名字或者遊標變數。

顯示遊標屬性

名    字

說    明

cusor%FOUND

如果取到記錄就返回TRUE,否者返回FLASE

cusor%NOTFOUND

如果沒有取得記錄就返回TRUE,否者返回FALSE

cusor%ROWCOUNT

返回當值從指定遊標取得的記錄數量

cusor%ISOPEN

如果遊標時開啟的則返回TRUE,否則返回FALE

2.6顯示遊標例子

2.6.1不帶引數的顯示遊標

declare
  cursor cur_emp is
    select e.ename, e.sal from emp e where e.deptno = 10; --宣告遊標

  v_ename emp.ename%type;
  v_sal   emp.sal%type;
begin
  open cur_emp; --開啟遊標
  --迴圈取出
  loop
    fetch cur_emp
      into v_ename, v_sal; --提取記錄
    exit when cur_emp%notfound;
    dbms_output.put_line(v_ename || '的工資是' || v_sal);
  end loop;
  close cur_emp; --關閉遊標

end;

2.6.2帶引數引數的顯示遊標

declare
  cursor cur_emp(p_empno number) is
    select e.ename, e.sal from emp e where e.empno = p_empno; --宣告遊標

  v_empno emp.empno%type := 7788;
  v_ename emp.ename%type;
  v_sal   emp.sal%type;
begin
  open cur_emp(v_empno); --開啟遊標
  --迴圈取出
  loop
    fetch cur_emp
      into v_ename, v_sal; --提取記錄
    exit when cur_emp%notfound;
    dbms_output.put_line(v_ename || '的工資是' || v_sal);
  end loop;
  close cur_emp; --關閉遊標
 
end;

2.6.3一次取出所有記錄

declare
  cursor cur_emp is
    select e.ename, e.sal from emp e where e.deptno = 10; --宣告遊標

  type it_ename is table of emp.ename%type index by binary_integer;
  type it_sal is table of emp.sal%type index by binary_integer;

  vit_ename it_ename;
  vit_sal   it_sal;
begin
  open cur_emp; --開啟遊標
  fetch cur_emp bulk collect
    into vit_ename, vit_sal; --一次性取出所有記錄
  close cur_emp; --關閉遊標

  for i in vit_ename.first .. vit_ename.last loop
    dbms_output.put_line(vit_ename(i) || '的工資是' || vit_sal(i));
  end loop;

end;

3.遊標變數

遊標變數是指向或者引用底層遊標的變數。顯示遊標已經為結果集的工作區指定了名字,而遊標變數只是指向這個工作區的引用。顯示遊標和隱式遊標都繫結到一個專門的查詢語句,而遊標變數可以是任何一個查詢語句,也可以是查詢語句的字串變數或者字面量。因為遊標變數的查詢語句可以是多個不相同的查詢,因此這種遊標稱為動態遊標。顯示遊標和隱式遊標稱為靜態遊標

3.1宣告REF CURSOR型別

TYPE cursor_type_name IS REF CURSOR [RETURN return_type];

其中,cursor_type_name是遊標型別的名字;return_type是該遊標型別返回的資料型別。

REF CURSOR型別可是強型別(帶有return)或者是弱型別(不帶return)。以下兩種宣告都是有效的宣告:

TYPE rc_emp is REF CURSOR RETURN emp%rowtype; --強型別

TYPE rc_emp is REF CURSOR;--弱型別

3.2宣告遊標變數

cursor_variable_name cursor_type_name;

其中,cursor_variable_name是遊標變數的名字;cursor_type_name是之前的REF CURSOR宣告的遊標型別的名字。

3.3開啟遊標變數

OPEN cursor_variable_name FOR select_statement;

其中,cursor_variable_name是遊標變數的名字; select_statement可以是一個直接的SQL語句,也可以一個SELECT語句的字串或者字面。如果select_statement是字串或者字面量,可以包含繫結變數的佔位符,並可以使用USING子句標識相應的佔位符值。

3.4提取資料

每次只提取一行資料:

FETCH  cursor_name

  INTO [(variable_name [, variable_name ]... | record_name )];

一次提取多行資料:

FETCH  cursor_name
  BULK COLLECT INTO [(collection_name [,collection_name...])];

3.5關閉顯示遊標

CLOSE cursor_name;

3.6遊標變數屬性

遊標變數屬性與顯示遊標屬性一樣,參見2.5

3.7遊標變數例子

3.7.1一次只取一行記錄

declare
  type rc_emp is ref cursor; --宣告ref遊標型別
  vrc_emp rc_emp; --宣告遊標變數
  v_ename emp.ename%type;
  v_sal   emp.sal%type;
begin
  open vrc_emp for
    select e.ename, e.sal from emp e where e.deptno = 10; --開啟遊標變數,並賦值。
  loop
    fetch vrc_emp
      into v_ename, v_sal; --提取記錄
    exit when vrc_emp%notfound;
    dbms_output.put_line(v_ename || '的工資是' || v_sal);
  end loop;
  close vrc_emp; ---關閉遊標變數

end;

3.7.2一次取出所有行記錄

declare
  type it_ename is table of emp.ename%type index by binary_integer;
  type it_sal is table of emp.sal%type index by binary_integer;
  type rc_emp is ref cursor; --宣告ref遊標型別\

  vrc_emp   rc_emp; --宣告遊標變數
  vit_ename it_ename;
  vit_sal   it_sal;
  v_ename   emp.ename%type;
  v_sal     emp.sal%type;
begin
  open vrc_emp for
    select e.ename, e.sal from emp e where e.deptno = 10; --開啟遊標變數,並賦值。
  fetch vrc_emp bulk collect
    into vit_ename, vit_sal; --一次性取出所有記錄
  close vrc_emp; --關閉遊標

  for i in vit_ename.first .. vit_ename.last loop
    dbms_output.put_line(vit_ename(i) || '的工資是' || vit_sal(i));
  end loop;

end;

3.7.3查詢語句是字串變數

declare
  type rc_emp is ref cursor; --宣告ref遊標型別
  vrc_emp  rc_emp; --宣告遊標變數
  v_ename  emp.ename%type;
  v_sal    emp.sal%type;
  v_deptno emp.deptno%type := 10;
  v_sql    clob;

begin
  v_sql := 'select e.ename, e.sal from emp e where e.deptno = :deptno';
  open vrc_emp for v_sql
    using v_deptno; --開啟遊標變數,並賦值。
  loop
    fetch vrc_emp
      into v_ename, v_sal; --提取記錄
    exit when vrc_emp%notfound;
    dbms_output.put_line(v_ename || '的工資是' || v_sal);
  end loop;
  close vrc_emp; ---關閉遊標變數

end;

4.遊標表示式

遊標表示式返回一個巢狀遊標,使用CUSOR操作符表示。可以使用遊標表示式從一個或者多個表中提取龐大的複雜的記錄集。

可以在下面的場合使用遊標表示式:

  • 顯示遊標宣告
  • REF CURSOR宣告和REF CURSOR變數
  • 動態SQL查詢

遊標表示式語法:

CURSOR (subquery)

當父遊標提取資料時,巢狀遊標就會隱式地開啟。巢狀遊標在以下這些時刻關閉:

  • 使用者顯示地關閉巢狀遊標
  • 父遊標再次執行
  • 父遊標關閉
  • 父遊標取消
  • 從父遊標提取資料時丟擲了異常。巢狀遊標會和父遊標一起關閉

使用遊標表示式,查詢各個部門的名稱、地址以及該部門下的員工:

declare
  cursor cur_dept_emp is
    select d.dname,
           d.loc,
           cursor (select * from emp e where e.deptno = d.deptno) nc_emp --巢狀遊標
      from dept d;

  type rc_emp is ref cursor return emp%rowtype;
  vrc_emp rc_emp;
  vrt_emp emp%rowtype;
  v_dname dept.dname%type;
  v_loc   dept.loc%type;

begin
  open cur_dept_emp;
  loop
    fetch cur_dept_emp
      into v_dname, v_loc, vrc_emp; --提取父遊標資料,並自動開啟巢狀遊標
    exit when cur_dept_emp%notfound;
    dbms_output.put_line(v_loc || '的' || v_dname || '部門的員工資訊:');
    loop
      fetch vrc_emp
        into vrt_emp; --從巢狀遊標提取資料
      exit when vrc_emp%notfound;
      dbms_output.put_line(vrt_emp.ename || '的工資' || vrt_emp.sal);
    end loop;
    dbms_output.put_line('');
  end loop;
  close cur_dept_emp; --關閉父遊標,同時也關閉了巢狀遊標

end;

5.遊標FOR迴圈

遊標FOR迴圈的迭代變數不需要事先宣告。這是一個%ROWTYPE記錄,其欄位名稱匹配查詢的列名,而且只能存在於迴圈中。資料庫自動開啟、提取、關閉遊標FOR迴圈。即使在迴圈中使用EXIT語句、GO語句或者丟擲異常,資料庫都會自動關閉該遊標。遊標FOR迴圈可以分為:隱式遊標FOR迴圈和顯示遊標FOR迴圈。

5.1隱式遊標FOR迴圈

begin
  --隱式遊標FOR迴圈
  for vrc_emp in (select * from emp e where e.deptno = 10) loop
    dbms_output.put_line(vrc_emp.ename || '的工資' || vrc_emp.sal);
  end loop;
end;

5.2顯示遊標FOR迴圈

declare
  cursor cur_emp is
    select * from emp e where e.deptno = 10;
begin
  --隱式遊標FOR迴圈
  for vrc_emp in cur_emp loop
    dbms_output.put_line(vrc_emp.ename || '的工資' || vrc_emp.sal);
  end loop;
end;

6.WHERE CURRENT OF語句

PL/SQL為遊標的UPDATE和DELETE語句提供了WHERE CURRENT OF語句。當你宣告一個在UPDATE或者DELETE語句的CURRENT OF子句引用的遊標時,必須使用FOR UPDATE語句獲取獨立的行級鎖。

要修改最新取出來的記錄的列:

UPDATE table_name

  SET set_clause

 WHERE CURRENT OF cursor_name;

要刪除最新取出的記錄:

DELETE FORM table_name WHERE CURRENT OF cursor_name;

declare
  cursor cur_emp is
    select * from emp for update nowait;
  vrt_emp emp%rowtype;
begin
  open cur_emp;
  loop
    fetch cur_emp
      into vrt_emp;
    exit when cur_emp%notfound;
    if vrt_emp.ename = 'SCOTT' then
      update emp e set e.sal = 6000 where current of cur_emp; --注意current of 後面子游標,而不是記錄
    end if;
    if vrt_emp.ename = 'CHICLEWU' then
      delete from emp where current of cur_emp;
    end if;
  end loop;
end;