PL/SQL Cursor 解析 : Oracle Store Procedure & Cursor
PL/SQL 游標深入解析:從基礎到作用域,一篇文章帶你徹底搞懂
大家好,我是Jayson。今天我們來聊聊 PL/SQL 中一個非常基礎但至關重要的概念——游標(Cursor)。無論你是剛接觸 Oracle 資料庫的萌新,還是已經寫過幾年 SQL 的老手,游標都是繞不開的話題。尤其是游標的作用範圍(Scope),很多人在實際開發中容易踩坑。
先來看一段簡單的範例程式碼:
CREATE OR REPLACE PROCEDURE TESTING
AS
F_DATE VARCHAR2(10);
CURSOR Hello_Cursor IS
SELECT A FROM <your table> WHERE A = 'ABC';
BEGIN
OPEN Hello_Cursor;
FETCH Hello_Cursor INTO F_DATE;
DBMS_OUTPUT.PUT_LINE(F_DATE);
CLOSE Hello_Cursor;
END;
這段程式碼看起來很簡單,但它背後隱藏著游標使用的完整流程。接下來我們就從這段程式碼出發,逐步拆解游標的方方面面,最後深入討論游標的作用範圍。
一、什麼是游標?
簡單來說,游標是 PL/SQL 中用於處理 SQL 查詢結果集的一種機制。當你執行一個 SELECT 語句,回傳的結果可能包含多行資料,而 PL/SQL 是面向單行處理的,這時候就需要游標來逐行讀取資料。
游標分為兩類:
- 隱式游標(Implicit Cursor):由 PL/SQL 自動管理,用於單行查詢或 DML 操作。
- 顯式游標(Explicit Cursor):由程式設計師顯式定義,用於處理多行結果集。
今天我們重點講解顯式游標,因為它是我們手動控制的核心。
二、顯式游標的四步曲
使用顯式游標通常需要四個步驟:
- 宣告游標(Declare):定義游標名稱和對應的 SELECT 語句。
- 開啟游標(Open):執行 SELECT 語句,將結果集載入到記憶體。
- 提取資料(Fetch):從結果集中逐行取出資料,存入變數。
- 關閉游標(Close):釋放資源。
對應到範例程式碼:
- 宣告:
CURSOR Hello_Cursor IS SELECT A FROM <your table> WHERE A = 'ABC'; - 開啟:
OPEN Hello_Cursor; - 提取:
FETCH Hello_Cursor INTO F_DATE; - 關閉:
CLOSE Hello_Cursor;
細節解析
F_DATE VARCHAR2(10);是一個區域變數(Local Variable),用來接收游標提取出的值。- 提取時,
FETCH語句將當前行的A欄位值賦給F_DATE。 - 如果查詢結果有多行,單次
FETCH只取一行。要取所有行,需要迴圈提取。 - 最後用
DBMS_OUTPUT.PUT_LINE印出結果。
這裡有個小問題:如果查詢結果為空,F_DATE 會是什麼?答案是 NULL,並且 PUT_LINE 不會報錯,只是印出空行。但更嚴謹的做法應該檢查游標的狀態。
三、游標的作用範圍(Scope)
題目中特別提到了「cursor 範圍用法」,也就是游標及其相關變數的作用域。我們來仔細分析一下。
1. 變數的作用域
在範例中,F_DATE 是在程序的 AS 部分宣告的,它的作用域是整個 TESTING 程序。也就是說,在 BEGIN...END 區塊內的任何地方都可以存取它。一旦程序結束,該變數就消失了。
2. 游標的作用域
游標 Hello_Cursor 也是在 AS 部分宣告的,所以它的作用域也是整個程序。在程序內部,你可以開啟、提取、關閉它。但是,游標本身是一個資料物件,它並不像普通變數那樣儲存資料,而是指向結果集的記憶體區域。
3. 游標的可見性
- 在宣告它的區塊內:整個區塊及巢狀子區塊中都可以使用該游標。
- 在區塊外:不可見。如果你在另一個程序中想使用同一個游標,必須重新宣告。
- 套件級游標(Package-level Cursor):如果你在套件的規格(Specification)或主體(Body)中宣告游標,那麼它的作用域可以擴充到整個套件,甚至透過套件名稱在其他地方存取(如果宣告在規格中)。
所以,游標的作用域遵循 PL/SQL 的區塊結構作用域規則:內部區塊可以存取外部區塊的游標,反之則不行。
4. 游標與例外處理
如果在游標操作過程中發生例外(Exception),游標是否會自動關閉?不會。你需要在例外處理部分顯式關閉游標,否則可能造成資源洩漏(Resource Leak)。這也是作用域的一個體現:例外處理區塊仍然在同一個作用域內,所以可以存取游標並關閉它。
四、進階:游標屬性與迴圈提取
上面的範例只提取了一行,如果查詢結果有多行,我們需要迴圈。同時,我們可以利用游標的屬性(Attributes)來獲取狀態資訊。
游標常用屬性
%FOUND:上次FETCH是否有回傳一行(布林值)。%NOTFOUND:與%FOUND相反。%ROWCOUNT:目前已經提取的行數。%ISOPEN:游標是否已開啟。
使用 LOOP 提取所有行
CREATE OR REPLACE PROCEDURE TESTING_ALL
AS
F_DATE VARCHAR2(10);
CURSOR Hello_Cursor IS
SELECT A FROM your_table WHERE A LIKE 'A%';
BEGIN
OPEN Hello_Cursor;
LOOP
FETCH Hello_Cursor INTO F_DATE;
EXIT WHEN Hello_Cursor%NOTFOUND; -- 沒有更多行時退出
DBMS_OUTPUT.PUT_LINE(F_DATE);
END LOOP;
CLOSE Hello_Cursor;
END;
這裡用 %NOTFOUND 判斷是否結束迴圈,避免了無窮迴圈。
使用 FOR 迴圈簡化游標
PL/SQL 提供了 FOR 迴圈自動管理游標的開啟、提取和關閉,大大簡化了程式碼:
CREATE OR REPLACE PROCEDURE TESTING_FOR
AS
BEGIN
FOR rec IN (SELECT A FROM your_table WHERE A LIKE 'A%') LOOP
DBMS_OUTPUT.PUT_LINE(rec.A);
END LOOP;
END;
rec 是隱式宣告的記錄變數(Record Variable),作用域僅限於迴圈內部。這種寫法最簡潔,推薦在不需要精細控制時使用。
五、參數化游標(Parameterized Cursor)
有時我們需要根據不同的參數執行相同的查詢,這時可以定義帶參數的游標:
CREATE OR REPLACE PROCEDURE TESTING_PARAM (p_prefix VARCHAR2)
AS
CURSOR Hello_Cursor (cp_prefix VARCHAR2) IS
SELECT A FROM your_table WHERE A LIKE cp_prefix || '%';
v_date VARCHAR2(10);
BEGIN
OPEN Hello_Cursor(p_prefix);
LOOP
FETCH Hello_Cursor INTO v_date;
EXIT WHEN Hello_Cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_date);
END LOOP;
CLOSE Hello_Cursor;
END;
參數的作用域僅限於游標內部,每次開啟時傳入不同的值。
六、例外處理(Exception Handling)
當使用顯式游標時,常見的例外有:
NO_DATA_FOUND:通常出現在隱式游標中,顯式游標的FETCH不會引發此例外,只會導致%NOTFOUND為真。TOO_MANY_ROWS:出現在隱式游標中,當SELECT INTO回傳多行時觸發。INVALID_CURSOR:當游標未開啟就進行FETCH或CLOSE時觸發。
建議在程序中加入例外處理,確保資源釋放:
CREATE OR REPLACE PROCEDURE TESTING_SAFE
AS
F_DATE VARCHAR2(10);
CURSOR Hello_Cursor IS SELECT A FROM your_table WHERE A = 'ABC';
BEGIN
OPEN Hello_Cursor;
FETCH Hello_Cursor INTO F_DATE;
DBMS_OUTPUT.PUT_LINE(F_DATE);
CLOSE Hello_Cursor;
EXCEPTION
WHEN OTHERS THEN
IF Hello_Cursor%ISOPEN THEN
CLOSE Hello_Cursor;
END IF;
RAISE; -- 重新丟出例外
END;
七、最佳實踐小結
- 能用 FOR 迴圈就用 FOR 迴圈,程式碼簡潔,避免忘記關閉游標。
- 注意作用域,不要試圖在游標宣告之外存取它。
- 及時關閉游標,尤其是在例外處理中確保關閉。
- 使用游標屬性檢查狀態,避免無效操作。
- 參數化游標提高複用性,減少硬解析(Hard Parsing)。
八、總結
今天我們透過一個簡單的範例,詳細講解了 PL/SQL 顯式游標的宣告、開啟、提取、關閉四個步驟,並深入分析了游標及其變數的作用範圍。我們還介紹了游標屬性、迴圈提取、參數化游標和例外處理等進階內容。
游標是 PL/SQL 開發的基礎,掌握好游標的作用域和生命週期,能幫助你寫出更強健、更高效的程式碼。希望這篇文章對你有所幫助!如果你有任何問題或見解,歡迎在留言區討論。
參考資料:
- Oracle 官方文件:PL/SQL Language Reference
- 《Oracle PL/SQL Programming》by Steven Feuerstein
下次我們將探討游標變數(REF CURSOR),敬請期待!
本文由[Jayson Chan]原創,轉載請註明出處: freecodeinfosite.blogspot.com
留言
張貼留言