1. 程式人生 > >遊標的概念及應用

遊標的概念及應用

遊標的概念: 

    遊標是SQL的一個記憶體工作區,由系統或使用者以變數的形式定義。遊標的作用就是用於臨時儲存從資料庫中提取的資料塊。在某些情況下,需要把資料從存放在磁碟的表中調到計算機記憶體中進行處理,最後將處理結果顯示出來或最終寫回資料庫。這樣資料處理的速度才會提高,否則頻繁的磁碟資料交換會降低效率。 
遊標有兩種型別:顯式遊標和隱式遊標。在前述程式中用到的SELECT...INTO...查詢語句,一次只能從資料庫中提取一行資料,對於這種形式的查詢和DML操作,系統都會使用一個隱式遊標。但是如果要提取多行資料,就要由程式設計師定義一個顯式遊標,並通過與遊標有關的語句進行處理。顯式遊標對應一個返回結果為多行多列的SELECT語句。 
遊標一旦開啟,資料就從資料庫中傳送到遊標變數中,然後應用程式再從遊標變數中分解出需要的資料,並進行處理。 
隱式遊標 
如前所述,DML操作和單行SELECT語句會使用隱式遊標,它們是: 
* 插入操作:INSERT。 
* 更新操作:UPDATE。 
* 刪除操作:DELETE。 
* 單行查詢操作:SELECT ... INTO ...。 
當系統使用一個隱式遊標時,可以通過隱式遊標的屬性來了解操作的狀態和結果,進而控制程式的流程。隱式遊標可以使用名字SQL來訪問,但要注意,通過SQL遊標名總是隻能訪問前一個DML操作或單行SELECT操作的遊標屬性。所以通常在剛剛執行完操作之後,立即使用SQL遊標名來訪問屬性。遊標的屬性有四種,如下所示。 

Sql程式碼 

  1. 隱式遊標的屬性 返回值型別   意    義   
  2. SQL%ROWCOUNT    整型  代表DML語句成功執行的資料行數   
  3. SQL%FOUND   布林型 值為TRUE代表插入、刪除、更新或單行查詢操作成功   
  4. SQL%NOTFOUND    布林型 與SQL%FOUND屬性返回值相反   
  5. SQL%ISOPEN  布林型 DML執行過程中為真,結束後為假  


【訓練1】 使用隱式遊標的屬性,判斷對僱員工資的修改是否成功。 
步驟1:輸入和執行以下程式: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2.         BEGIN  
  3.         UPDATE emp SET sal=sal+100 WHERE empno=1234;   
  4.          IF SQL%FOUND THEN    
  5.         DBMS_OUTPUT.PUT_LINE('成功修改僱員工資!');   
  6.         COMMIT;    
  7.         ELSE  
  8.         DBMS_OUTPUT.PUT_LINE('修改僱員工資失敗!');   
  9.          END IF;    
  10.         END;  


執行結果為: 

Sql程式碼 

  1. 修改僱員工資失敗!   
  2.         PL/SQL 過程已成功完成。  


步驟2:將僱員編號1234改為7788,重新執行以上程式: 
執行結果為: 

Sql程式碼 

  1. 成功修改僱員工資!   
  2.         PL/SQL 過程已成功完成。  


說明:本例中,通過SQL%FOUND屬性判斷修改是否成功,並給出相應資訊。 
顯式遊標 
遊標的定義和操作 
遊標的使用分成以下4個步驟。 
1.宣告遊標 
在DECLEAR部分按以下格式宣告遊標: 
CURSOR 遊標名[(引數1 資料型別[,引數2 資料型別...])] 
IS SELECT語句; 
引數是可選部分,所定義的引數可以出現在SELECT語句的WHERE子句中。如果定義了引數,則必須在開啟遊標時傳遞相應的實際引數。 
SELECT語句是對錶或檢視的查詢語句,甚至也可以是聯合查詢。可以帶WHERE條件、ORDER BY或GROUP BY等子句,但不能使用INTO子句。在SELECT語句中可以使用在定義遊標之前定義的變數。 
2.開啟遊標 
在可執行部分,按以下格式開啟遊標: 
OPEN 遊標名[(實際引數1[,實際引數2...])]; 
開啟遊標時,SELECT語句的查詢結果就被傳送到了遊標工作區。 
3.提取資料 
在可執行部分,按以下格式將遊標工作區中的資料取到變數中。提取操作必須在開啟遊標之後進行。 
FETCH 遊標名 INTO 變數名1[,變數名2...]; 
或 
FETCH 遊標名 INTO 記錄變數; 
遊標開啟後有一個指標指向資料區,FETCH語句一次返回指標所指的一行資料,要返回多行需重複執行,可以使用迴圈語句來實現。控制迴圈可以通過判斷遊標的屬性來進行。 
下面對這兩種格式進行說明: 
第一種格式中的變數名是用來從遊標中接收資料的變數,需要事先定義。變數的個數和型別應與SELECT語句中的欄位變數的個數和型別一致。 
第二種格式一次將一行資料取到記錄變數中,需要使用%ROWTYPE事先定義記錄變數,這種形式使用起來比較方便,不必分別定義和使用多個變數。 
定義記錄變數的方法如下: 
變數名 表名|遊標名%ROWTYPE; 
其中的表必須存在,遊標名也必須先定義。 
4.關閉遊標 
CLOSE 遊標名; 
顯式遊標開啟後,必須顯式地關閉。遊標一旦關閉,遊標佔用的資源就被釋放,遊標變成無效,必須重新開啟才能使用。 
以下是使用顯式遊標的一個簡單練習。 
【訓練1】  用遊標提取emp表中7788僱員的名稱和職務。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE    
  3.          v_ename VARCHAR2(10);   
  4.          v_job VARCHAR2(10);   
  5.          CURSOR emp_cursor IS    
  6.          SELECT ename,job FROM emp WHERE empno=7788;   
  7.          BEGIN  
  8.      OPEN emp_cursor;   
  9.     FETCH emp_cursor INTO v_ename,v_job;   
  10.         DBMS_OUTPUT.PUT_LINE(v_ename||','||v_job);   
  11.         CLOSE emp_cursor;   
  12.         END;  


