本文原創:https://www.cnblogs.com/Marydon20170307/p/12869692.html 感謝博主分享
注意:原文中方式四FORALL處有語法錯誤,應該使用FOR。
1.情景展示
Oracle 遍歷游標的四種方式(for、fetch、while、bulk collect+forall)
2.問題分析
我們可以把游標想象成一張表,想要遍歷游標,就要取到游標的每行數據,所以問題的關鍵就成了:如何取到行數據?
3.解決方案
方式一:FOR 循環(推薦使用)
變形一:遍歷顯式游標
/* 如果是在存儲過程外使用顯式游標,需要使用DECLARE關鍵字 */ DECLARE /*創建游標*/ CURSOR CUR_FIRST_INDEX IS SELECT A.ID A_ID, --一級指標ID A.INDEXNAME A_INDEXNAME --一級指標名稱 FROM INDEX_A A ORDER BY A_ID; /*定義游標變量,該變量的類型為基於游標CUR_FIRST_INDEX的行記錄*/ ROW_CUR_FIRST_INDEX CUR_FIRST_INDEX%ROWTYPE; /*游標處理*/ BEGIN /*遍歷顯式游標*/ --FOR 循環 FOR ROW_CUR_FIRST_INDEX IN CUR_FIRST_INDEX LOOP --循環體 DBMS_OUTPUT.PUT_LINE('{"ID":"' || ROW_CUR_FIRST_INDEX.A_ID || '","名稱":"' || ROW_CUR_FIRST_INDEX.A_INDEXNAME || '"}'); END LOOP; END;
執行,輸出結果
變形二:遍歷隱式游標(推薦使用)
for循環遍歷游標,其實又可以分為兩種方式,一種是顯式游標的遍歷,另一種是隱式游標的遍歷。
/* 如果是在存儲過程外使用隱式游標,如果用不到變量無需聲明DECLARE關鍵字 */ /*游標處理*/ BEGIN /*遍歷隱式游標*/ --FOR 循環 FOR ROW_CUR_FIRST_INDEX IN (SELECT A.ID A_ID, --一級指標ID A.INDEXNAME A_INDEXNAME --一級指標名稱 FROM INDEX_A A ORDER BY A_ID) LOOP --循環體 DBMS_OUTPUT.PUT_LINE('{"ID":"' || ROW_CUR_FIRST_INDEX.A_ID || '","名稱":"' || ROW_CUR_FIRST_INDEX.A_INDEXNAME || '"}'); END LOOP; END;
隱式游標相較於顯式游標用法更加簡單,無需聲明直接調用即可。
方式二:FETCH 循環
/*游標聲明代碼和方式一一致,此處省略,直接展示游標處理代碼*/ BEGIN /*遍歷游標*/ --FETCH 循環 OPEN CUR_FIRST_INDEX; --必須要明確的打開和關閉游標 LOOP FETCH CUR_FIRST_INDEX INTO ROW_CUR_FIRST_INDEX; EXIT WHEN CUR_FIRST_INDEX%NOTFOUND; --循環體 DBMS_OUTPUT.PUT_LINE('{"ID":"' || ROW_CUR_FIRST_INDEX.A_ID || '","名稱":"' || ROW_CUR_FIRST_INDEX.A_INDEXNAME || '"}'); END LOOP; CLOSE CUR_FIRST_INDEX; END;
方式三:WHILE 循環
/*游標聲明代碼和方式一一致,此處省略,直接展示游標處理代碼*/ BEGIN /*遍歷游標*/ OPEN CUR_FIRST_INDEX; --必須要明確的打開和關閉游標 FETCH CUR_FIRST_INDEX INTO ROW_CUR_FIRST_INDEX; WHILE CUR_FIRST_INDEX%FOUND LOOP --循環體 DBMS_OUTPUT.PUT_LINE('{"ID":"' || ROW_CUR_FIRST_INDEX.A_ID || '","名稱":"' || ROW_CUR_FIRST_INDEX.A_INDEXNAME || '"}'); FETCH CUR_FIRST_INDEX INTO ROW_CUR_FIRST_INDEX; END LOOP; CLOSE CUR_FIRST_INDEX; END;
注意:使用while循環時,需要fetch兩次。
方式四:BULK COLLECT+FORALL(速度最快)
/* 如果是在存儲過程外使用顯示游標,需要使用DECLARE關鍵字 */ /*聲明游標*/ DECLARE /*創建顯式游標*/ CURSOR CUR_FIRST_INDEX IS SELECT A.ID A_ID, --一級指標ID A.INDEXNAME A_INDEXNAME --一級指標名稱 FROM INDEX_A A ORDER BY A_ID; /*定義表類型,該表的表結構為游標CUR_FIRST_INDEX的行記錄(可以存儲多條游標記錄)*/ TYPE TABLE_CUR_FIRST_INDEX IS TABLE OF CUR_FIRST_INDEX%ROWTYPE; /* 聲明表變量*/ TAB_FIRST_INDEX TABLE_CUR_FIRST_INDEX; /*游標處理過程*/ BEGIN /*遍歷游標*/ OPEN CUR_FIRST_INDEX; LOOP --將n行游標數據放到表中 FETCH CUR_FIRST_INDEX BULK COLLECT INTO TAB_FIRST_INDEX LIMIT 1; -- 數據量太少,僅當前測試使用哦,實際開發建議 500 左右 -- 退出條件 EXIT WHEN TAB_FIRST_INDEX.COUNT = 0; --循環表數據 FOR I IN TAB_FIRST_INDEX.FIRST .. TAB_FIRST_INDEX.LAST LOOP DBMS_OUTPUT.PUT_LINE('{"ID":"' || TAB_FIRST_INDEX(I).A_ID || '","名稱":"' || TAB_FIRST_INDEX(I).A_INDEXNAME || '"}'); END LOOP; END LOOP; CLOSE CUR_FIRST_INDEX; END;
注意上面語句的FOR,原為寫成了FORALL是不正確的(我被成功帶到溝里才發現不對)。
以下內容出自另一篇博客,更加全面詳細可參考 https://www.cnblogs.com/hellokitty1/p/4584333.html
DECLARE CURSOR emp_cur IS SELECT empno, ename, hiredate FROM emp; TYPE emp_rec_type IS RECORD ( empno emp.empno%TYPE, ename emp.ename%TYPE , hiredate emp.hiredate%TYPE ); -- 定義基於記錄的嵌套表 TYPE nested_emp_type IS TABLE OF emp_rec_type; -- 聲明集合變量 emp_tab nested_emp_type; -- 定義了一個變量來作為limit的值 v_limit PLS_INTEGER := 5; -- 定義變量來記錄FETCH次數 v_counter PLS_INTEGER := 0; BEGIN OPEN emp_cur; LOOP -- fetch時使用了BULK COLLECT子句 FETCH emp_cur BULK COLLECT INTO emp_tab LIMIT v_limit; -- 使用limit子句限制提取數據量 EXIT WHEN emp_tab.COUNT = 0; -- 注意此時游標退出使用了emp_tab.COUNT,而不是emp_cur%notfound v_counter := v_counter + 1; -- 記錄使用LIMIT之后fetch的次數 FOR i IN emp_tab.FIRST .. emp_tab.LAST LOOP DBMS_OUTPUT.PUT_LINE( '當前記錄: ' ||emp_tab(i).empno||CHR(9) ||emp_tab(i).ename||CHR(9) ||emp_tab(i).hiredate); END LOOP; END LOOP; CLOSE emp_cur; DBMS_OUTPUT.put_line( '總共獲取次數為:' || v_counter ); END;
4.總結
使用for循環的優勢在於:
不需要手動打開&關閉游標(聲明游標的開啟和關閉);
不需要手動捕獲數據(自動將數據fetch到記錄型變量);
不需要關注何時要退出,也就是不需要寫退出循環的滿足條件(遍歷完成就會退出)。
第4方式與前3種的區別在於:
前三種的游標變量:ROW_CUR_FIRST_INDEX,只能存儲游標的一條數據;
第四種的表變量:TAB_FIRST_INDEX,可以存儲游標的多條數據。
大數據批量處理的時候,第4種方式的優勢將會凸顯出來。