" 1、游標 " "1.1、游標屬性" "1.2、隱式游標" "1.3、游標處理及案例" " 2、異常 " "2.1、異常類別" "2.2、異常函數" "2.3、異常處理及案例" " 3、事務 " "3.1、開始事務、結束事務" "3.2、自治事務" "3.3、事務處理及案例" " 4、總結 " 1 ...
1、游標
游標是一種數據處理機制,它提供了在結果集中依次瀏覽一行或多行數據的能力。游標就相當於是一個指針,它可以指定結果中的任何位置,然後允許用戶對指定位置的數據進行處理。Oracle 中的游標分為顯示游標和隱式游標兩種。
1.1、游標屬性
每個游標和游標變數都有四個屬性:%FOUND、%ISOPEN、%NOTFOUND 和 %ROWCOUNT。當附加到游標或游標變數時,這些屬性返回有關執行數據操作語句的狀態信息。
1、%ISOPEN:如果游標處於打開狀態,則 cursor_name%ISOPEN 返回 TRUE;否則返回FALSE。
2、%FOUND:在游標被打開之後,第一次提取數據行之前,cursor_name%FOUND 會返回 NULL。此後,如果最近一次提取返回了一行數據,則返回 TRUE,如果最近一次提取未能返回一行數據,則返回 FALSE。
3、%NOTFOUND:在游標被打開之後,第一次提取數據行之前,cursor_name%NOTFOUND 返回NULL。此後,如果最近一次提取返回了一行數據,則返回 FALSE,如果最近一次提取未能返回一行數據,則返回 TRUE。
4、%ROWCOUNT:在游標被打開之後,第一次提取數據行之前,cursor_name%ROWCOUNT 返回 0。此後,它返回到目前為止已提取的行數。如果最近一次提取返回了一行,則該數字會遞增。
1.2、隱式游標
顯示游標主要用於處理查詢語句,尤其是查詢結果為多條記錄的情況,需要由程式員顯式地聲明一個游標來單獨處理這些行。而對於 SELECT ... INTO ...、INSERT、DELETE、UPDATE 等語句,Oracle 系統會自動地為這些操作設置隱式游標,併為該游標的取名為 SQL。隱式游標的相關操作均由 Oracle 系統自動完成,無需用戶進行任何諸如打開或關閉之類的處理。
用戶可以通過隱式游標的名稱和屬性來瞭解操作的狀態和結果,進而控製程序的流程。但需要註意的是,通過 SQL 游標名只能訪問前一個 DML 操作或單行 SELECT 操作的游標屬性(所以通常得在執行完操作之後,立馬使用 SQL 游標名來訪問屬性)。因為在隱式游標的工作區中,所存放的數據是與用戶自定義的顯示游標無關的、最新處理的一條 SQL 語句所包含的數據。
關於單行 SELECT 操作的隱式游標,還 3 點需要註意:
- 1、每一個 單行 SELECT 隱式游標必須得有一個 INTO 子句。
- 2、INTO 子句後接收數據的變數的數據類型要與對應列的數據類型一致。
- 3、隱式游標一次只能返回一條數據。
示例:
DECLARE
v_course_id demo.t_course.course_id%TYPE := 7;
BEGIN
DBMS_OUTPUT.PUT_LINE('SQL%ROWCOUNT: '||SQL%ROWCOUNT); -- NULL
UPDATE demo.t_course t SET t.course_desc='7' WHERE t.course_id=v_course_id;
DBMS_OUTPUT.PUT_LINE('SQL%ROWCOUNT: '||SQL%ROWCOUNT); -- 1
IF SQL%ISOPEN THEN -- false
DBMS_OUTPUT.PUT_LINE('SQL%ISOPEN is true.');
END IF;
IF SQL%FOUND THEN -- true
DBMS_OUTPUT.PUT_LINE('SQL%FOUND is true.');
END IF;
IF SQL%NOTFOUND THEN -- false
DBMS_OUTPUT.PUT_LINE('SQL%NOTFOUND is true.');
END IF;
END;
1.3、游標處理及案例
在使用游標之前,必須先聲明游標。可以在聲明的時候給游標取一個名字,並將其與特定的查詢想關聯,還可以同時為游標指定一個返回類型。在通過游標提取數據之前,還得先打開游標,然後通過游標提取行。當處理完所有行之後,應立即關閉游標變數,以免造成資源浪費。
示例 1(遍歷輸出所有課程名稱):
DECLARE
TYPE course_type IS REF CURSOR RETURN demo.t_course%ROWTYPE; -- 定義游標類型
cursor1 course_type; -- 定義一個游標 cursor1
v_course demo.t_course%ROWTYPE; -- 定義一個表示 t_course 表中行的變數
BEGIN
IF NOT cursor1%ISOPEN THEN -- 如果游標不是打開狀態,如果試圖打開一個已打開的游標時將會出現錯誤,所以應該要判斷一下
OPEN cursor1 FOR SELECT t.* FROM demo.t_course t; -- 打開游標,這裡得確保 SELECT 列表和游標變數的數據類型一致
LOOP
FETCH cursor1 INTO v_course; -- 從游標中提取行
EXIT WHEN cursor1%NOTFOUND; -- 如果找不到數據行了就退出迴圈
DBMS_OUTPUT.PUT_LINE('課程名稱:'||v_course.course_name); -- 列印被提取的數據
END LOOP;
END IF;
CLOSE cursor1; -- 關閉游標,如果試圖關閉一個已關閉的游標時也會出現錯誤
END;
示例 2(遍歷輸出所有課程名稱,並分段顯示):
DECLARE
cursor1 SYS_REFCURSOR;
v_course_name demo.t_course.course_name%TYPE;
BEGIN
OPEN cursor1 FOR SELECT t.course_name FROM demo.t_course t;
LOOP
FETCH cursor1 INTO v_course_name;
EXIT WHEN cursor1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(cursor1%ROWCOUNT||':'||v_course_name);
IF cursor1%ROWCOUNT=2 THEN
DBMS_OUTPUT.PUT_LINE('--- 已輸出兩行記錄 ---');
END IF;
END LOOP;
CLOSE cursor1;
END;
上例中的 SYS_REFCURSOR 是 Oracle 系統提供的,用於傳遞游標變數,與使用自定義的“REF CURSOR”並無本質區別
示例 3(輸出指定課程的名稱和描述):
DECLARE
v_course_id demo.t_course.course_id%TYPE := 1;
v_course_name demo.t_course.course_name%TYPE;
v_course_desc demo.t_course.course_desc%TYPE;
CURSOR cursor1 IS
SELECT t.course_name,t.course_desc FROM demo.t_course t WHERE t.course_id=v_course_id;
BEGIN
OPEN cursor1;
FETCH cursor1 INTO v_course_name,v_course_desc;
IF cursor1%FOUND THEN
DBMS_OUTPUT.PUT_LINE('名稱:'||v_course_name||CHR(10)||'描述:'||v_course_desc);
END IF;
CLOSE cursor1;
END;
示例 4(輸出指定課程的相關信息):
DECLARE
CURSOR cursor1 RETURN demo.t_course%ROWTYPE IS
SELECT t.* FROM demo.t_course t WHERE t.course_id=2;
v_course t_course%ROWTYPE;
BEGIN
OPEN cursor1;
FETCH cursor1 INTO v_course;
IF cursor1%FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_course.course_id||CHR(10)||v_course.course_name||CHR(10)||v_course.course_desc);
END IF;
CLOSE cursor1;
END;
示例 5(創建一個提取信息的程式包,用於根據條件提取不同表中的信息):
CREATE PACKAGE pkg_take_info AS
TYPE cursor_type IS REF CURSOR;
PROCEDURE sp_take_info(cursor1 IN OUT cursor_type,choice INT);
END pkg_take_info;
CREATE PACKAGE BODY pkg_take_info AS
PROCEDURE sp_take_info(cursor1 IN OUT cursor_type,choice INT) IS
BEGIN
IF choice=1 THEN
OPEN cursor1 FOR SELECT t.* FROM demo.t_course t;
ELSIF choice=2 THEN
OPEN cursor1 FOR SELECT t.* FROM demo.t_staff t;
ELSIF choice=3 THEN
OPEN cursor1 FOR SELECT t.* FROM demo.t_field_enum t;
END IF;
CLOSE cursor1;
END;
END pkg_take_info;
2、異常
異常(PL/SQL 運行時錯誤)可能源自設計錯誤、編碼錯誤、硬體故障以及許多其它來源。我們往往無法預期所有可能的異常,但可以編寫異常處理模塊,讓程式繼續有效運行。
2.1、異常類別
- 內部定義異常:運行時系統會自動引發內部定義的異常。典型的內部定義異常如 ORA-00060(在等待資源時檢測到死鎖)和 ORA-27102(記憶體不足)。內部定義的異常總是有一個錯誤代碼,但沒有名稱。
- 預定義異常:預定義的異常也是內部定義的異常,但 PL/SQL 給它取了名字。例如,ORA-06500(PL/SQL:存儲錯誤)就有預定義名稱 STORAGE_ERROR。
- 用戶自定義異常:可以在任何 PL/SQL 匿名塊、子程式或程式包的聲明部分聲明自己的異常。例如,可以聲明一個名為餘額不足的異常來標記已透支的銀行帳戶,併在異常處理模塊中拋出該異常。
2.2、異常函數
1、錯誤代碼 SQLCODE 函數
在異常處理程式中,SQLCODE 函數返回正在處理的異常的數字代碼。在異常處理程式之外,SQLCODE 返回 0。對於內部定義的異常,數字代碼是有關 Oracle 資料庫錯誤的編號。除“no data found”數字代碼為 +100 之外,該數字一般是負數。對於用戶自定義的異常,數字代碼預設為 +1 或與EXCEPTION_INIT
編譯指示異常相關聯的錯誤代碼。在 SQL 語句無法調用 SQLCODE。
2、錯誤消息 SQLERRM 函數
SQLERRM 函數返回與錯誤代碼相關聯的錯誤消息。
語法:
SQLERRM [(error_code)]
示例:
BEGIN
DBMS_OUTPUT.PUT_LINE(SQLERRM); -- ORA-0000: normal, successful completion
DBMS_OUTPUT.PUT_LINE(SQLERRM(100)); -- ORA-01403: 未找到任何數據
DBMS_OUTPUT.PUT_LINE(SQLERRM(-60)); -- ORA-00060: 等待資源時檢測到死鎖
DBMS_OUTPUT.PUT_LINE(SQLERRM(-27102)); -- ORA-27102: 記憶體不足
DBMS_OUTPUT.PUT_LINE(SQLERRM(-6500)); -- ORA-06500: PL/SQL: 存儲錯誤
END;
2.3、異常處理及案例
定義異常
語法:
exception_name EXCEPTION;
如上所述,定義用戶自定義異常的方法非常簡單,只需給出異常名稱即可。另外,還可以通過EXCEPTION_INIT
將該名稱分配給內部定義的異常,具體用法可參考:《Oracle Database PL/SQL Language Reference: EXCEPTION_INIT Pragma》。
拋出異常
PL/SQL 中的 RAISE 與 C# 中的 throw 相似,RAISE 語句的作用是在 Oracle 中拋出異常。只要在異常處理模塊之外,定義好用戶自定義異常並指定異常名稱,就可以通過 RAISE 語句明確引發該異常。如果省略異常名稱,則 RAISE 語句將會重新檢查當前異常。
語法:
RAISE [ exception_name ];
任何 PL/SQL 塊(包括匿名塊、子程式和程式包)都可以有一個異常處理模塊,用來處理一或多個異常。對於命名異常,可以編寫一個特定的異常處理程式,而不是使用 OTHERS 異常來處理它。特定的異常處理程式比 OTHERS 異常處理程式更有效,因為後者還必須調用一個函數來確定它正在處理哪個異常。
如果沒有異常處理程式,您必須檢查可能發生的每一個可能的錯誤,然後處理它。很容易忽略可能的錯誤或可能發生的地方,特別是如果錯誤不能立即被檢測到(例如,壞數據可能無法檢測,直到在計算中使用它)。這會導致大量錯誤處理代碼散佈在整個程式中。
使用異常處理程式,您不需要知道每一個可能的錯誤或其可能發生的任何地方。您只需在每個可能發生錯誤的程式段中包含一個異常處理部分。在異常處理部分,您可以包括特定和未知錯誤的異常處理程式。如果塊中的任何位置(包括子塊內)發生錯誤,則異常處理程式處理它。錯誤處理代碼在塊的異常處理部分中被隔離。
異常模塊語法:
EXCEPTION
WHEN ex_name_1 THEN statements_1;
WHEN ex_name_2 OR ex_name_3 THEN statements_2;
WHEN OTHERS THEN statements_3;
當塊中可執行部分出現異常時,可執行部分停止執行,異常處理部分開始執行。例如,出現異常ex_name_1
,則statements_1
會被運行;如果出現異常ex_name_2
或ex_name_3
,則statements_2
會被運行。如果在沒有異常處理程式的塊中引發異常,則異常傳播。也就是說,異常在連續的封閉塊中重現本身,直到被處理,如果沒有被處理,則 PL/SQL 會向調用者或主機環境返回未處理的異常錯誤。
示例(ZERO_DIVIDE
):
DECLARE
stock_price NUMBER(18,6) := 8; -- 股票市場價格
net_earnings NUMBER(18,6) := 0; -- 凈收益
pe_ratio NUMBER(18,6); -- 市盈率
BEGIN
pe_ratio := stock_price/net_earnings; -- 除數為零
DBMS_OUTPUT.PUT_LINE('Price/earnings ratio: '||pe_ratio);
EXCEPTION
WHEN ZERO_DIVIDE THEN
DBMS_OUTPUT.PUT_LINE('Zero earnings!');
pe_ratio := NULL;
END;
示例(NO_DATA_FOUND
):
DECLARE
v_course_id demo.t_course.course_id%TYPE := 8;
v_course_name demo.t_course.course_name%TYPE;
v_course_desc demo.t_course.course_desc%TYPE;
BEGIN
SELECT t.course_name,t.course_desc INTO v_course_name,v_course_desc
FROM demo.t_course t
WHERE t.course_id=v_course_id;
DBMS_OUTPUT.PUT_LINE('課程名稱:'||v_course_name);
DBMS_OUTPUT.PUT_LINE('課程說明:'||v_course_desc);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No such course!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('未知錯誤!');
END;
示例(VALUE_ERROR
):
BEGIN
DECLARE
credit_limit NUMBER(5) := 200000;
BEGIN
NULL;
END;
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('賦值時出錯!');
END;
示例(用戶自定義異常):
DECLARE
v_amount NUMBER(18,6) := 1.00;
v_due_date DATE := TO_DATE('2017-01-07','yyyy-mm-dd');
no_money EXCEPTION;
past_due EXCEPTION;
BEGIN
IF v_amount<=0 THEN
RAISE no_money;
END IF;
IF v_due_date<demo.fn_today THEN
RAISE past_due;
END IF;
EXCEPTION
WHEN no_money THEN
DBMS_OUTPUT.PUT_LINE('沒錢了!');
WHEN past_due THEN
DBMS_OUTPUT.PUT_LINE('逾期了!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('出現未知異常!');
RAISE;
END;
更多 Oracle 異常處理細節及案例,請參考:《Oracle Database PL/SQL Language Reference: PL/SQL Error Handling》。
3、事務
事務是資料庫區別於文件系統的特性之一。事務會把資料庫從一種一致狀態轉變為另一種一致狀態。Oracle 中的事務體現了所有必要的 ACID 特性。ACID 是以下 4 個詞的縮寫。
- 原子性(Atomicity):事務中的所有動作要麼都發生,要麼都不發生。
- 一致性(Consistency):事務將資料庫從一種一致狀態轉變為下一種一致狀態。
- 隔離性(Isolation):一個事務的影響在該事務提交前對其它事務都不可見。
- 持久性(Durability):事務一旦提交,其結果就是永久性的。
事務是包含一個或多個 SQL 語句的邏輯單元。事務中所有 SQL 語句的效果可以是全部提交(應用於資料庫)或全部回滾(從資料庫撤消)。
3.1、開始事務、結束事務
在 Oracle 中不需要用專門的語句來“開始事務”。事務會在遇到第一個可執行的 SQL 語句時處隱式開始。也可以使用 SET TRANSACTION 或 DBMS_TRANSACTION 包來顯式地開始一個事務,但這一步並非是必須的。
當執行了不帶 SAVEPOINT 子句的 COMMIT(提交)或 ROLLBACK(回滾)時,事務就會被顯式地結束。當執行了 DDL 或 DCL 語句時,事務就會被自動提交,也就是隱式地結束。如果用戶斷開與 Oracle 的連接,當前事務自動提交。如果用戶進程異常終止,當前事務自動回滾。如用戶退出 SQLPlus 會話時,若沒有提交或回滾事務,SQLPlus 會自動為用戶提交。當系統崩潰時事務也會被隱式提交。我們不能過度依賴這些隱式行為,因為將來這些行為可能會改變。
提交意味著用戶明確或暗示地要求將事務中的更改設置為永久性。當用戶發出 COMMIT 語句時,會發出顯式請求。在應用程式正常終止或 DDL 操作完成後,會發生隱式請求。只有事務提交之後,事務的 SQL 語句所做的更改才會變得永久可見。在事務提交後發出的查詢將看到已提交的更改。
Oracle 中的 DDL 語句具有原子性,不過只是在語句級保證原子性,如果操作成功則提交,否則回滾 DDL 操作。提交所有未完成的工作,結束當前的所有事務。只要執行了 DDL 語句,就可以將現有的事務立即提交,並完成後面的 DDL 命令,這些 DDL 命令可能提交從而得到持久的結果,也可能因為出現錯誤而回滾。儘管 DDL 並不違反 ACID 概念,但 DDL 語句會提交的這一點確實需要註意。
ORACLE 伺服器會執行隱式的存儲點。如果在執行過程中的任何時候,一旦 SQL 語句運行出錯,就會回滾該語句的所有效果。回滾的效果就好像是該語句從未被運行過。回滾意味著撤消對未提交事務中的SQL語句執行的數據的任何更改。Oracle 使用 undo 表空間(或回滾段)來存儲舊值。重做日誌包含更改記錄。Oracle 允許您回滾整個未提交的事務。還可以將未提交事務的尾部部分回滾到稱為保存點的標記。
3.2、自治事務
自治事務(autonomous transaction)允許你創建一個“事務中的事務”,它能獨立於其父事務提交或回滾。利用自治事務,可以掛起當前執行的事務,開始一個新事務,完成一些工作,然後提交或回滾,所有這些都不影響當前所執行事務的狀態。換句話說,自治事務允許從某個事務中調用另一個獨立的事務。一旦被調用,自治事務就完全獨立於調用它的主事務。在自治事務中,看不到主事務中發生的任何未提交的更改,並且不與主事務共用任何鎖或資源。
在自治事務中還可以調用另一個自治事務,除了資源限制外,對於可以調用多少層級的自治事務沒有限制。自治事務與其調用的事務之間可能會出現死鎖,當 Oracle 檢測到這種死鎖時會返回錯誤,應用程式開發人員應極力避免這種死鎖的發生。當一個自治塊調用另一個自治塊或其自身時,被調用的塊不會與調用塊共用任何事務上下文。然而,當自主塊調用非自主塊(即,未聲明為自主事務的塊)時,被調用塊繼承了調用自主塊的事務上下文。
自動事務對於實現需要獨立執行的操作非常有用,無論調用事務是否提交或回滾,例如記錄錯誤日誌或信息型消息,從而可以獨立於父事務完成提交。
語法(聲明為自治事務):
PRAGMA AUTONOMOUS_TRANSACTION;
3.3、事務處理及案例
COMMIT:結束當前會話事務,並使得已做的所有修改成為永久性的。
語法:
COMMIT [WORK] [COMMENT clause] [WRITE clause] [FORCE clause];
預設是 COMMIT WORK WRITE WAIT IMMEDIATE,表示同步提交,如果明確寫出 COMMIT NOWAIT 則表示非同步提交。PL/SQL 一直都透明的使用非同步提交,而流行的 API(ODBC 和 JDBC) 預設會自動提交事務。如果應用需要與人交互,就應當使用同步提交。對於面向客戶的線上應用,不能把非同步提交做為改善性能的手段。非同步應用只適用於面向批處理的應用,也就是那些出現故障時能自動重啟的應用。互動式應用在出現故障時無法自動重啟,必須由人來重新執行事務。
ROLLBACK:回滾當前會話事務,並撤銷所有未提交的修改。
語法:
ROLLBACK [WORK] [TO [SAVEPOINT] savepoint_name | FORCE 'string'];
SAVEPOINT:允許你在事務上下文中創建保存點,一個事務可以有多個保存點,將一個事務分為多個較小的部分。保存點只在事務執行過程中有效,事務結束即被釋放。保存點在應用程式中同樣有用。如果一個過程包含幾個函數,那麼可以在每個函數開始之前創建一個保存點。然後,如果函數失敗,則在函數開始之前很容易將數據返回到其狀態,並使用修改後的參數重新運行該函數或執行恢復操作。
ROLLBACK TO
SET TRANSACTION:允許你設置不同的事務屬性,如事務的名稱、隔離級別以及事務是只讀的還是可讀寫的。SET TRANSACTION 語句執行的操作僅影響當前事務,而不影響其它用戶或其它事務。
語法:
SET TRANSACTION [READ ONLY | READ WRITE]
[ISOLATION LEVEL [SERIALIZE | READ COMMITED]
[USE ROLLBACK SEGMENT 'segment_name']
[NAME 'transaction_name'];
示例 1:
BEGIN
INSERT INTO t3(f1) VALUES(1); -- 第一條修改數據的語句即事務的開始
SAVEPOINT p1; -- 創建一個標記點
INSERT INTO t3(f1) VALUES(2);
ROLLBACK TO p1; -- 回滾到 p1,這樣 p1 之後數據 2 就丟失了,只剩下 1
INSERT INTO t3(f1) VALUES(3);
COMMIT; -- 提交事務,所有保存點均失效,表中數據 1 和 3
END;
示例 2:
BEGIN
SET TRANSACTION READ WRITE NAME 'tran1';
SAVEPOINT p1; -- 創建一個標記點
INSERT INTO t3(f1) VALUES(4);
COMMIT; -- 提交事務
INSERT INTO t3(f1) VALUES(5);
ROLLBACK; -- 回滾事務,表中數據 1、3、4
END;
想要瞭解更多 Oracle 中的事務處理知識可參考:
- 《Oracle Database Concepts: Transaction Management》
- 《Oracle Database Administrator's Guide: Distributed Transactions Concepts》
- 《Oracle Database Administrator's Guide: Managing Distributed Transactions》。
4、總結
本文主要講述了 PL/SQL 中的游標、異常和事務的基本概念和基本用法,且特別地講述了 Oracle 中的兩個非常特殊的事物——隱式游標和自治事務。
本文鏈接:http://www.cnblogs.com/hanzongze/p/Oracle-plsql-2.html
版權聲明:本文為博客園博主 韓宗澤 原創,作者保留署名權!歡迎通過轉載、演繹或其它傳播方式來使用本文,但必須在明顯位置給出作者署名和本文鏈接!本人初寫博客,水平有限,若有不當之處,敬請批評指正,謝謝!