執行結果為: 

Sql程式碼 

  1. SCOTT,ANALYST   
  2.         PL/SQL 過程已成功完成。   


說明:該程式通過定義遊標emp_cursor,提取並顯示僱員7788的名稱和職務。 
作為對以上例子的改進,在以下訓練中採用了記錄變數。 
【訓練2】  用遊標提取emp表中7788僱員的姓名、職務和工資。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE  
  3.          CURSOR emp_cursor IS  SELECT ename,job,sal FROM emp WHERE empno=7788;   
  4.          emp_record emp_cursor%ROWTYPE;   
  5.         BEGIN  
  6. OPEN emp_cursor;       
  7.         FETCH emp_cursor INTO emp_record;   
  8.            DBMS_OUTPUT.PUT_LINE(emp_record.ename||','|| emp_record.job||','|| emp_record.sal);   
  9.          CLOSE emp_cursor;   
  10.         END;  


執行結果為: 

Sql程式碼 

  1. SCOTT,ANALYST,3000   
  2.         PL/SQL 過程已成功完成。   


說明:例項中使用記錄變數來接收資料,記錄變數由遊標變數定義,需要出現在遊標定義之後。 
注意:可通過以下形式獲得記錄變數的內容: 
記錄變數名.欄位名。 
【訓練3】  顯示工資最高的前3名僱員的名稱和工資。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE  
  3.          V_ename VARCHAR2(10);   
  4.         V_sal NUMBER(5);   
  5.         CURSOR emp_cursor IS  SELECT ename,sal FROM emp ORDER BY sal DESC;   
  6.         BEGIN  
  7.          OPEN emp_cursor;   
  8.          FOR I IN 1..3 LOOP   
  9.            FETCH emp_cursor INTO v_ename,v_sal;   
  10.          DBMS_OUTPUT.PUT_LINE(v_ename||','||v_sal);   
  11.           END LOOP;   
  12.          CLOSE emp_cursor;   
  13.          END;  


執行結果為: 

Sql程式碼 

  1. KING,5000   
  2.      SCOTT,3000   
  3.      FORD,3000   
  4.      PL/SQL 過程已成功完成。  


  說明:該程式在遊標定義中使用了ORDER BY子句進行排序,並使用迴圈語句來提取多行資料。 
遊標迴圈 
【訓練1】  使用特殊的FOR迴圈形式顯示全部僱員的編號和名稱。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3.   CURSOR emp_cursor IS    
  4.   SELECT empno, ename FROM emp;   
  5. BEGIN  
  6. FOR Emp_record IN emp_cursor LOOP      
  7.     DBMS_OUTPUT.PUT_LINE(Emp_record.empno|| Emp_record.ename);   
  8.     END LOOP;   
  9.     END;  


執行結果為: 

Sql程式碼 

  1. 7369SMITH   
  2. 7499ALLEN   
  3. 7521WARD   
  4. 7566JONES   
  5.          PL/SQL 過程已成功完成。  


  說明:可以看到該迴圈形式非常簡單,隱含了記錄變數的定義、遊標的開啟、提取和關閉過程。Emp_record為隱含定義的記錄變數,迴圈的執行次數與遊標取得的資料的行數相一致。 
【訓練2】  另一種形式的遊標迴圈。 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2. BEGIN  
  3.  FOR re IN (SELECT ename FROM EMP)  LOOP   
  4.   DBMS_OUTPUT.PUT_LINE(re.ename)   
  5.  END LOOP;   
  6. END;  


執行結果為: 

Sql程式碼 

  1. SMITH   
  2. ALLEN   
  3. WARD   
  4. JONES  


    說明:該種形式更為簡單,省略了遊標的定義,遊標的SELECT查詢語句在迴圈中直接出現。 
