1. 程式人生 > >【原】Oracle-pl/sql-遊標的使用

【原】Oracle-pl/sql-遊標的使用

(注:主要摘自尚矽谷oracle教程筆記)
在PL/SQL 程式中,對於處理多行記錄 的事務經常使用遊標來實現。

遊標概念

為了處理 SQL 語句,ORACLE 必須分配一個上下文的區域來處理所必需的資訊,其中包括要處理的行的數目,一個指向語句被分析以後的表示形式的指標以及查詢的活動集。
遊標是一個指向上下文的控制代碼或指標。通過遊標,PL/SQL 可以控制上下文區和處理語句時上下文會發生什麼事情。
對於不同的SQL 語句,遊標的使用情況不同:

SQL語句 遊標
非查詢語句 隱式的
結果是單行的查詢語句 隱式的或顯示的
結果是多行的查詢語句 顯示的

處理顯示遊標

1.顯式遊標處理
顯式遊標處理需四個 PL/SQL 步驟:

  • 定義遊標:就是定義一個遊標名,以及與其相對應的 SELECT 語句。
    格式:
CURSOR cursor_name[(parameter..)] IS select_statement;

遊標引數只能為輸入引數,其格式為:

parameter_name [in] datatype [{:= | DEFAULT} expression]

在指定資料型別時,不能使用長度約束。如 NUMBER(5)、CHAR(10) 等都是錯誤的。

  • 開啟遊標:就是執行遊標所對應的 SELECT 語句,將其查詢結果放入工作區,並且指標指向工作區的首部,標識遊標結果集合。如果遊標查詢語句中有 FOR UPDATE 選項,OPEN 語句還將鎖定資料表中的遊標結果集合對應的資料行。
    格式:
OPEN cursor_name;

在向遊標傳遞引數時,可以使用與函式引數相同的傳值方法,即位置表示法和名稱表示法。PL/SQL程式不能用OPEN語句重複開啟一個遊標。

  • 提取遊標資料:就是檢索結果集合中的資料行,放入指定的輸出變數中。格式:
 FETCH cursor_name INTO {variable_list | record_variable };
  • 對該記錄進行處理
  • 繼續處理,知道活動集合中沒有記錄
  • 關閉遊標:當提取和處理完成結果集合資料後,應及時關閉遊標,以釋放該遊標所佔用的系統資源,並使該遊標的工作區變成無效,不能再使用 FETCH 語句取其中資料。關閉後的遊標可以 使用 OPEN 語句重新開啟。
    格式:
CLOSE cursor_name;

注:定義的遊標不能有 INTO 子句

遊標屬性:

%FOUND 布林型屬性,當最近一次讀記錄時成功返回,則值為 TRUE
%NOTFOUND 布林型屬性,與%FOUND 相反
%ISOPEN 布林型屬性,當遊標已開啟時返回 TRUE
%ROWCOUNT 數字型屬性,返回已從遊標中讀取的記錄數

示例

要求: 打印出 80 部門的所有的員工的工資:salary: xxx

declare
  --1. 定義遊標
  cursor salary_cursor is select salary 
  	from employees where department_id = 80;
  v_salary employees.salary%type;
begin
 --2. 開啟遊標
 open salary_cursor;

 --3. 提取遊標
 fetch salary_cursor into v_salary;
 
 --4. 對遊標進行迴圈操作: 判斷遊標中是否有下一條記錄
while salary_cursor%found loop
      dbms_output.put_line('salary: ' || v_salary);
      fetch salary_cursor into v_salary;
end loop;  
 
 --5. 關閉遊標
 close  salary_cursor;
end;

要求: 打印出 80 部門的所有的員工的工資: Xxx 's salary is: xxx

declare
  cursor sal_cursor is select salary ,last_name 
  	from employees where department_id = 80;
  v_sal number(10);
  v_name varchar2(20);
begin
  open sal_cursor;
  fetch sal_cursor into v_sal,v_name;
  while sal_cursor%found loop
        dbms_output.put_line(v_name||'`s salary is '||v_sal);
        fetch sal_cursor into v_sal,v_name;
  end loop;
  
  close sal_cursor;
end;

打印出 manager_id 為 100 的員工的 last_name, email, salary 資訊(使用遊標, 記錄型別)

declare  
           --宣告遊標    
           cursor emp_cursor is select last_name, email, salary 
           	from employees where manager_id = 100;
           
           --宣告記錄型別
           type emp_record is record(
                name employees.last_name%type,
                email employees.email%type,
                salary employees.salary%type
           );
           
           -- 宣告記錄型別的變數
           v_emp_record emp_record;
begin
           --開啟遊標
           open emp_cursor;
           
           --提取遊標
           fetch emp_cursor into v_emp_record;
           
           --對遊標進行迴圈操作
           while emp_cursor%found loop
                  dbms_output.put_line(
                  	v_emp_record.name || ', ' 
                  	|| v_emp_record.email || ', ' 
                  	|| v_emp_record.salary
                   );                
                  fetch emp_cursor into v_emp_record;
           end loop;
           
           --關閉遊標
           close emp_cursor;
end;

遊標的FOR 迴圈
PL/SQL 語句提供了 遊標 FOR 迴圈語句,自動執行遊標的 OPEN、FETCH、CLOSE語句和迴圈語句的功能,當進入迴圈時,遊標 FOR 迴圈語句自動開啟遊標,並提取第一行遊標資料,當程式處理完當前所提取的資料而進入下一次迴圈時,遊標 FOR 迴圈語句自動提取下一行資料提供程式處理,當提取完結果集合中的所有資料行後結束迴圈,並自動關閉遊標。
格式:

FOR index_variable IN cursor_name LOOP
	-- 遊標資料處理程式碼
END LOOP;

