三大迴圈類型對照表
| 迴圈類型 | 適用場景 | 終止條件 | 效能特性 |
| 基本 LOOP | 至少執行一次、複雜終止邏輯 | EXIT WHEN / EXIT | 手動控制、靈活 |
| WHILE LOOP | 先檢查條件再執行、可能零次 | 條件為 FALSE | 條件前置檢查 |
| FOR LOOP (數值) | 已知迭代次數、計數器需求 | 達到上限 | 自動管理計數器、不可修改 |
| FOR LOOP (游標) | 查詢結果集遍歷 | 游標耗盡 | 自動 OPEN/FETCH/CLOSE |
1. 基本 LOOP
語法
LOOP
statements;
EXIT [WHEN condition];
END LOOP;
範例:累加 1 到 5
DECLARE
v_sum NUMBER := 0;
v_i NUMBER := 1;
BEGIN
LOOP
v_sum := v_sum + v_i;
v_i := v_i + 1;
EXIT WHEN v_i > 5;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Sum 1-5 = ' || v_sum); -- 15
END;
/
範例:資料驗證迴圈(至少執行一次)
DECLARE
v_input VARCHAR2(100);
v_valid BOOLEAN := FALSE;
BEGIN
LOOP
v_input := get_user_input(); -- 假設函數
IF validate(v_input) THEN
v_valid := TRUE;
EXIT;
ELSE
DBMS_OUTPUT.PUT_LINE('輸入無效,請重試');
END IF;
END LOOP;
process(v_input);
END;
/
2. WHILE LOOP
語法
WHILE condition LOOP
statements;
END LOOP;
範例:相同邏輯用 WHILE
DECLARE
v_sum NUMBER := 0;
v_i NUMBER := 1;
BEGIN
WHILE v_i <= 5 LOOP
v_sum := v_sum + v_i;
v_i := v_i + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('Sum 1-5 = ' || v_sum);
END;
/
範例:游標式 WHILE (手動控制)
DECLARE
CURSOR c_emp IS SELECT * FROM employees WHERE department_id = 30;
v_emp employees%ROWTYPE;
BEGIN
OPEN c_emp;
FETCH c_emp INTO v_emp;
WHILE c_emp%FOUND LOOP
process_emp(v_emp);
FETCH c_emp INTO v_emp;
END LOOP;
CLOSE c_emp;
END;
/
-- 等同於 FOR 迴圈但手動控制,適合複雜邏輯
3. FOR LOOP (數值範圍)
語法
FOR counter IN [REVERSE] lower_bound .. upper_bound LOOP
statements;
END LOOP;
關鍵特性
counter 隱式宣告為 INTEGER,唯讀,不可在迴圈中修改
- 範圍在迴圈開始前評估一次,不受迴圈內變更影響
REVERSE 遞減迭代 (upper_bound 到 lower_bound)
- 下限 > 上限時,迴圈零次執行 (不報錯)
範例:正向與反向
BEGIN
DBMS_OUTPUT.PUT_LINE('正向:');
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
DBMS_OUTPUT.PUT_LINE('反向:');
FOR i IN REVERSE 1..3 LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
END;
/
-- 輸出: 1,2,3 然後 3,2,1
範例:動態邊界
DECLARE
v_start NUMBER := 1;
v_end NUMBER := 5;
BEGIN
FOR i IN v_start..v_end LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
END;
/
4. FOR LOOP (游標) - 隱式游標
語法
FOR record_variable IN (SELECT_statement) LOOP
statements;
END LOOP;
-- 或使用已宣告游標
FOR record_variable IN cursor_name[(parameters)] LOOP
statements;
END LOOP;
範例:查詢遍歷
BEGIN
FOR dept_rec IN (SELECT department_id, department_name FROM departments) LOOP
DBMS_OUTPUT.PUT_LINE(dept_rec.department_id || ': ' || dept_rec.department_name);
END LOOP;
END;
/
範例:參數化游標 FOR 迴圈
DECLARE
CURSOR c_emp(p_dept_id NUMBER) IS
SELECT employee_id, first_name, salary
FROM employees WHERE department_id = p_dept_id;
BEGIN
FOR emp_rec IN c_emp(30) LOOP
DBMS_OUTPUT.PUT_LINE(emp_rec.employee_id || ': ' || emp_rec.first_name || ' - ' || emp_rec.salary);
END LOOP;
-- 同一游標不同參數
FOR emp_rec IN c_emp(90) LOOP
DBMS_OUTPUT.PUT_LINE(emp_rec.employee_id || ': ' || emp_rec.first_name);
END LOOP;
END;
/
進階:%ROWTYPE 變數搭配
DECLARE
CURSOR c_emp IS SELECT * FROM employees WHERE department_id = 30;
v_emp c_emp%ROWTYPE; -- 使用游標型別
BEGIN
FOR v_emp IN c_emp LOOP
DBMS_OUTPUT.PUT_LINE(v_emp.employee_id);
END LOOP;
END;
/
迴圈控制語句
| 語句 | 作用 | 適用迴圈 |
EXIT | 立即離開當前迴圈 | 所有 |
EXIT WHEN condition | 條件成立時離開 | 所有 |
CONTINUE | 跳過本次迭代剩餘語句,進入下一次 | 11g+ 所有 |
CONTINUE WHEN condition | 條件成立時跳過 | 11g+ 所有 |
GOTO label | 跳轉到標籤 (不推薦) | 所有 |
CONTINUE 範例 (11g+)
BEGIN
FOR i IN 1..10 LOOP
CONTINUE WHEN MOD(i, 2) = 0; -- 跳過偶數
DBMS_OUTPUT.PUT_LINE('奇數: ' || i);
END LOOP;
END;
/
巢狀迴圈與標籤
BEGIN
<>
FOR i IN 1..3 LOOP
<>
FOR j IN 1..3 LOOP
IF i = 2 AND j = 2 THEN
EXIT outer_loop; -- 直接跳出外層
END IF;
DBMS_OUTPUT.PUT_LINE('i=' || i || ' j=' || j);
END LOOP inner_loop;
END LOOP outer_loop;
END;
/
效能對比:隱式 FOR 迴圈 vs 顯式 BULK COLLECT
-- 方案 A:隱式 FOR 迴圈 (逐列 Context Switch)
DECLARE
v_start NUMBER;
BEGIN
v_start := DBMS_UTILITY.GET_TIME;
FOR r IN (SELECT * FROM large_table) LOOP
NULL; -- 處理邏輯
END LOOP;
DBMS_OUTPUT.PUT_LINE('FOR LOOP: ' || (DBMS_UTILITY.GET_TIME - v_start) || ' cs');
END;
/
-- 方案 B:顯式游標 + BULK COLLECT (批次 1000 列)
DECLARE
CURSOR c IS SELECT * FROM large_table;
TYPE t_tab IS TABLE OF large_table%ROWTYPE;
l_tab t_tab;
v_start NUMBER;
BEGIN
v_start := DBMS_UTILITY.GET_TIME;
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_tab LIMIT 1000;
EXIT WHEN l_tab.COUNT = 0;
FORALL i IN 1..l_tab.COUNT
NULL; -- 批次處理
END LOOP;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('BULK COLLECT: ' || (DBMS_UTILITY.GET_TIME - v_start) || ' cs');
END;
/
-- 典型效能提升: 10x - 100x
最佳實務檢核表
- [ ] 已知次數迭代 → FOR 數值迴圈
- [ ] 查詢結果遍歷 → FOR 游標迴圈 (簡單) 或 顯式游標 + BULK COLLECT (大量)
- [ ] 至少一次、複雜終止 → 基本 LOOP + EXIT WHEN
- [ ] 先檢查條件 → WHILE LOOP
- [ ] 大量資料 DML → 必須用 BULK COLLECT + FORALL
- [ ] 避免在迴圈中 COMMIT (除非業務要求)
- [ ] 避免修改 FOR 迴圈計數器 (編譯錯誤)
- [ ] 巢狀迴圈使用標籤管理 EXIT/CONTINUE 範圍
- [ ] 11z+ 善用 CONTINUE WHEN 簡化邏輯
常見錯誤
| 錯誤 | 原因 | 修正 |
PLS-00363: expression 'I' cannot be used as an assignment target | 修改 FOR 迴圈計數器 | 改用基本 LOOP 或 WHILE 自行管理變數 |
| 無限迴圈 | EXIT 條件永不滿足 / 忘記 FETCH | 檢查邏輯、加入安全計數器限制最大迭代 |
ORA-01001: invalid cursor | 迴圈中 COMMIT 導致游標失效 | 移出 COMMIT 或用 FOR UPDATE + WHERE CURRENT OF |
| FOR 迴圈零次執行無提示 | 下限 > 上限 | 預期需至少一次時改用基本 LOOP |