顯式遊標屬性 
雖然可以使用前面的形式獲得遊標資料,但是在遊標定義以後使用它的一些屬性來進行結構控制是一種更為靈活的方法。顯式遊標的屬性如下所示。 

Sql程式碼 

  1. 遊標的屬性   返回值型別   意    義   
  2. %ROWCOUNT   整型  獲得FETCH語句返回的資料行數   
  3. %FOUND  布林型 最近的FETCH語句返回一行資料則為真,否則為假   
  4. %NOTFOUND   布林型 與%FOUND屬性返回值相反   
  5. %ISOPEN 布林型 遊標已經開啟時值為真,否則為假  


可按照以下形式取得遊標的屬性: 
遊標名%屬性 
要判斷遊標emp_cursor是否處於開啟狀態,可以使用屬性emp_cursor%ISOPEN。如果遊標已經開啟,則返回值為“真”,否則為“假”。具體可參照以下的訓練。 
【訓練1】  使用遊標的屬性練習。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3.   V_ename VARCHAR2(10);   
  4.   CURSOR emp_cursor IS    
  5.   SELECT ename FROM emp;   
  6. BEGIN  
  7.  OPEN emp_cursor;   
  8.  IF emp_cursor%ISOPEN THEN  
  9. LOOP   
  10.    FETCH emp_cursor INTO v_ename;   
  11.    EXIT WHEN emp_cursor%NOTFOUND;   
  12.    DBMS_OUTPUT.PUT_LINE(to_char(emp_cursor%ROWCOUNT)||'-'||v_ename);   
  13.   END LOOP;   
  14.  ELSE  
  15.   DBMS_OUTPUT.PUT_LINE('使用者資訊:遊標沒有開啟!');   
  16.  END IF;   
  17.  CLOSE  emp_cursor;   
  18. END;  


執行結果為: 

Sql程式碼 

  1. 1-SMITH   
  2. 2-ALLEN   
  3. 3-WARD   
  4.  PL/SQL 過程已成功完成。  


    說明:本例使用emp_cursor%ISOPEN判斷遊標是否開啟;使用emp_cursor%ROWCOUNT獲得到目前為止FETCH語句返回的資料行數並輸出;使用迴圈來獲取資料,在迴圈體中使用FETCH語句;使用emp_cursor%NOTFOUND判斷FETCH語句是否成功執行,當FETCH語句失敗時說明資料已經取完,退出迴圈。 
【練習1】去掉OPEN emp_cursor;語句,重新執行以上程式。 
遊標引數的傳遞 
 【訓練1】  帶引數的遊標。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE  
  3.             V_empno NUMBER(5);   
  4.             V_ename VARCHAR2(10);   
  5.             CURSOR  emp_cursor(p_deptno NUMBER,     p_job VARCHAR2) IS  
  6.             SELECT  empno, ename FROM emp   
  7.             WHERE   deptno = p_deptno AND job = p_job;   
  8. BEGIN  
  9.      OPEN emp_cursor(10, 'CLERK');   
  10.     LOOP   
  11.      FETCH emp_cursor INTO v_empno,v_ename;   
  12.      EXIT WHEN emp_cursor%NOTFOUND;   
  13.      DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);   
  14.       END LOOP;   
  15.     END;  


執行結果為: 

Sql程式碼 

  1. 7934,MILLER   
  2.         PL/SQL 過程已成功完成。  


說明:遊標emp_cursor定義了兩個引數:p_deptno代表部門編號,p_job代表職務。語句OPEN emp_cursor(10, 'CLERK')傳遞了兩個引數值給遊標,即部門為10、職務為CLERK,所以遊標查詢的內容是部門10的職務為CLERK的僱員。迴圈部分用於顯示查詢的內容。 
【練習1】修改Open語句的引數:部門號為20、職務為ANALYST,並重新執行。 
也可以通過變數向遊標傳遞引數,但變數需要先於遊標定義,並在遊標開啟之前賦值。對以上例子重新改動如下: 
  【訓練2】  通過變數傳遞引數給遊標。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE  
  3.         v_empno NUMBER(5);   
  4.         v_ename VARCHAR2(10);   
  5.         v_deptno NUMBER(5);   
  6. v_job VARCHAR2(10);   
  7.          CURSOR emp_cursor IS  
  8.             SELECT empno, ename FROM emp   
  9.             WHERE   deptno = v_deptno AND job = v_job;   
  10.         BEGIN  
  11.          v_deptno:=10;   
  12.          v_job:='CLERK';   
  13.          OPEN emp_cursor;   
  14.         LOOP   
  15.          FETCH emp_cursor INTO v_empno,v_ename;   
  16.            EXIT WHEN emp_cursor%NOTFOUND;   
  17. DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);   
  18.          END LOOP;   
  19.         END;  


執行結果為: 

Sql程式碼 

  1. 7934,MILLER   
  2.         PL/SQL 過程已成功完成。  