其中:
index_variable 為遊標 FOR 迴圈語句隱含宣告的索引變數,該變數為記錄變數,其結構與遊標查詢語句返回的結構集合的結構相同。在程式中可以通過引用該索引記錄變數元素來讀取所提取的遊標資料,index_variable 中各元素的名稱與遊標查詢語句選擇列表中的所制定的列名相同。如果在遊標查詢語句的選擇列表中存在計算列,則必須為這些計算列指定別名後才能通過遊標 FOR 迴圈語句中的索引標量來訪問這些列資料。

注:不要在程式中對遊標進行人工操作,不要在程式中定義用於控制 FOR 迴圈的記錄。

(法二:使用for迴圈)

declare
      cursor emp_cursor is 
      select last_name,email,salary
      from employees
      where manager_id = 100;
begin
      for v_emp_record in emp_cursor loop
          dbms_output.put_line(
          v_emp_record.last_name||','
          ||v_emp_record.email
          ||','||v_emp_record.salary
       );
      end loop;
end;

利用遊標, 調整公司中員工的工資

工資範圍 調整基數
0 - 5000 5%
5000 - 10000 3%
10000 - 15000 2%
15000 - 1%
declare
    --定義遊標
    cursor emp_sal_cursor 
    	is select salary, employee_id from employees;
    
    --定義基數變數
    temp number(4, 2);
    
    --定義存放遊標值的變數
    v_sal employees.salary%type;
    v_id employees.employee_id%type;
begin
    --開啟遊標
    open emp_sal_cursor;
    
    --提取遊標
    fetch emp_sal_cursor into v_sal, v_id;
    
    --處理遊標的迴圈操作
    while emp_sal_cursor%found loop
          --判斷員工的工資, 執行 update 操作
          --dbms_output.put_line(v_id || ': ' || v_sal);
            
          if v_sal <= 5000 then
             temp := 0.05;
          elsif v_sal<= 10000 then
             temp := 0.03;   
          elsif v_sal <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if; 
          --dbms_output.put_line(v_id || ': ' || v_sal || ', ' || temp);
          update employees set salary = salary * (1 + temp) where employee_id = v_id; 
                  
          fetch emp_sal_cursor into v_sal, v_id;
    end loop;
    --關閉遊標
    close emp_sal_cursor;
end;

利用遊標 for 迴圈完成

declare
    --定義遊標
    cursor emp_sal_cursor 
    	is select salary, employee_id id from employees;
    --定義基數變數
    temp number(4, 2);
begin
    --處理遊標的迴圈操作
    for c in emp_sal_cursor loop
          --判斷員工的工資, 執行 update 操作
          --dbms_output.put_line(v_id || ': ' || v_sal);
            
          if c.salary <= 5000 then
             temp := 0.05;
          elsif c.salary <= 10000 then
             temp := 0.03;   
          elsif c.salary <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if;
          
          --dbms_output.put_line(
          	v_id || ': ' || v_sal || ', ' || temp);
          	
          update employees set salary = salary * (1 + temp) 
          	where employee_id = c.id;
    end loop;
end;

帶引數的遊標

declare
    --定義遊標
    cursor emp_sal_cursor(dept_id number, sal number) is 
           select salary + 1000 sal, employee_id id 
           from employees 
           where department_id = dept_id and salary > sal;
    
    --定義基數變數
    temp number(4, 2);
begin
    --處理遊標的迴圈操作
    for c in emp_sal_cursor(sal => 4000, dept_id => 80) loop
          --判斷員工的工資, 執行 update 操作
          --dbms_output.put_line(c.id || ': ' || c.sal);
          
          if c.sal <= 5000 then
             temp := 0.05;
          elsif c.sal <= 10000 then
             temp := 0.03;   
          elsif c.sal <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if;
          
          dbms_output.put_line(
          	c.sal || ': ' || c.id || ', ' || temp
          );
          --update employees set salary = salary * (1 + temp)
            where employee_id = c.id;
    end loop;
end;

PL/SQL 還允許在遊標 FOR 迴圈語句中使用子查詢來實現遊標的功能。

BEGIN
	for v_emp in (select last_name,salary from employees) loop
		dbms_output.put_line(
			v_emp.last_name ||
			',' || v_emp.salary 
		);
		end loop;
	end;

處理隱式遊標
顯式遊標主要是用於對查詢語句的處理,尤其是在查詢結果為多條記錄的情況下,而對於非查詢語句,如修改,刪除操作,則由 ORACLE 系統自動地為這些操作設定遊標並建立其工作區,這些由系統隱含建立的遊標稱為隱式遊標,隱式遊標的名字為SQL,這是由 ORACLE 系統定義的。對於隱式遊標的操作,如定義、開啟、取值及關閉操作,都由 ORACLE 系統自動地完成,無需使用者進行處理。使用者只能通過隱式遊標的相關屬性,來完成相應的操作。在隱式遊標的工作區中,所存放的資料是與使用者自定義的顯示遊標無關的、最新處理的一條SQL 語句所包含的資料。
格式呼叫:

SQL%

隱式遊標屬性

SQL%FOUND 布林型屬性,當最近一次讀記錄時成功返回,則值為TRUE
SQL%NOTFOUND 布林型屬性,與SQL%FOUND相反
SQL%ROWCOUNT 數字型屬性,返回已從遊標中讀取得記錄數
SQL%ISOPEN 布林型屬性,取值總是FALSE。SQL命令執行完畢立即關閉隱式遊標。

隱式遊標: 更新指定員工 salary(漲工資 10),如果該員工沒有找到,則列印”查無此人” 資訊

begin
         update employees set salary = salary + 10 
         where employee_id = 1005;
         
         if sql%notfound then
            dbms_output.put_line('查無此人!');
         end if;
end;