游標是 PL/SQL 處理多行查詢結果的核心機制。本文系統梳理隱式游標屬性、顯式游標完整生命週期、參數化游標、FOR 迴圈簡化寫法、REF CURSOR 動態綁定與動態 SQL 進階應用,並提供可直接執行的範例碼。
Oracle 為每執行一條 DML(INSERT/UPDATE/DELETE)或 SELECT INTO 語句自動建立隱式游標,名為 SQL,僅存在於該語句執行期間。
BEGIN
UPDATE employees2 SET first_name = first_name || ' t' WHERE employee_id = 2;
IF SQL%FOUND THEN
DBMS_OUTPUT.PUT_LINE('資料已更新');
ELSE
DBMS_OUTPUT.PUT_LINE('查無資料');
END IF;
END;
/
BEGIN
UPDATE employees2 SET first_name = 'john' WHERE employee_id = 2;
IF SQL%ROWCOUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('更新了 ' || SQL%ROWCOUNT || ' 筆');
END IF;
END;
/
Oracle 執行完 SQL 即自動關閉隱式游標,因此 SQL%ISOPEN 永遠回傳 FALSE,僅適用於顯式游標檢查。
顯式游標需經歷 DECLARE → OPEN → FETCH → CLOSE 四階段,適用於需逐行處理邏輯的場景。
DECLARE
CURSOR emp_cur IS
SELECT * FROM employees2 WHERE division_id = 'SUP';
v_rec employees2%ROWTYPE;
BEGIN
OPEN emp_cur;
FETCH emp_cur INTO v_rec;
WHILE emp_cur%FOUND LOOP
DBMS_OUTPUT.PUT_LINE('ID=' || v_rec.employee_id ||
' Div=' || v_rec.division_id ||
' Name=' || v_rec.first_name);
FETCH emp_cur INTO v_rec;
END LOOP;
CLOSE emp_cur;
END;
/
DECLARE
CURSOR emp_cur IS SELECT first_name FROM employees2 WHERE division_id = 'SUP';
v_name employees2.first_name%TYPE;
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur INTO v_name;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Row ' || emp_cur%ROWCOUNT || ': ' || v_name);
END LOOP;
CLOSE emp_cur;
END;
/
DECLARE
CURSOR c_emp (p_id NUMBER) IS
SELECT first_name FROM employees2 WHERE employee_id = p_id;
v_name employees2.first_name%TYPE;
BEGIN
IF c_emp%ISOPEN THEN
DBMS_OUTPUT.PUT_LINE('游標已開啟');
ELSE
OPEN c_emp(2);
FETCH c_emp INTO v_name;
DBMS_OUTPUT.PUT_LINE(v_name);
CLOSE c_emp;
END IF;
END;
/
Oracle 9i 後支援 FOR record IN cursor_name LOOP,自動處理 OPEN/FETCH/CLOSE,程式碼更簡潔。
DECLARE
CURSOR c_emp (p_id NUMBER) IS
SELECT first_name FROM employees2 WHERE employee_id = p_id;
BEGIN
FOR rec IN c_emp(2) LOOP
DBMS_OUTPUT.PUT_LINE(rec.first_name);
END LOOP;
END;
/
SET SERVEROUTPUT ON;
DECLARE
v_div_id VARCHAR2(5) := '&division_id'; -- SQL*Plus 替換變數
CURSOR c_emp (p_div VARCHAR2) IS
SELECT employee_id, division_id, job_id, first_name, last_name
FROM employees2
WHERE division_id = p_div;
BEGIN
FOR rec IN c_emp(v_div_id) LOOP
DBMS_OUTPUT.PUT_LINE('ID=' || rec.employee_id);
END LOOP;
END;
/
REF CURSOR 是指向游標的指標型別,允許在執行期動態決定查詢語句,常用於回傳結果集給應用層(Java/.NET/Python)或跨 PL/SQL 區塊傳遞游標。
TYPE t_cur IS REF CURSOR RETURN employees2%ROWTYPE; 限定回傳結構。TYPE t_cur IS REF CURSOR; 可指向任意查詢,彈性最高。SET SERVEROUTPUT ON;
ACCEPT tab PROMPT '查詢員工(E)或部門(D):';
DECLARE
TYPE ref_cur_t IS REF CURSOR; -- 弱型別
v_cur ref_cur_t;
v_id NUMBER;
v_name VARCHAR2(100);
v_sel VARCHAR2(1) := UPPER(SUBSTR('&tab', 1, 1));
BEGIN
IF v_sel = 'E' THEN
OPEN v_cur FOR SELECT empno, ename FROM emp;
DBMS_OUTPUT.PUT_LINE('=== 員工資訊 ===');
ELSIF v_sel = 'D' THEN
OPEN v_cur FOR SELECT deptno, dname FROM dept;
DBMS_OUTPUT.PUT_LINE('=== 部門資訊 ===');
ELSE
DBMS_OUTPUT.PUT_LINE('請輸入 E 或 D');
RETURN;
END IF;
LOOP
FETCH v_cur INTO v_id, v_name;
EXIT WHEN v_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('#' || v_id || ':' || v_name);
END LOOP;
CLOSE v_cur;
END;
/
SET SERVEROUTPUT ON;
VARIABLE max_sal NUMBER;
EXECUTE :max_sal := 2500;
DECLARE
TYPE c_type IS REF CURSOR;
v_cur c_type;
v_rec emp%ROWTYPE;
v_sal NUMBER := :max_sal;
BEGIN
OPEN v_cur FOR
'SELECT * FROM emp WHERE sal > :1 ORDER BY sal DESC'
USING v_sal;
DBMS_OUTPUT.PUT_LINE('薪水大於 ' || v_sal || ' 的員工:');
LOOP
FETCH v_cur INTO v_rec;
EXIT WHEN v_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('編號:' || v_rec.empno ||
' 姓名:' || v_rec.ename ||
' 薪水:' || v_rec.sal);
END LOOP;
CLOSE v_cur;
END;
/
在游標查詢後加上 FOR UPDATE 可鎖定提取的資料列,防止併發修改衝突。
FOR UPDATE NOWAIT:資源被鎖時立即報錯 (ORA-00054)。FOR UPDATE WAIT 5:最多等待 5 秒再報錯。FOR UPDATE SKIP LOCKED (11g+):跳過已鎖定列,適合佇列處理。DECLARE
CURSOR c_lock IS
SELECT * FROM employees2 WHERE employee_id = 2 FOR UPDATE WAIT 3;
v_rec employees2%ROWTYPE;
BEGIN
OPEN c_lock;
FETCH c_lock INTO v_rec;
-- 執行更新邏輯
UPDATE employees2 SET salary = salary * 1.1 WHERE CURRENT OF c_lock;
COMMIT;
CLOSE c_lock;
END;
/
| 場景 | 建議用法 |
|---|---|
| 單筆 DML 後判斷影響 | 隱式游標 SQL%FOUND / %ROWCOUNT |
| 需逐行複雜邏輯處理 | 顯式游標 OPEN/FETCH/CLOSE |
| 單純遍歷輸出/計算 | FOR 迴圈游標(最簡潔) |
| 查詢條件/欄位動態決定 | REF CURSOR + 動態 SQL |
| 回傳結果集給外部應用 | OUT 參數用弱型別 REF CURSOR |
| 佇列/批次處理需跳過鎖定列 | FOR UPDATE SKIP LOCKED (11g+) |
熟練掌握各類游標技術,能讓 PL/SQL 程式在效能、併發與維護性間取得最佳平衡。