說明:該程式與前一程式實現相同的功能。 
動態SELECT語句和動態遊標的用法 
Oracle支援動態SELECT語句和動態遊標,動態的方法大大擴充套件了程式設計的能力。 
對於查詢結果為一行的SELECT語句,可以用動態生成查詢語句字串的方法,在程式執行階段臨時地生成並執行,語法是: 
execute immediate 查詢語句字串 into 變數1[,變數2...]; 
以下是一個動態生成SELECT語句的例子。 
【訓練1】  動態SELECT查詢。 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2.         DECLARE    
  3.         str varchar2(100);   
  4.         v_ename varchar2(10);   
  5.         begin  
  6.         str:='select ename from scott.emp where empno=7788';   
  7.         execute immediate str into v_ename;    
  8.         dbms_output.put_line(v_ename);   
  9.         END;   


執行結果為: 

Sql程式碼 

  1. SCOTT   
  2.         PL/SQL 過程已成功完成。  


說明:SELECT...INTO...語句存放在STR字串中,通過EXECUTE語句執行。 
在變數宣告部分定義的遊標是靜態的,不能在程式執行過程中修改。雖然可以通過引數傳遞來取得不同的資料,但還是有很大的侷限性。通過採用動態遊標,可以在程式執行階段隨時生成一個查詢語句作為遊標。要使用動態遊標需要先定義一個遊標型別,然後宣告一個遊標變數,遊標對應的查詢語句可以在程式的執行過程中動態地說明。 
定義遊標型別的語句如下: 
TYPE 遊標型別名 REF CURSOR; 
宣告遊標變數的語句如下: 
遊標變數名 遊標型別名; 
在可執行部分可以如下形式開啟一個動態遊標: 
OPEN 遊標變數名 FOR 查詢語句字串; 
【訓練2】  按名字中包含的字母順序分組顯示僱員資訊。 
輸入並執行以下程式: 

Sql程式碼 

  1. declare    
  2.  type cur_type is ref cursor;   
  3.  cur cur_type;   
  4.  rec scott.emp%rowtype;   
  5.  str varchar2(50);   
  6.  letter char:= 'A';   
  7. begin  
  8.         loop           
  9.          str:= 'select ename from emp where ename like ''%'||letter||'%''';   
  10.          open cur for str;   
  11.          dbms_output.put_line('包含字母'||letter||'的名字:');   
  12.           loop   
  13.          fetch cur into rec.ename;   
  14.          exit when cur%notfound;   
  15.         dbms_output.put_line(rec.ename);   
  16. end loop;   
  17.   exit when letter='Z';   
  18.   letter:=chr(ascii(letter)+1);   
  19.  end loop;   
  20. end;  


執行結果為: 

Sql程式碼 

  1. 包含字母A的名字:   
  2. ALLEN   
  3. WARD   
  4. MARTIN   
  5. BLAKE   
  6. CLARK   
  7. ADAMS   
  8. JAMES   
  9. 包含字母B的名字:   
  10. BLAKE   
  11. 包含字母C的名字:   
  12. CLARK   
  13. SCOTT  


說明:使用了二重迴圈,在外迴圈體中,動態生成遊標的SELECT語句,然後開啟。通過語句letter:=chr(ascii(letter)+1)可獲得字母表中的下一個字母。 

異常處理 
錯誤處理 
錯誤處理部分位於程式的可執行部分之後,是由WHEN語句引導的多個分支構成的。錯誤處理的語法如下: 
EXCEPTION 
WHEN 錯誤1[OR 錯誤2] THEN 
語句序列1; 
WHEN 錯誤3[OR 錯誤4] THEN 
語句序列2; 
WHEN OTHERS 
語句序列n; 
END; 
其中: 
錯誤是在標準包中由系統預定義的標準錯誤,或是由使用者在程式的說明部分自定義的錯誤,參見下一節系統預定義的錯誤型別。 
語句序列就是不同分支的錯誤處理部分。 
凡是出現在WHEN後面的錯誤都是可以捕捉到的錯誤,其他未被捕捉到的錯誤,將在WHEN OTHERS部分進行統一處理,OTHENS必須是EXCEPTION部分的最後一個錯誤處理分支。如要在該分支中進一步判斷錯誤種類,可以通過使用預定義函式SQLCODE( )和SQLERRM( )來獲得系統錯誤號和錯誤資訊。 
如果在程式的子塊中發生了錯誤,但子塊沒有錯誤處理部分,則錯誤會傳遞到主程式中。 
下面是由於查詢編號錯誤而引起系統預定義異常的例子。 
【訓練1】  查詢編號為1234的僱員名字。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. v_name VARCHAR2(10);   
  4. BEGIN  
  5.    SELECT   ename   
  6.    INTO     v_name   
  7.    FROM     emp   
  8.    WHERE    empno = 1234;   
  9. DBMS_OUTPUT.PUT_LINE('該僱員名字為:'|| v_name);   
  10. EXCEPTION   
  11.   WHEN NO_DATA_FOUND THEN  
  12.     DBMS_OUTPUT.PUT_LINE('編號錯誤,沒有找到相應僱員!');   
  13.   WHEN OTHERS THEN  
  14.     DBMS_OUTPUT.PUT_LINE('發生其他錯誤!');   
  15. END;  


