游標屬性速查表
| 屬性 | 適用對象 | 回傳型別 | 意義 | 初始值 |
%FOUND | 顯式/隱式游標 | BOOLEAN | 最近 FETCH/DML 找到/影響資料 | NULL(未執行前) |
%NOTFOUND | 顯式/隱式游標 | BOOLEAN | 最近 FETCH/DML 未找到/未影響資料 | NULL |
%ROWCOUNT | 顯式/隱式游標 | NUMBER | 已擷取/影響列數累計 | 0 |
%ISOPEN | 顯式游標/游標變數 | BOOLEAN | 游標是否開啟中 | FALSE |
屬性行為時序圖
顯式游標生命週期:
宣告 → %ISOPEN=FALSE, %ROWCOUNT=0, %FOUND=NULL, %NOTFOUND=NULL
↓ OPEN
%ISOPEN=TRUE
↓ FETCH (第1列)
%ROWCOUNT=1, %FOUND=TRUE, %NOTFOUND=FALSE
↓ FETCH (第N列)
%ROWCOUNT=N, %FOUND=TRUE, %NOTFOUND=FALSE
↓ FETCH (無資料)
%ROWCOUNT=N, %FOUND=FALSE, %NOTFOUND=TRUE ← 關鍵判斷點
↓ CLOSE
%ISOPEN=FALSE, 其他屬性保持最後狀態
隱式游標 (DML):
執行前 → %ROWCOUNT=0, %FOUND=NULL, %NOTFOUND=NULL
↓ UPDATE/INSERT/DELETE
%ROWCOUNT=影響列數, %FOUND=(>0), %NOTFOUND=(=0)
↓ 下一個 DML/SELECT
屬性被新語句覆蓋
完整實戰範例:顯式游標屬性示範
SET SERVEROUTPUT ON
DECLARE
-- 靜態游標
CURSOR emps IS
SELECT * FROM employees WHERE ROWNUM < 6 ORDER BY employee_id;
l_emp employees%ROWTYPE;
l_row_num NUMBER := 1;
BEGIN
-- 開啟游標
OPEN emps;
DBMS_OUTPUT.PUT_LINE('OPEN 後 %ISOPEN=' || CASE WHEN emps%ISOPEN THEN 'TRUE' ELSE 'FALSE' END);
-- 首次 FETCH
FETCH emps INTO l_emp;
DBMS_OUTPUT.PUT_LINE('首次 FETCH: %ROWCOUNT=' || emps%ROWCOUNT ||
' %FOUND=' || CASE WHEN emps%FOUND THEN 'TRUE' ELSE 'FALSE' END);
-- 迴圈處理
LOOP
IF emps%FOUND THEN
DBMS_OUTPUT.PUT_LINE('處理第 ' || l_row_num || ' 筆: ID=' || l_emp.employee_id ||
' 累計=' || emps%ROWCOUNT);
FETCH emps INTO l_emp;
l_row_num := l_row_num + 1;
ELSIF emps%NOTFOUND THEN
DBMS_OUTPUT.PUT_LINE('無更多資料,結束迴圈');
EXIT;
END IF;
END LOOP;
-- 關閉前檢查
IF emps%ISOPEN THEN
CLOSE emps;
DBMS_OUTPUT.PUT_LINE('游標已關閉');
END IF;
END;
/
進階應用模式
模式 1:%ROWCOUNT 進行進度追蹤
DECLARE
CURSOR c_big IS SELECT * FROM large_table;
TYPE t_tab IS TABLE OF large_table%ROWTYPE;
l_tab t_tab;
l_total NUMBER := 0;
BEGIN
OPEN c_big;
LOOP
FETCH c_big BULK COLLECT INTO l_tab LIMIT 10000;
EXIT WHEN l_tab.COUNT = 0;
-- 批次處理邏輯
FORALL i IN 1..l_tab.COUNT
INSERT INTO target VALUES l_tab(i);
l_total := l_total + l_tab.COUNT;
-- 每 10 萬列記錄進度
IF MOD(l_total, 100000) = 0 THEN
DBMS_OUTPUT.PUT_LINE('已處理: ' || l_total || ' 列');
-- 或寫入日誌表
END IF;
END LOOP;
CLOSE c_big;
DBMS_OUTPUT.PUT_LINE('總計處理: ' || l_total);
END;
/
模式 2:%NOTFOUND 作為迴圈終止條件(標準模式)
OPEN c_data;
LOOP
FETCH c_data INTO l_rec;
EXIT WHEN c_data%NOTFOUND; -- 最清晰的終止判斷
process(l_rec);
END LOOP;
CLOSE c_data;
模式 3:%ISOPEN 防禦性關閉(例外處理必備)
DECLARE
CURSOR c IS SELECT * FROM t;
BEGIN
OPEN c;
-- 處理邏輯...
EXCEPTION
WHEN OTHERS THEN
IF c%ISOPEN THEN CLOSE c; END IF; -- 確保資源釋放
RAISE;
END;
模式 4:隱式游標屬性檢查 DML 結果
BEGIN
DELETE FROM temp_table WHERE created_date < SYSDATE - 30;
IF SQL%ROWCOUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('無資料刪除');
ELSIF SQL%ROWCOUNT > 10000 THEN
DBMS_OUTPUT.PUT_LINE('大量刪除: ' || SQL%ROWCOUNT || ' 列,建議分批');
ELSE
DBMS_OUTPUT.PUT_LINE('正常刪除: ' || SQL%ROWCOUNT || ' 列');
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
常見誤區與陷阱
| 誤區 | 正確認知 |
%FOUND 在 OPEN 後立即為 TRUE | OPEN 後屬性未變,需 FETCH 後才更新 |
%ROWCOUNT 在 OPEN 後歸零 | 宣告時即為 0,OPEN 不改變 |
%NOTFOUND 等同於 NOT %FOUND | 初始皆為 NULL,NULL != TRUE/FALSE,需用 %NOTFOUND 判斷 |
隱式游標有 %ISOPEN | 隱式游標 %ISOPEN 永遠 FALSE |
FOR rec IN c LOOP 可存取 c%ROWCOUNT | FOR 迴圈隱式游標無名稱,無法存取屬性 |
BULK COLLECT 後 %ROWCOUNT 只加 1 | %ROWCOUNT 正確累加實際提取列數 |
除錯技巧:記錄游標狀態
CREATE OR REPLACE PROCEDURE debug_cursor_state(p_cursor_name VARCHAR2, p_cursor IN OUT SYS_REFCURSOR) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(p_cursor_name || ': %ISOPEN=' || CASE WHEN p_cursor%ISOPEN THEN 'T' ELSE 'F' END ||
' %ROWCOUNT=' || p_cursor%ROWCOUNT);
END;
/
-- 使用
debug_cursor_state('MAIN_CURSOR', c_main);
總結:屬性使用黃金法則
- 迴圈終止:永遠用
EXIT WHEN cursor%NOTFOUND
- 進度追蹤:用
cursor%ROWCOUNT
- 資源管理:例外區塊用
IF cursor%ISOPEN THEN CLOSE cursor
- DML 檢查:緊接 DML 後用
SQL%ROWCOUNT/%FOUND
- 初始狀態:記住
%FOUND/%NOTFOUND 初始為 NULL,非 TRUE/FALSE