轉自:http://blog.csdn.net/liyong199012/article/details/8948952程序員
遊標的概念:
遊標是SQL的一個內存工做區,由系統或用戶以變量的形式定義。遊標的做用就是用於臨時存儲從數據庫中提取的數據塊。在某些狀況下,須要把數據從存放在磁盤的表中調到計算機內存中進行處理,最後將處理結果顯示出來或最終寫回數據庫。這樣數據處理的速度纔會提升,不然頻繁的磁盤數據交換會下降效率。
遊標有兩種類型:顯式遊標和隱式遊標。在前述程序中用到的SELECT...INTO...查詢語句,一次只能從數據庫中提取一行數據,對於這種形式的查詢和DML操做,系統都會使用一個隱式遊標。可是若是要提取多行數據,就要由程序員定義一個顯式遊標,並經過與遊標有關的語句進行處理。顯式遊標對應一個返回結果爲多行多列的SELECT語句。
遊標一旦打開,數據就從數據庫中傳送到遊標變量中,而後應用程序再從遊標變量中分解出須要的數據,並進行處理。
隱式遊標
如前所述,DML操做和單行SELECT語句會使用隱式遊標,它們是:
* 插入操做:INSERT。
* 更新操做:UPDATE。
* 刪除操做:DELETE。
* 單行查詢操做:SELECT ... INTO ...。
當系統使用一個隱式遊標時,能夠經過隱式遊標的屬性來了解操做的狀態和結果,進而控制程序的流程。隱式遊標可使用名字SQL來訪問,但要注意,經過SQL遊標名老是隻能訪問前一個DML操做或單行SELECT操做的遊標屬性。因此一般在剛剛執行完操做以後,當即使用SQL遊標名來訪問屬性。遊標的屬性有四種,以下所示。 sql
- 隱式遊標的屬性 返回值類型 意 義
- SQL%ROWCOUNT 整型 表明DML語句成功執行的數據行數
- SQL%FOUND 布爾型 值爲TRUE表明插入、刪除、更新或單行查詢操做成功
- SQL%NOTFOUND 布爾型 與SQL%FOUND屬性返回值相反
- SQL%ISOPEN 布爾型 DML執行過程當中爲真,結束後爲假
【訓練1】 使用隱式遊標的屬性,判斷對僱員工資的修改是否成功。
步驟1:輸入和運行如下程序: 數據庫
- SET SERVEROUTPUT ON
- BEGIN
- UPDATE emp SET sal=sal+100 WHERE empno=1234;
- IF SQL%FOUND THEN
- DBMS_OUTPUT.PUT_LINE('成功修改僱員工資!');
- COMMIT;
- ELSE
- DBMS_OUTPUT.PUT_LINE('修改僱員工資失敗!');
- END IF;
- END;
運行結果爲: 函數
- 修改僱員工資失敗!
- PL/SQL 過程已成功完成。
步驟2:將僱員編號1234改成7788,從新執行以上程序:
運行結果爲: oop
- 成功修改僱員工資!
- 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僱員的名稱和職務。 fetch
- SET SERVEROUTPUT ON
- DECLARE
- v_ename VARCHAR2(10);
- v_job VARCHAR2(10);
- CURSOR emp_cursor IS
- SELECT ename,job FROM emp WHERE empno=7788;
- BEGIN
- OPEN emp_cursor;
- FETCH emp_cursor INTO v_ename,v_job;
- DBMS_OUTPUT.PUT_LINE(v_ename||','||v_job);
- CLOSE emp_cursor;
- END;
執行結果爲: spa
- SCOTT,ANALYST
- PL/SQL 過程已成功完成。
說明:該程序經過定義遊標emp_cursor,提取並顯示僱員7788的名稱和職務。
做爲對以上例子的改進,在如下訓練中採用了記錄變量。
【訓練2】 用遊標提取emp表中7788僱員的姓名、職務和工資。 .net
- SET SERVEROUTPUT ON
- DECLARE
- CURSOR emp_cursor IS SELECT ename,job,sal FROM emp WHERE empno=7788;
- emp_record emp_cursor%ROWTYPE;
- BEGIN
- OPEN emp_cursor;
- FETCH emp_cursor INTO emp_record;
- DBMS_OUTPUT.PUT_LINE(emp_record.ename||','|| emp_record.job||','|| emp_record.sal);
- CLOSE emp_cursor;
- END;
執行結果爲: 設計
- SCOTT,ANALYST,3000
- PL/SQL 過程已成功完成。
說明:實例中使用記錄變量來接收數據,記錄變量由遊標變量定義,須要出如今遊標定義以後。
注意:可經過如下形式得到記錄變量的內容:
記錄變量名.字段名。
【訓練3】 顯示工資最高的前3名僱員的名稱和工資。 指針
- SET SERVEROUTPUT ON
- DECLARE
- V_ename VARCHAR2(10);
- V_sal NUMBER(5);
- CURSOR emp_cursor IS SELECT ename,sal FROM emp ORDER BY sal DESC;
- BEGIN
- OPEN emp_cursor;
- FOR I IN 1..3 LOOP
- FETCH emp_cursor INTO v_ename,v_sal;
- DBMS_OUTPUT.PUT_LINE(v_ename||','||v_sal);
- END LOOP;
- CLOSE emp_cursor;
- END;
執行結果爲:
- KING,5000
- SCOTT,3000
- FORD,3000
- PL/SQL 過程已成功完成。
說明:該程序在遊標定義中使用了ORDER BY子句進行排序,並使用循環語句來提取多行數據。
遊標循環
【訓練1】 使用特殊的FOR循環形式顯示所有僱員的編號和名稱。
- SET SERVEROUTPUT ON
- DECLARE
- CURSOR emp_cursor IS
- SELECT empno, ename FROM emp;
- BEGIN
- FOR Emp_record IN emp_cursor LOOP
- DBMS_OUTPUT.PUT_LINE(Emp_record.empno|| Emp_record.ename);
- END LOOP;
- END;
執行結果爲:
- 7369SMITH
- 7499ALLEN
- 7521WARD
- 7566JONES
- PL/SQL 過程已成功完成。
說明:能夠看到該循環形式很是簡單,隱含了記錄變量的定義、遊標的打開、提取和關閉過程。Emp_record爲隱含定義的記錄變量,循環的執行次數與遊標取得的數據的行數相一致。
【訓練2】 另外一種形式的遊標循環。
- SET SERVEROUTPUT ON
- BEGIN
- FOR re IN (SELECT ename FROM EMP) LOOP
- DBMS_OUTPUT.PUT_LINE(re.ename)
- END LOOP;
- END;
執行結果爲:
說明:該種形式更爲簡單,省略了遊標的定義,遊標的SELECT查詢語句在循環中直接出現。
顯式遊標屬性
雖然可使用前面的形式得到遊標數據,可是在遊標定義之後使用它的一些屬性來進行結構控制是一種更爲靈活的方法。顯式遊標的屬性以下所示。
- 遊標的屬性 返回值類型 意 義
- %ROWCOUNT 整型 得到FETCH語句返回的數據行數
- %FOUND 布爾型 最近的FETCH語句返回一行數據則爲真,不然爲假
- %NOTFOUND 布爾型 與%FOUND屬性返回值相反
- %ISOPEN 布爾型 遊標已經打開時值爲真,不然爲假
可按照如下形式取得遊標的屬性:
遊標名%屬性
要判斷遊標emp_cursor是否處於打開狀態,可使用屬性emp_cursor%ISOPEN。若是遊標已經打開,則返回值爲「真」,不然爲「假」。具體可參照如下的訓練。
【訓練1】 使用遊標的屬性練習。
- SET SERVEROUTPUT ON
- DECLARE
- V_ename VARCHAR2(10);
- CURSOR emp_cursor IS
- SELECT ename FROM emp;
- BEGIN
- OPEN emp_cursor;
- IF emp_cursor%ISOPEN THEN
- LOOP
- FETCH emp_cursor INTO v_ename;
- EXIT WHEN emp_cursor%NOTFOUND;
- DBMS_OUTPUT.PUT_LINE(to_char(emp_cursor%ROWCOUNT)||'-'||v_ename);
- END LOOP;
- ELSE
- DBMS_OUTPUT.PUT_LINE('用戶信息:遊標沒有打開!');
- END IF;
- CLOSE emp_cursor;
- END;
執行結果爲:
- 1-SMITH
- 2-ALLEN
- 3-WARD
- PL/SQL 過程已成功完成。
說明:本例使用emp_cursor%ISOPEN判斷遊標是否打開;使用emp_cursor%ROWCOUNT得到到目前爲止FETCH語句返回的數據行數並輸出;使用循環來獲取數據,在循環體中使用FETCH語句;使用emp_cursor%NOTFOUND判斷FETCH語句是否成功執行,當FETCH語句失敗時說明數據已經取完,退出循環。
【練習1】去掉OPEN emp_cursor;語句,從新執行以上程序。
遊標參數的傳遞
【訓練1】 帶參數的遊標。
- SET SERVEROUTPUT ON
- DECLARE
- V_empno NUMBER(5);
- V_ename VARCHAR2(10);
- CURSOR emp_cursor(p_deptno NUMBER, p_job VARCHAR2) IS
- SELECT empno, ename FROM emp
- WHERE deptno = p_deptno AND job = p_job;
- BEGIN
- OPEN emp_cursor(10, 'CLERK');
- LOOP
- FETCH emp_cursor INTO v_empno,v_ename;
- EXIT WHEN emp_cursor%NOTFOUND;
- DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);
- END LOOP;
- END;
執行結果爲:
- 7934,MILLER
- PL/SQL 過程已成功完成。
說明:遊標emp_cursor定義了兩個參數:p_deptno表明部門編號,p_job表明職務。語句OPEN emp_cursor(10, 'CLERK')傳遞了兩個參數值給遊標,即部門爲十、職務爲CLERK,因此遊標查詢的內容是部門10的職務爲CLERK的僱員。循環部分用於顯示查詢的內容。
【練習1】修改Open語句的參數:部門號爲20、職務爲ANALYST,並從新執行。
也能夠經過變量向遊標傳遞參數,但變量須要先於遊標定義,並在遊標打開以前賦值。對以上例子從新改動以下:
【訓練2】 經過變量傳遞參數給遊標。
- SET SERVEROUTPUT ON
- DECLARE
- v_empno NUMBER(5);
- v_ename VARCHAR2(10);
- v_deptno NUMBER(5);
- v_job VARCHAR2(10);
- CURSOR emp_cursor IS
- SELECT empno, ename FROM emp
- WHERE deptno = v_deptno AND job = v_job;
- BEGIN
- v_deptno:=10;
- v_job:='CLERK';
- OPEN emp_cursor;
- LOOP
- FETCH emp_cursor INTO v_empno,v_ename;
- EXIT WHEN emp_cursor%NOTFOUND;
- DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);
- END LOOP;
- END;
執行結果爲:
- 7934,MILLER
- PL/SQL 過程已成功完成。
說明:該程序與前一程序實現相同的功能。
動態SELECT語句和動態遊標的用法
Oracle支持動態SELECT語句和動態遊標,動態的方法大大擴展了程序設計的能力。
對於查詢結果爲一行的SELECT語句,能夠用動態生成查詢語句字符串的方法,在程序執行階段臨時地生成並執行,語法是:
execute immediate 查詢語句字符串 into 變量1[,變量2...];
如下是一個動態生成SELECT語句的例子。
【訓練1】 動態SELECT查詢。
- SET SERVEROUTPUT ON
- DECLARE
- str varchar2(100);
- v_ename varchar2(10);
- begin
- str:='select ename from scott.emp where empno=7788';
- execute immediate str into v_ename;
- dbms_output.put_line(v_ename);
- END;
執行結果爲:
說明:SELECT...INTO...語句存放在STR字符串中,經過EXECUTE語句執行。
在變量聲明部分定義的遊標是靜態的,不能在程序運行過程當中修改。雖然能夠經過參數傳遞來取得不一樣的數據,但仍是有很大的侷限性。經過採用動態遊標,能夠在程序運行階段隨時生成一個查詢語句做爲遊標。要使用動態遊標須要先定義一個遊標類型,而後聲明一個遊標變量,遊標對應的查詢語句能夠在程序的執行過程當中動態地說明。
定義遊標類型的語句以下:
TYPE 遊標類型名 REF CURSOR;
聲明遊標變量的語句以下:
遊標變量名 遊標類型名;
在可執行部分能夠以下形式打開一個動態遊標:
OPEN 遊標變量名 FOR 查詢語句字符串;
【訓練2】 按名字中包含的字母順序分組顯示僱員信息。
輸入並運行如下程序:
- declare
- type cur_type is ref cursor;
- cur cur_type;
- rec scott.emp%rowtype;
- str varchar2(50);
- letter char:= 'A';
- begin
- loop
- str:= 'select ename from emp where ename like ''%'||letter||'%''';
- open cur for str;
- dbms_output.put_line('包含字母'||letter||'的名字:');
- loop
- fetch cur into rec.ename;
- exit when cur%notfound;
- dbms_output.put_line(rec.ename);
- end loop;
- exit when letter='Z';
- letter:=chr(ascii(letter)+1);
- end loop;
- end;
運行結果爲:
- 包含字母A的名字:
- ALLEN
- WARD
- MARTIN
- BLAKE
- CLARK
- ADAMS
- JAMES
- 包含字母B的名字:
- BLAKE
- 包含字母C的名字:
- CLARK
- 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的僱員名字。
- SET SERVEROUTPUT ON
- DECLARE
- v_name VARCHAR2(10);
- BEGIN
- SELECT ename
- INTO v_name
- FROM emp
- WHERE empno = 1234;
- DBMS_OUTPUT.PUT_LINE('該僱員名字爲:'|| v_name);
- EXCEPTION
- WHEN NO_DATA_FOUND THEN
- DBMS_OUTPUT.PUT_LINE('編號錯誤,沒有找到相應僱員!');
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE('發生其餘錯誤!');
- END;
執行結果爲:
- 編號錯誤,沒有找到相應僱員!
- PL/SQL 過程已成功完成。
說明:在以上查詢中,由於編號爲1234的僱員不存在,因此將發生類型爲「NO_DATA_
FOUND」的異常。「NO_DATA_FOUND」是系統預約義的錯誤類型,EXCEPTION部分下的WHEN語句將捕捉到該異常,並執行相應代碼部分。在本例中,輸出用戶自定義的錯誤信息「編號錯誤,沒有找到相應僱員!」。若是發生其餘類型的錯誤,將執行OTHERS條件下的代碼部分,顯示「發生其餘錯誤!」。
【訓練2】 由程序代碼顯示系統錯誤。
- SET SERVEROUTPUT ON
- DECLARE
- v_temp NUMBER(5):=1;
- BEGIN
- v_temp:=v_temp/0;
- EXCEPTION
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE('發生系統錯誤!');
- DBMS_OUTPUT.PUT_LINE('錯誤代碼:'|| SQLCODE( ));
- DBMS_OUTPUT.PUT_LINE('錯誤信息:' ||SQLERRM( ));
- END;
執行結果爲:
- 發生系統錯誤!
- 錯誤代碼:?1476
- 錯誤信息:ORA-01476: 除數爲 0
- PL/SQL 過程已成功完成。
說明:程序運行中發生除零錯誤,由WHEN OTHERS捕捉到,執行用戶本身的輸出語句顯示錯誤信息,而後正常結束。在錯誤處理部分使用了預約義函數SQLCODE( )和SQLERRM( )來進一步得到錯誤的代碼和種類信息。
預約義錯誤
Oracle的系統錯誤不少,但只有一部分常見錯誤在標準包中予以定義。定義的錯誤能夠在EXCEPTION部分經過標準的錯誤名來進行判斷,並進行異常處理。常見的系統預約義異常以下所示。
- 錯 誤 名 稱 錯誤代碼 錯 誤 含 義
- CURSOR_ALREADY_OPEN ORA_06511 試圖打開已經打開的遊標
- INVALID_CURSOR ORA_01001 試圖使用沒有打開的遊標
- DUP_VAL_ON_INDEX ORA_00001 保存重複值到唯一索引約束的列中
- ZERO_DIVIDE ORA_01476 發生除數爲零的除法錯誤
- INVALID_NUMBER ORA_01722 試圖對無效字符進行數值轉換
- ROWTYPE_MISMATCH ORA_06504 主變量和遊標的類型不兼容
- VALUE_ERROR ORA_06502 轉換、截斷或算術運算髮生錯誤
- TOO_MANY_ROWS ORA_01422 SELECT…INTO…語句返回多於一行的數據
- NO_DATA_FOUND ORA_01403 SELECT…INTO…語句沒有數據返回
- TIMEOUT_ON_RESOURCE ORA_00051 等待資源時發生超時錯誤
- TRANSACTION_BACKED_OUT ORA_00060 因爲死鎖,提交失敗
- STORAGE_ERROR ORA_06500 發生內存錯誤
- PROGRAM_ERROR ORA_06501 發生PL/SQL內部錯誤
- NOT_LOGGED_ON ORA_01012 試圖操做未鏈接的數據庫
- LOGIN_DENIED ORA_01017 在鏈接時提供了無效用戶名或口令
好比,若是程序向表的主鍵列插入重複值,則將發生DUP_VAL_ON_INDEX錯誤。
若是一個系統錯誤沒有在標準包中定義,則須要在說明部分定義,語法以下:
錯誤名 EXCEPTION;
定義後使用PRAGMA EXCEPTION_INIT來將一個定義的錯誤同一個特別的Oracle錯誤代碼相關聯,就能夠同系統預約義的錯誤同樣使用了。語法以下:
PRAGMA EXCEPTION_INIT(錯誤名,- 錯誤代碼);
【訓練1】 定義新的系統錯誤類型。
- SET SERVEROUTPUT ON
- DECLARE
- V_ENAME VARCHAR2(10);
- NULL_INSERT_ERROR EXCEPTION;
- PRAGMA EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);
- BEGIN
- INSERT INTO EMP(EMPNO) VALUES(NULL);
- EXCEPTION
- WHEN NULL_INSERT_ERROR THEN
- DBMS_OUTPUT.PUT_LINE('沒法插入NULL值!');
- WHEN OTHERS THEN
- DBMS_OUTPUT.PUT_LINE('發生其餘系統錯誤!');
- END;
執行結果爲:
- 沒法插入NULL值!
- PL/SQL 過程已成功完成。
說明:NULL_INSERT_ERROR是自定義異常,同系統錯誤1400相關聯。
自定義異常
程序設計者能夠利用引起異常的機制來進行程序設計,本身定義異常類型。能夠在聲明部分定義新的異常類型,定義的語法是:
錯誤名 EXCEPTION;
用戶定義的錯誤不能由系統來觸發,必須由程序顯式地觸發,觸發的語法是:
RAISE 錯誤名;
RAISE也能夠用來引起模擬系統錯誤,好比,RAISE ZERO_DIVIDE將引起模擬的除零錯誤。
使用RAISE_APPLICATION_ERROR函數也能夠引起異常。該函數要傳遞兩個參數,第一個是用戶自定義的錯誤編號,第二個參數是用戶自定義的錯誤信息。使用該函數引起的異常的編號應該在20 000和20 999之間選擇。
自定義異常處理錯誤的方式同前。
【訓練1】 插入新僱員,限定插入僱員的編號在7000~8000之間。
- SET SERVEROUTPUT ON
- DECLARE
- new_no NUMBER(10);
- new_excp1 EXCEPTION;
- new_excp2 EXCEPTION;
- BEGIN
- new_no:=6789;
- INSERT INTO emp(empno,ename)
- VALUES(new_no, '小鄭');
- IF new_no<7000 THEN
- RAISE new_excp1;
- END IF;
- IF new_no>8000 THEN
- RAISE new_excp2;
- END IF;
- COMMIT;
- EXCEPTION
- WHEN new_excp1 THEN
- ROLLBACK;
- DBMS_OUTPUT.PUT_LINE('僱員編號小於7000的下限!');
- WHEN new_excp2 THEN
- ROLLBACK;
- DBMS_OUTPUT.PUT_LINE('僱員編號超過8000的上限!');
- END;
執行結果爲:
僱員編號小於7000的下限!
PL/SQL 過程已成功完成。
說明:在此例中,自定義了兩個異常:new_excp1和new_excp2,分別表明編號小於7000和編號大於8000的錯誤。在程序中經過判斷編號大小,產生對應的異常,並在異常處理部分回退插入操做,而後顯示相應的錯誤信息。
【訓練2】 使用RAISE_APPLICATION_ERROR函數引起系統異常。
- SET SERVEROUTPUT ON
- DECLARE
- New_no NUMBER(10);
- BEGIN
- New_no:=6789;
- INSERT INTO emp(empno,ename)
- VALUES(new_no, 'JAMES');
- IF new_no<7000 THEN
- ROLLBACK;
- RAISE_APPLICATION_ERROR(-20001, '編號小於7000的下限!');
- END IF;
- IF new_no>8000 THEN
- ROLLBACK;
- RAISE_APPLICATION_ERROR (-20002, '編號大於8000的下限!');
- END IF;
- END;
執行結果爲:
- DECLARE
- *
- ERROR 位於第 1 行:
- ORA-20001: 編號小於7000的下限!
- 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表:
- SET SERVEROUTPUT ON
- DECLARE
- v_empno NUMBER(5):=7788;
- emp_rec emp%ROWTYPE;
- BEGIN
- SELECT * INTO emp_rec FROM emp WHERE empno=v_empno;
- DELETE FROM emp WHERE empno=v_empno;
- INSERT INTO emp1 VALUES emp_rec;
- IF SQL%FOUND THEN
- COMMIT;
- DBMS_OUTPUT.PUT_LINE('僱員複製成功!');
- ELSE
- ROLLBACK;
- DBMS_OUTPUT.PUT_LINE('僱員複製失敗!');
- END IF;
- END;
執行結果爲:
僱員複製成功!
PL/SQL 過程已成功完成。
步驟2:顯示覆制結果:
SELECT empno,ename,job FROM emp1;
執行結果爲:
- EMPNO ENAME JOB
- 7788 SCOTT ANALYST
說明:emp_rec變量是根據emp表定義的記錄變量,SELECT...INTO...語句將整個記錄傳給該變量。INSERT語句將整個記錄變量插入emp1表,若是插入成功(SQL%FOUND爲真),則提交事務,不然回滾撤銷事務。試修改僱員編號爲7902,從新執行以上程序。
【訓練2】 輸出僱員工資,僱員工資用不一樣高度的*表示。
輸入並執行如下程序:
- SET SERVEROUTPUT ON
- BEGIN
- FOR re IN (SELECT ename,sal FROM EMP) LOOP
- DBMS_OUTPUT.PUT_LINE(rpad(re.ename,12,' ')||rpad('*',re.sal/100,'*'));
- END LOOP;
- END;
輸出結果爲:
- SMITH ********
- ALLEN ****************
- WARD *************
- JONES ******************************
- MARTIN *************
- BLAKE *****************************
- CLARK *****************************
- SCOTT ******************************
- KING **************************************************
- TURNER ***************
- ADAMS ***********
- JAMES **********
- FORD ******************************
- MILLER *************
- 執行結果爲:
- PL/SQL 過程已成功完成。
說明:第一個rpad函數產生對齊效果,第二個rpad函數根據工資額產生不一樣數目的*。該程序採用了隱式的簡略遊標循環形式。
【訓練3】 編寫程序,格式化輸出部門信息。
輸入並執行以下程序:
- SET SERVEROUTPUT ON
- DECLARE
- v_count number:=0;
- CURSOR dept_cursor IS SELECT * FROM dept;
- BEGIN
- DBMS_OUTPUT.PUT_LINE('部門列表');
- DBMS_OUTPUT.PUT_LINE('---------------------------------');
- FOR Dept_record IN dept_cursor LOOP
- DBMS_OUTPUT.PUT_LINE('部門編號:'|| Dept_record.deptno);
- DBMS_OUTPUT.PUT_LINE('部門名稱:'|| Dept_record.dname);
- DBMS_OUTPUT.PUT_LINE('所在城市:'|| Dept_record.loc);
- DBMS_OUTPUT.PUT_LINE('---------------------------------');
- v_count:= v_count+1;
- END LOOP;
- DBMS_OUTPUT.PUT_LINE('共有'||to_char(v_count)||'個部門!');
- END;
輸出結果爲:
- 部門列表
- 部門編號:10
- 部門名稱:ACCOUNTING
- 所在城市:NEW YORK
- 部門編號:20
- 部門名稱:RESEARCH
- 所在城市:DALLAS
- ...
- 共有4個部門!
- PL/SQL 過程已成功完成。
說明:該程序中將字段內容垂直排列。V_count變量記錄循環次數,即部門個數。
【訓練4】 已知每一個部門有一個經理,編寫程序,統計輸出部門名稱、部門總人數、總工資和部門經理。
輸入並執行以下程序:
- SET SERVEROUTPUT ON
- DECLARE
- v_deptno number(8);
- v_count number(3);
- v_sumsal number(6);
- v_dname varchar2(15);
- v_manager varchar2(15);
- CURSOR list_cursor IS
- SELECT deptno,count(*),sum(sal) FROM emp group by deptno;
- BEGIN
- OPEN list_cursor;
- DBMS_OUTPUT.PUT_LINE('----------- 部 門 統 計 表 -----------');
- DBMS_OUTPUT.PUT_LINE('部門名稱 總人數 總工資 部門經理');
- FETCH list_cursor INTO v_deptno,v_count,v_sumsal;
- WHILE list_cursor%found LOOP
- SELECT dname INTO v_dname FROM dept
- WHERE deptno=v_deptno;
- SELECT ename INTO v_manager FROM emp
- WHERE deptno=v_deptno and job='MANAGER';
- DBMS_OUTPUT.PUT_LINE(rpad(v_dname,13)||rpad(to_char(v_count),8)
- ||rpad(to_char(v_sumsal),9)||v_manager);
- FETCH list_cursor INTO v_deptno,v_count,v_sumsal;
- END LOOP;
- DBMS_OUTPUT.PUT_LINE('--------------------------------------');
- CLOSE list_cursor;
- END;
輸出結果爲:
- 部門名稱 總人數 總工資 部門經理
- ACCOUNTING 3 8750 CLARK
- RESEARCH 5 10875 JONES
- SALES 6 9400 BLAKE
-
- PL/SQL 過程已成功完成。
說明:遊標中使用到了起分組功能的SELECT語句,統計出各部門的總人數和總工資。再根據部門編號和職務找到部門的經理。該程序假定每一個部門有一個經理。
【訓練5】 爲僱員增長工資,從工資低的僱員開始,爲每一個人增長原工資的10%,限定所增長的工資總額爲800元,顯示增長工資的人數和餘額。
輸入並調試如下程序:
- SET SERVEROUTPUT ON
- DECLARE
- V_NAME CHAR(10);
- V_EMPNO NUMBER(5);
- V_SAL NUMBER(8);
- V_SAL1 NUMBER(8);
- V_TOTAL NUMBER(8) := 800;
- V_NUM NUMBER(5):=0;
- CURSOR emp_cursor IS
- SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL ASC;
- BEGIN
- OPEN emp_cursor;
- DBMS_OUTPUT.PUT_LINE('姓名 原工資 新工資');
- DBMS_OUTPUT.PUT_LINE('---------------------------');
- LOOP
- FETCH emp_cursor INTO V_EMPNO,V_NAME,V_SAL;
- EXIT WHEN emp_cursor%NOTFOUND;
- V_SAL1:= V_SAL*0.1;
- IF V_TOTAL>V_SAL1 THEN
- V_TOTAL := V_TOTAL - V_SAL1;
- V_NUM:=V_NUM+1;
- DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||
- TO_CHAR(V_SAL+V_SAL1,'99999'));
- UPDATE EMP SET SAL=SAL+V_SAL1
- WHERE EMPNO=V_EMPNO;
- ELSE
- DBMS_OUTPUT.PUT_LINE(V_NAME||TO_CHAR(V_SAL,'99999')||TO_CHAR(V_SAL,'99999'));
- END IF;
- END LOOP;
- DBMS_OUTPUT.PUT_LINE('---------------------------');
- DBMS_OUTPUT.PUT_LINE('增長工資人數:'||V_NUM||' 剩餘工資:'||V_TOTAL);
- CLOSE emp_cursor;
- COMMIT;
- END;
輸出結果爲:
- 姓名 原工資 新工資
-
- SMITH 1289 1418
- JAMES 1531 1684
- MARTIN 1664 1830
- MILLER 1730 1903
- ALLEN 1760 1936
- ADAMS 1771 1771
- TURNER 1815 1815
- WARD 1830 1830
- BLAKE 2850 2850
- CLARK 2850 2850
- JONES 2975 2975
- FORD 3000 3000
- KING 5000 5000
- 增長工資人數:5 剩餘工資:3
- PL/SQL 過程已成功完成。
【練習1】按部門編號從小到大的順序輸出僱員名字、工資以及工資與平均工資的差。 【練習2】爲全部僱員增長工資,工資在1000之內的增長30%,工資在1000~2000之間的增長20%,2000以上的增長10%。