執行結果為: 

Sql程式碼 

  1. 編號錯誤,沒有找到相應僱員!   
  2.         PL/SQL 過程已成功完成。  


說明:在以上查詢中,因為編號為1234的僱員不存在,所以將發生型別為“NO_DATA_ 
FOUND”的異常。“NO_DATA_FOUND”是系統預定義的錯誤型別,EXCEPTION部分下的WHEN語句將捕捉到該異常,並執行相應程式碼部分。在本例中,輸出使用者自定義的錯誤資訊“編號錯誤,沒有找到相應僱員!”。如果發生其他型別的錯誤,將執行OTHERS條件下的程式碼部分,顯示“發生其他錯誤!”。 
【訓練2】  由程式程式碼顯示系統錯誤。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. v_temp NUMBER(5):=1;   
  4. BEGIN  
  5. v_temp:=v_temp/0;   
  6. EXCEPTION   
  7.   WHEN OTHERS THEN  
  8. DBMS_OUTPUT.PUT_LINE('發生系統錯誤!');   
  9.     DBMS_OUTPUT.PUT_LINE('錯誤程式碼:'|| SQLCODE( ));   
  10.     DBMS_OUTPUT.PUT_LINE('錯誤資訊:' ||SQLERRM( ));   
  11.         END;  


執行結果為: 

Sql程式碼 

  1. 發生系統錯誤!   
  2.         錯誤程式碼:?1476   
  3.         錯誤資訊:ORA-01476: 除數為 0   
  4.         PL/SQL 過程已成功完成。  


說明:程式執行中發生除零錯誤,由WHEN OTHERS捕捉到,執行使用者自己的輸出語句顯示錯誤資訊,然後正常結束。在錯誤處理部分使用了預定義函式SQLCODE( )和SQLERRM( )來進一步獲得錯誤的程式碼和種類資訊。 
預定義錯誤 
Oracle的系統錯誤很多,但只有一部分常見錯誤在標準包中予以定義。定義的錯誤可以在EXCEPTION部分通過標準的錯誤名來進行判斷,並進行異常處理。常見的系統預定義異常如下所示。 

Sql程式碼 

  1. 錯 誤 名 稱 錯誤程式碼    錯 誤 含 義   
  2. CURSOR_ALREADY_OPEN ORA_06511   試圖開啟已經開啟的遊標   
  3. INVALID_CURSOR  ORA_01001   試圖使用沒有開啟的遊標   
  4. DUP_VAL_ON_INDEX    ORA_00001   儲存重複值到惟一索引約束的列中   
  5. ZERO_DIVIDE ORA_01476   發生除數為零的除法錯誤   
  6. INVALID_NUMBER  ORA_01722   試圖對無效字元進行數值轉換   
  7. ROWTYPE_MISMATCH    ORA_06504   主變數和遊標的型別不相容   
  8. VALUE_ERROR ORA_06502   轉換、截斷或算術運算髮生錯誤   
  9. TOO_MANY_ROWS   ORA_01422   SELECT…INTO…語句返回多於一行的資料   
  10. NO_DATA_FOUND   ORA_01403   SELECT…INTO…語句沒有資料返回   
  11. TIMEOUT_ON_RESOURCE ORA_00051   等待資源時發生超時錯誤   
  12. TRANSACTION_BACKED_OUT  ORA_00060   由於死鎖,提交失敗   
  13. STORAGE_ERROR   ORA_06500   發生記憶體錯誤   
  14. PROGRAM_ERROR   ORA_06501   發生PL/SQL內部錯誤   
  15. NOT_LOGGED_ON   ORA_01012   試圖操作未連線的資料庫   
  16. LOGIN_DENIED    ORA_01017   在連線時提供了無效使用者名稱或口令  


比如,如果程式向表的主鍵列插入重複值,則將發生DUP_VAL_ON_INDEX錯誤。 
如果一個系統錯誤沒有在標準包中定義,則需要在說明部分定義,語法如下: 
錯誤名 EXCEPTION; 
定義後使用PRAGMA EXCEPTION_INIT來將一個定義的錯誤同一個特別的Oracle錯誤程式碼相關聯,就可以同系統預定義的錯誤一樣使用了。語法如下: 
PRAGMA EXCEPTION_INIT(錯誤名,- 錯誤程式碼); 
【訓練1】  定義新的系統錯誤型別。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2.         DECLARE  
  3.         V_ENAME VARCHAR2(10);   
  4.         NULL_INSERT_ERROR EXCEPTION;   
  5.         PRAGMA EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);   
  6.         BEGIN  
  7.         INSERT INTO EMP(EMPNO) VALUES(NULL);   
  8. EXCEPTION   
  9. WHEN NULL_INSERT_ERROR THEN  
  10.     DBMS_OUTPUT.PUT_LINE('無法插入NULL值!');   
  11.   WHEN OTHERS  THEN  
  12.     DBMS_OUTPUT.PUT_LINE('發生其他系統錯誤!');   
  13. END;  


執行結果為: 

Sql程式碼 

  1. 無法插入NULL值!   
  2.         PL/SQL 過程已成功完成。  


  說明:NULL_INSERT_ERROR是自定義異常,同系統錯誤1400相關聯。 
自定義異常 
程式設計者可以利用引發異常的機制來進行程式設計,自己定義異常型別。可以在宣告部分定義新的異常型別,定義的語法是: 
錯誤名 EXCEPTION; 
使用者定義的錯誤不能由系統來觸發,必須由程式顯式地觸發,觸發的語法是: 
RAISE 錯誤名; 
RAISE也可以用來引發模擬系統錯誤,比如,RAISE ZERO_DIVIDE將引發模擬的除零錯誤。 
使用RAISE_APPLICATION_ERROR函式也可以引發異常。該函式要傳遞兩個引數,第一個是使用者自定義的錯誤編號,第二個引數是使用者自定義的錯誤資訊。使用該函式引發的異常的編號應該在20 000和20 999之間選擇。 
自定義異常處理錯誤的方式同前。 
【訓練1】  插入新僱員,限定插入僱員的編號在7000~8000之間。 

Java程式碼 

  1. SET SERVEROUTPUT ON   
  2. DECLARE   
  3. new_no NUMBER(10);   
  4. new_excp1 EXCEPTION;   
  5. new_excp2 EXCEPTION;   
  6. BEGIN   
  7. new_no:=6789;   
  8. INSERT INTO emp(empno,ename)   
  9.   VALUES(new_no, '小鄭');   
  10.   IF new_no<7000 THEN   
  11.     RAISE new_excp1;   
  12.   END IF;   
  13.   IF new_no>8000 THEN   
  14.     RAISE new_excp2;   
  15.   END IF;   
  16.   COMMIT;   
  17. EXCEPTION   
  18. WHEN new_excp1  THEN   
  19.     ROLLBACK;   
  20.     DBMS_OUTPUT.PUT_LINE('僱員編號小於7000的下限!');   
  21.     WHEN new_excp2  THEN   
  22.     ROLLBACK;   
  23.     DBMS_OUTPUT.PUT_LINE('僱員編號超過8000的上限!');   
  24.     END;  


執行結果為: 
僱員編號小於7000的下限! 
PL/SQL 過程已成功完成。 
說明:在此例中,自定義了兩個異常:new_excp1和new_excp2,分別代表編號小於7000和編號大於8000的錯誤。在程式中通過判斷編號大小,產生對應的異常,並在異常處理部分回退插入操作,然後顯示相應的錯誤資訊。 
【訓練2】  使用RAISE_APPLICATION_ERROR函式引發系統異常。 

Sql程式碼 

  1. SET SERVEROUTPUT ON  
  2. DECLARE  
  3. New_no NUMBER(10);   
  4. BEGIN  
  5.   New_no:=6789;   
  6.  INSERT INTO    emp(empno,ename)   
  7.   VALUES(new_no, 'JAMES');   
  8. IF new_no<7000 THEN  
  9.     ROLLBACK;   
  10.     RAISE_APPLICATION_ERROR(-20001, '編號小於7000的下限!');   
  11.   END IF;   
  12.   IF new_no>8000 THEN  
  13.     ROLLBACK;   
  14.     RAISE_APPLICATION_ERROR (-20002, '編號大於8000的下限!');   
  15.   END IF;   
  16. END;  


執行結果為: 

Sql程式碼 

  1. DECLARE  
  2.         *   
  3.         ERROR 位於第 1 行:   
  4.         ORA-20001: 編號小於7000的下限!   
  5.         ORA-06512: 在line 9  


  說明:在本訓練中,使用RAISE_APPLICATION_ERROR引發自定義異常,並以系統錯誤的方式進行顯示。錯誤編號為20001和20002。 
注意:同上一個訓練比較,此種方法不需要事先定義異常,可直接引發。 
可以參考下面的程式片斷將出錯資訊記錄到表中,其中,errors為記錄錯誤資訊的表,SQLCODE為發生異常的錯誤編號,SQLERRM為發生異常的錯誤資訊。 
DECLARE 
  v_error_code      NUMBER; 
  v_error_message   VARCHAR2(255); 
BEGIN 
... 
EXCEPTION 
... 
WHEN OTHERS THEN 
    v_error_code := SQLCODE ; 
    v_error_message := SQLERRM ; 
    INSERT INTO errors 
    VALUES(v_error_code, v_error_message); 
END; 
  【練習1】修改僱員的工資,通過引發異常控制修改範圍在600~6000之間。 
階段訓練 
【訓練1】  將僱員從一個表複製到另一個表。 
步驟1:建立一個結構同EMP表一樣的新表EMP1: 
CREATE TABLE emp1 AS SELECT * FROM SCOTT.EMP WHERE 1=2; 
步驟2:通過指定僱員編號,將僱員由EMP表移動到EMP1表: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2. DECLARE  
  3. v_empno NUMBER(5):=7788;   
  4. emp_rec emp%ROWTYPE;   
  5. BEGIN  
  6.  SELECT * INTO emp_rec FROM emp WHERE empno=v_empno;   
  7.  DELETE FROM emp WHERE empno=v_empno;   
  8. INSERT INTO emp1 VALUES emp_rec;   
  9.  IF SQL%FOUND THEN  
  10.   COMMIT;   
  11.   DBMS_OUTPUT.PUT_LINE('僱員複製成功!');   
  12.  ELSE    
  13.   ROLLBACK;   
  14.   DBMS_OUTPUT.PUT_LINE('僱員複製失敗!');   
  15.  END IF;   
  16. END;  


執行結果為: 
僱員複製成功! 
PL/SQL 過程已成功完成。 
步驟2:顯示覆制結果: 
SELECT empno,ename,job FROM emp1; 
執行結果為: 
 

Sql程式碼 

  1. EMPNO ENAME      JOB   
  2. ------------- -------------- ----------------   
  3.     7788  SCOTT      ANALYST  


說明:emp_rec變數是根據emp表定義的記錄變數,SELECT...INTO...語句將整個記錄傳給該變數。INSERT語句將整個記錄變數插入emp1表,如果插入成功(SQL%FOUND為真),則提交事務,否則回滾撤銷事務。試修改僱員編號為7902,重新執行以上程式。 
【訓練2】  輸出僱員工資,僱員工資用不同高度的*表示。 
輸入並執行以下程式: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2. BEGIN  
  3.  FOR re IN (SELECT ename,sal FROM EMP)  LOOP   
  4.   DBMS_OUTPUT.PUT_LINE(rpad(re.ename,12,' ')||rpad('*',re.sal/100,'*'));   
  5.  END LOOP;   
  6. END;  


輸出結果為: 

Sql程式碼 

  1. SMITH       ********   
  2. ALLEN           ****************   
  3. WARD        *************   
  4. JONES           ******************************   
  5. MARTIN      *************   
  6. BLAKE       *****************************   
  7. CLARK           *****************************   
  8. SCOTT           ******************************   
  9. KING            **************************************************   
  10. TURNER      ***************   
  11. ADAMS       ***********   
  12. JAMES           **********   
  13. FORD            ******************************   
  14. MILLER          *************   
  15.          執行結果為:   
  16.         PL/SQL 過程已成功完成。  


  說明:第一個rpad函式產生對齊效果,第二個rpad函式根據工資額產生不同數目的*。該程式採用了隱式的簡略遊標迴圈形式。 
【訓練3】  編寫程式,格式化輸出部門資訊。 
輸入並執行如下程式: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2.         DECLARE  
  3.          v_count number:=0;   
  4.          CURSOR dept_cursor IS SELECT * FROM dept;   
  5.         BEGIN  
  6.           DBMS_OUTPUT.PUT_LINE('部門列表');   
  7. DBMS_OUTPUT.PUT_LINE('---------------------------------');   
  8.          FOR Dept_record IN dept_cursor LOOP      
  9.          DBMS_OUTPUT.PUT_LINE('部門編號:'|| Dept_record.deptno);   
  10.          DBMS_OUTPUT.PUT_LINE('部門名稱:'|| Dept_record.dname);   
  11.             DBMS_OUTPUT.PUT_LINE('所在城市:'|| Dept_record.loc);   
  12. DBMS_OUTPUT.PUT_LINE('---------------------------------');   
  13.       v_count:= v_count+1;   
  14.         END LOOP;   
  15.          DBMS_OUTPUT.PUT_LINE('共有'||to_char(v_count)||'個部門!');   
  16.         END;  


輸出結果為: 

Sql程式碼 

  1. 部門列表   
  2. ------------------------------------   
  3. 部門編號:10   
  4. 部門名稱:ACCOUNTING   
  5. 所在城市:NEW YORK   
  6. ------------------------------------   
  7. 部門編號:20   
  8. 部門名稱:RESEARCH   
  9. 所在城市:DALLAS   
  10. ...   
  11. 共有4個部門!   
  12. PL/SQL 過程已成功完成。  


  說明:該程式中將欄位內容垂直排列。V_count變數記錄迴圈次數,即部門個數。 
【訓練4】  已知每個部門有一個經理,編寫程式,統計輸出部門名稱、部門總人數、總工資和部門經理。 
輸入並執行如下程式: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2. DECLARE  
  3.  v_deptno number(8);   
  4.  v_count number(3);   
  5.  v_sumsal number(6);   
  6.  v_dname  varchar2(15);   
  7. v_manager  varchar2(15);   
  8.  CURSOR list_cursor IS  
  9.    SELECT deptno,count(*),sum(sal) FROM emp group by deptno;   
  10. BEGIN  
  11.   OPEN list_cursor;    
  12.   DBMS_OUTPUT.PUT_LINE('----------- 部 門 統 計 表 -----------');   
  13. DBMS_OUTPUT.PUT_LINE('部門名稱   總人數  總工資   部門經理');   
  14.   FETCH list_cursor INTO v_deptno,v_count,v_sumsal;    
  15.   WHILE list_cursor%found LOOP     
  16.  SELECT dname INTO v_dname FROM dept   
  17.     WHERE deptno=v_deptno;   
  18.     SELECT ename INTO v_manager FROM emp    
  19.     WHERE deptno=v_deptno and job='MANAGER';   
  20. DBMS_OUTPUT.PUT_LINE(rpad(v_dname,13)||rpad(to_char(v_count),8)   
  21.       ||rpad(to_char(v_sumsal),9)||v_manager);   
  22.     FETCH list_cursor INTO v_deptno,v_count,v_sumsal;    
  23.     END LOOP;   
  24.         DBMS_OUTPUT.PUT_LINE('--------------------------------------');   
  25.         CLOSE list_cursor;   
  26.         END;  


輸出結果為: 

Sql程式碼 

  1. -------------------- 部 門 統 計 表 -----------------   
  2.         部門名稱     總人數  總工資     部門經理   
  3.         ACCOUNTING    3      8750       CLARK   
  4.         RESEARCH      5     10875       JONES   
  5.         SALES             6      9400       BLAKE   
  6.         -------------------------------------------------------------   
  7.         PL/SQL 過程已成功完成。   


說明:遊標中使用到了起分組功能的SELECT語句,統計出各部門的總人數和總工資。再根據部門編號和職務找到部門的經理。該程式假定每個部門有一個經理。 
【訓練5】  為僱員增加工資,從工資低的僱員開始,為每個人增加原工資的10%,限定所增加的工資總額為800元,顯示增加工資的人數和餘額。 
輸入並除錯以下程式: 

Sql程式碼 

  1. SET SERVEROUTPUT ON    
  2. DECLARE    
  3.   V_NAME CHAR(10);   
  4.   V_EMPNO NUMBER(5);   
  5.   V_SAL NUMBER(8);   
  6.   V_SAL1 NUMBER(8);   
  7.   V_TOTAL NUMBER(8) := 800;     --增加工資的總額   
  8. V_NUM NUMBER(5):=0;     --增加工資的人數   
  9.          CURSOR emp_cursor IS    
  10.           SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL ASC;   
  11.         BEGIN  
  12.          OPEN emp_cursor;   
  13.         DBMS_OUTPUT.PUT_LINE('姓名      原工資  新工資');    
  14.         DBMS_OUTPUT.PUT_LINE('---------------------------');    
  15.          LOOP   
  16.             FETCH emp_cursor INTO V_EMPNO,V_NAME,V_SAL;   
  17. EXIT WHEN emp_cursor%NOTFOUND;   
  18.          V_SAL1:= V_SAL*0.1;   
  19.             IF V_TOTAL>V_SAL1 THEN  
  20.             V_TOTAL := V_TOTAL - V_SAL1;   
  21.             V_NUM:=V_NUM+1;   
  22.     DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||   
  23.         TO_CHAR(V_SAL+V_SAL1,'99999'));   
  24.              UPDATE EMP SET SAL=SAL+V_SAL1   
  25.              WHERE EMPNO=V_EMPNO;   
  26.          ELSE  
  27. DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||TO_CHAR(V_SAL,'99999'));   
  28.          END IF;   
  29.         END LOOP;   
  30.         DBMS_OUTPUT.PUT_LINE('---------------------------');   
  31.         DBMS_OUTPUT.PUT_LINE('增加工資人數:'||V_NUM||' 剩餘工資:'||V_TOTAL);     
  32.          CLOSE emp_cursor;    
  33.          COMMIT;   
  34.          END;  


輸出結果為: 

Sql程式碼 

  1. 姓名        原工資  新工資   
  2.         ---------------------------------------------   
  3. SMITH       1289   1418   
  4. JAMES       1531   1684   
  5. MARTIN      1664   1830   
  6. MILLER          1730   1903   
  7. ALLEN           1760   1936   
  8. ADAMS       1771   1771   
  9. TURNER      1815   1815   
  10. WARD        1830   1830   
  11. BLAKE       2850   2850   
  12. CLARK       2850   2850   
  13. JONES           2975   2975   
  14. FORD            3000   3000   
  15. KING            5000   5000   
  16. -----------------------------------------------   
  17. 增加工資人數:5 剩餘工資:3   
  18. PL/SQL 過程已成功完成。