總的目錄和進(jìn)度,請參見開始讀 Oracle PL/SQL Programming 第6版
本章探討 PL/SQL 的迭代控制結(jié)構(gòu)(也稱為循環(huán)),它允許您重復(fù)執(zhí)行相同的代碼。
PL/SQL 提供了三種不同類型的循環(huán)結(jié)構(gòu):
- 簡單或無限循環(huán)
- FOR 循環(huán)(數(shù)字和游標(biāo))
- WHILE 循環(huán)
每種類型的循環(huán)都是針對特定目的而設(shè)計(jì):
屬性 | 描述 |
---|---|
循環(huán)如何終止 | 循環(huán)重復(fù)執(zhí)行代碼。 如何使循環(huán)停止執(zhí)行其主體? |
何時(shí)進(jìn)行終止測試 | 終止測試是在循環(huán)的開始還是結(jié)束時(shí)進(jìn)行? 后果是什么? |
使用此循環(huán)的原因 | 您應(yīng)該考慮哪些特殊因素來確定此循環(huán)是否適合您的情況? |
Loop Basics
為什么存在三種不同類型的循環(huán)? 為了給您提供靈活性,為了代碼簡單,更容易理解和維護(hù)。
Examples of Different Loops
簡單或無限循環(huán):
LOOP
EXIT WHEN i > 100;
...;
i:= i + 1;
END LOOP;
FOR循環(huán)(數(shù)字):
FOR i IN 1 .. 100
LOOP
...;
END LOOP;
FOR循環(huán)(游標(biāo)):
FOR i IN (SELECT ...)
LOOP
...;
END LOOP;
WHILE循環(huán):
WHILE (i <= 100)
LOOP
...;
END LOOP;
Structure of PL/SQL Loops
雖然這三個(gè)循環(huán)結(jié)構(gòu)之間存在差異,但每個(gè)循環(huán)都有兩個(gè)部分:
-
循環(huán)邊界
它由啟動(dòng)循環(huán)的保留字、導(dǎo)致循環(huán)終止的條件以及結(jié)束循環(huán)的 END LOOP 語句組成。 -
循環(huán)體
這是循環(huán)邊界內(nèi)的可執(zhí)行語句序列,在循環(huán)的每次迭代中執(zhí)行。
The Simple Loop
形式為:
LOOP
執(zhí)行語句...
END LOOP;
僅當(dāng)執(zhí)行語句包含EXIT或EXIT WHEN時(shí),才會(huì)退出循環(huán);否則為無限循環(huán)。
EXIT;
EXIT WHEN 判斷條件;
EXIT WHEN等同于IF-THEN加EXIT。
使用簡單循環(huán)的場景:
- 希望循環(huán)至少執(zhí)行1次
- 無法確定循環(huán)需執(zhí)行多少次
當(dāng)存在單個(gè)條件表達(dá)式來確定循環(huán)是否應(yīng)終止時(shí),最好使用 EXIT WHEN。 在有多個(gè)退出條件的情況下,或者當(dāng)您需要根據(jù)不同的條件設(shè)置從循環(huán)中退出時(shí),最好使用 IF 或 CASE 語句,然后加上EXIT 語句 。
Emulating a REPEAT UNTIL Loop
PL/SQL中沒有WHILE…UNTIL語句,但有類似的實(shí)現(xiàn):
LOOP
...
EXIT WHEN ...;
END LOOP;
The Intentionally Infinite Loop
無限循環(huán)可能存在,但通常都會(huì)加sleep語句:
LOOP
執(zhí)行操作;
DBMS_LOCK.sleep(10);
END LOOP;
如何終止無限循環(huán)?在交互式PL/SQL中可以用“Ctrl+C”,在后臺(tái)運(yùn)行的程序可以用操作系統(tǒng)的kill命令,注意數(shù)據(jù)庫中的ALTER SYSTEM KILL SESSION
命令不一定可以終止無限循環(huán)。但是kill命令有可能誤殺,例如在共享服務(wù)器模式下。
作者推薦的方式為利用PL/SQL中的管道,這類似于Shell編程中的信號(hào):
DECLARE
pipename CONSTANT VARCHAR2(12) := 'signaler';
result INTEGER;
pipebuf VARCHAR2(64);
BEGIN
/* create private pipe with a known name */
result := DBMS_PIPE.create_pipe(pipename);
LOOP
DBMS_OUTPUT.PUT_LINE('I''m doing works ...'); -- 請?zhí)鎿Q為實(shí)際執(zhí)行的操作
DBMS_LOCK.sleep(5);
/* see if there is a message on the pipe */
IF DBMS_PIPE.receive_message(pipename, 0) = 0
THEN
/* interpret the message and act accordingly */
DBMS_PIPE.unpack_message(pipebuf);
IF pipebuf = 'stop'
THEN
DBMS_OUTPUT.PUT_LINE('Exiting ...');
EXIT;
END IF;
END IF;
END LOOP;
END;
上面是接收信號(hào)的程序,下面是發(fā)送信號(hào)的程序:
DECLARE
pipename VARCHAR2 (12) := 'signaler';
result INTEGER := DBMS_PIPE.create_pipe (pipename);
BEGIN
DBMS_PIPE.pack_message ('stop');
result := DBMS_PIPE.send_message (pipename);
END;
測試在SQL Plus中通過,但SQL Developer沒有通過,不知為何。
以下為成功時(shí)的輸出:
I'm doing works ...
I'm doing works ...
I'm doing works ...
I'm doing works ...
I'm doing works ...
Exiting ...
PL/SQL procedure successfully completed.
此示例使用私有管道,因此發(fā)送和接收程序需為同一用戶。 另請注意,私有管道的數(shù)據(jù)庫命名空間在當(dāng)前用戶運(yùn)行的所有會(huì)話中是全局的。
The WHILE Loop
如事先不知道循環(huán)執(zhí)行的次數(shù)(可以一次都不執(zhí)行),則可使用 WHILE 循環(huán):
WHILE 布爾變量|表達(dá)式
LOOP
執(zhí)行語句
END LOOP;
The Numeric FOR Loop
PL/SQL FOR 循環(huán)有兩種:數(shù)字FOR 循環(huán)和游標(biāo)FOR 循環(huán)。 數(shù)字 FOR 循環(huán)是傳統(tǒng)且熟悉的“計(jì)數(shù)”循環(huán)。 FOR 循環(huán)的迭代次數(shù)在循環(huán)開始時(shí)就已知。
范圍方案隱式聲明循環(huán)索引(如果尚未聲明),指定范圍的起點(diǎn)和終點(diǎn),并可選擇指定循環(huán)索引進(jìn)行的順序(從最低到最高或從最高到最低)。
FOR loop index IN [REVERSE] lowest number .. highest number
LOOP
executable statement(s)
END LOOP;
Rules for Numeric FOR Loops
- 不要聲明循環(huán)索引。 PL/SQL 自動(dòng)且隱式地將其聲明為數(shù)據(jù)類型為 INTEGER 的局部變量。 該索引的范圍是循環(huán)本身; 您不能在循環(huán)外引用循環(huán)索引。
- 范圍方案中使用的表達(dá)式(最低和最高邊界)在循環(huán)開始時(shí)評估一次。 在循環(huán)執(zhí)行期間不會(huì)重新評估范圍。所以,后面改了也不會(huì)生效。
- 切勿在循環(huán)內(nèi)更改循環(huán)索引或范圍邊界的值。
Examples of Numeric FOR Loops
一個(gè)倒計(jì)時(shí)程序:
BEGIN
FOR i IN REVERSE 1 .. 10
LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
END;
還有范圍可以由常數(shù)定義,也可以由變量或表達(dá)式定義。
Handling Nontrivial Increments
和C語言不一樣,PL/SQL中的循環(huán)索引的步增/步減永遠(yuǎn)是1。所以,如果增減量為非1,則需要另加IF判斷,如MOD函數(shù)。
The Cursor FOR Loop
游標(biāo) FOR 循環(huán)是與直接合并在循環(huán)邊界內(nèi)的顯式游標(biāo)或 SELECT 語句關(guān)聯(lián)(并實(shí)際由其定義)的循環(huán)。 僅當(dāng)需要從游標(biāo)中獲取并處理每條記錄時(shí)(游標(biāo)經(jīng)常出現(xiàn)這種情況),才使用游標(biāo) FOR 循環(huán)。
游標(biāo) FOR 循環(huán)充分利用了過程結(jié)構(gòu)與 SQL 數(shù)據(jù)庫語言的強(qiáng)大功能的緊密且有效的集成。 它減少了從游標(biāo)獲取數(shù)據(jù)所需編寫的代碼量。 它大大減少了在編程中引入循環(huán)錯(cuò)誤的機(jī)會(huì),而循環(huán)是程序中最容易出錯(cuò)的部分之一。
FOR record IN { cursor_name | (explicit SELECT statement) }
LOOP
executable statement(s)
END LOOP;
其中 record 是由 PL/SQL 使用 %ROWTYPE 屬性針對cursor_name 指定的游標(biāo)隱式聲明的記錄。
不要顯式聲明與循環(huán)索引記錄同名的記錄。 它不是必需的(PL/SQL 隱式聲明它在循環(huán)中使用)并且可能導(dǎo)致邏輯錯(cuò)誤。 有關(guān)在循環(huán)執(zhí)行之外或之后訪問有關(guān)游標(biāo) FOR 循環(huán)記錄的信息的提示,請看本文后面部分:Obtaining Information About FOR Loop Execution。
Example of Cursor FOR Loops
沒有用游標(biāo)FOR循環(huán)之前:
SET SERVEROUTPUT ON
DECLARE
CURSOR hr_cur IS
SELECT first_name, last_name
FROM employees WHERE department_id = 100;
hr_rec hr_cur%ROWTYPE;
BEGIN
OPEN hr_cur;
LOOP
FETCH hr_cur INTO hr_rec;
EXIT WHEN hr_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(hr_rec.first_name || ' ' || hr_rec.last_name);
END LOOP;
CLOSE hr_cur;
END;
用游標(biāo)FOR循環(huán)后,簡潔很多! 無需記錄的聲明。 OPEN、FETCH 和 CLOSE 語句已消失。 不再需要檢查 %NOTFOUND 屬性。:
SET SERVEROUTPUT ON
DECLARE
CURSOR hr_cur IS
SELECT first_name, last_name
FROM employees WHERE department_id = 100;
BEGIN
FOR hr_rec IN hr_cur
LOOP
DBMS_OUTPUT.PUT_LINE(hr_rec.first_name || ' ' || hr_rec.last_name);
END LOOP;
END;
與所有其他游標(biāo)一樣,您可以在游標(biāo) FOR 循環(huán)中將參數(shù)傳遞給游標(biāo)?。 如果光標(biāo)選擇列表中的任何列是表達(dá)式,請記住您必須在選擇列表中為該表達(dá)式指定別名。 在循環(huán)內(nèi),訪問游標(biāo)記錄中特定值的唯一方法是使用點(diǎn)符號(hào)(record_name.column_name,如 ocupancy_rec.room_number),因此您需要一個(gè)與表達(dá)式關(guān)聯(lián)的列名。
Loop Labels
您可以使用標(biāo)簽為循環(huán)命名。 格式為:
<<label_name>>
<<label_name>> 必須出現(xiàn)在循環(huán)體第一個(gè)語句之前,而在END LOOP也可以加label_name,但不是必須。
循環(huán)標(biāo)簽作用:
- 代碼更容易維護(hù)和調(diào)試。就好像括號(hào)必須成對出現(xiàn)。
- 可使用標(biāo)簽來限定循環(huán)索引變量的名稱,這有助于提高可讀性。
- 當(dāng)您有嵌套循環(huán)時(shí),您可以使用標(biāo)簽來提高可讀性并增強(qiáng)對循環(huán)執(zhí)行的控制。如
EXIT loop_label [WHEN condition];
不過這種用戶和GOTO一樣不建議。
The CONTINUE Statement
CONTINUE 語句退出循環(huán)的當(dāng)前迭代,并立即繼續(xù)該循環(huán)的下一次迭代。 該語句有兩種形式,就像 EXIT 一樣:無條件 CONTINUE 和條件 CONTINUE WHEN。
下例演示了利用CONTINUE實(shí)現(xiàn)步進(jìn)為3:
BEGIN
FOR l_index IN 1 .. 10
LOOP
CONTINUE WHEN MOD (l_index - 1, 3) != 0;
DBMS_OUTPUT.PUT_LINE ('Loop index = ' || TO_CHAR (l_index));
END LOOP;
END;
/
輸出為:
Loop index = 1
Loop index = 4
Loop index = 7
Loop index = 10
您還可以使用 CONTINUE 終止內(nèi)部循環(huán)并立即繼續(xù)進(jìn)行外部循環(huán)體的下一次迭代。 為此,您需要使用標(biāo)簽為外部循環(huán)命名。
IS CONTINUE AS BAD AS GOTO?
continue 語句不能濫用,但用對地方則很有價(jià)值,因?yàn)樗勾a更短,使代碼更易于閱讀,并減少了對布爾變量的需求。
作者舉了使用和不使用continue的2個(gè)例子作為對比。我看懂了,并認(rèn)可。
使用continue的例子:
LOOP
EXIT WHEN exit_condition_met;
CONTINUE WHEN condition1;
CONTINUE WHEN condition2;
setup_steps_here;
IF condition4 THEN
action4_executed;
CONTINUE;
END IF;
IF condition5 THEN
action5_executed;
CONTINUE; -- Not strictly required.
END IF;
END LOOP;
如果不使用continue:
LOOP
EXIT WHEN exit_condition_met;
IF condition1
THEN
NULL;
ELSIF condition2
THEN
NULL;
ELSE
setup_steps_here;
IF condition4 THEN
action4_executed;
ELSIF condition5 THEN
action5_executed;
END IF;
END IF;
END LOOP;
Tips for Iterative Processing
循環(huán)是非常強(qiáng)大且有用的結(jié)構(gòu),但您應(yīng)該謹(jǐn)慎使用它們。 程序中的性能問題通??梢宰匪莸窖h(huán),并且循環(huán)中的任何問題都會(huì)因其重復(fù)執(zhí)行而被放大。 確定何時(shí)停止循環(huán)的邏輯可能非常復(fù)雜。 本節(jié)提供了一些關(guān)于如何編寫干凈、易于理解且易于維護(hù)的循環(huán)的技巧。
Use Understandable Names for Loop Indexes
使用有意義的循環(huán)索引變量名稱,而非簡單的i,j,k。
The Proper Way to Say Goodbye
結(jié)構(gòu)化編程的一個(gè)重要且基本的原則是“一進(jìn)一出”; 也就是說,程序應(yīng)該有一個(gè)入口點(diǎn)和一個(gè)出口點(diǎn)。一個(gè)入口是必然的,這里講的是如何避免多個(gè)出口。
您應(yīng)該遵循以下循環(huán)終止準(zhǔn)則:
- 不要在 FOR 和 WHILE 循環(huán)中使用 EXIT 或 EXIT WHEN 語句。
- 不要在循環(huán)中使用 RETURN 或 GOTO 語句,這同樣會(huì)導(dǎo)致循環(huán)過早、非結(jié)構(gòu)化終止。
如果需要根據(jù)游標(biāo) FOR 循環(huán)獲取的信息終止循環(huán)(例如當(dāng)取得值的合計(jì)大于某值時(shí)退出),則應(yīng)使用 WHILE 循環(huán)或簡單循環(huán)代替。 那么代碼的結(jié)構(gòu)就會(huì)更清楚地表達(dá)你的意圖。
Obtaining Information About FOR Loop Execution
FOR 循環(huán)是方便且簡潔的結(jié)構(gòu),對于游標(biāo) FOR 循環(huán)尤其如此。 然而,有一個(gè)權(quán)衡:數(shù)據(jù)庫自動(dòng)為您完成大量工作,但您在循環(huán)終止后對有關(guān)循環(huán)最終結(jié)果的信息的訪問受到限制。
簡單來說,游標(biāo) FOR 循環(huán)的END LOOP語句后,游標(biāo)就被關(guān)閉了,也就是說,此時(shí)無法獲取游標(biāo)的信息。因此,你需要再循環(huán)內(nèi)部(游標(biāo)關(guān)閉前)暫存游標(biāo)的信息,如行數(shù)(cursor%ROWCOUNT),后續(xù)關(guān)閉后就可以繼續(xù)訪問。
SQL Statement as Loop
實(shí)際上,您可以將像 SELECT 這樣的 SQL 語句視為循環(huán)。 畢竟,這樣的語句指定了對一組數(shù)據(jù)采取的操作; 然后,SQL 引擎“循環(huán)”數(shù)據(jù)集并應(yīng)用操作。
例如一個(gè)數(shù)據(jù)歸檔的例子,從源表中逐行讀取,然后插入歸檔表后刪除。這既可以用PL/SQL實(shí)現(xiàn),也可以用2條SQL實(shí)現(xiàn)(INSERT INTO … DELETE,DELETE )
SQL實(shí)現(xiàn)編寫的代碼更少,而且運(yùn)行效率更高,因?yàn)闇p少了上下文切換的次數(shù)(在 PL/SQL 和 SQL 執(zhí)行引擎之間來回移動(dòng))。 只執(zhí)行一次插入和一次刪除。
但SQL的靈活性差一點(diǎn),因?yàn)镾QL是事務(wù)型的,要么全成功,要么全失敗;SQL也不能做特殊處理,如記錄歸檔失敗的記錄。因此,PL/SQL 提供更大的靈活性。文章來源:http://www.zghlxwxcb.cn/news/detail-830982.html
總之,PL/SQL 提供一次訪問和處理單行并采取操作(或許還有基于該特定記錄內(nèi)容的復(fù)雜過程邏輯)的能力。 另一方面,使用原生SQL代碼更少,運(yùn)行效率更高。必要時(shí),可以混合使用 PL/SQL 和 SQL。文章來源地址http://www.zghlxwxcb.cn/news/detail-830982.html
單詞
- go figure 多奇怪!多怪異!多愚蠢!
到了這里,關(guān)于Oracle PL/SQL Programming 第5章:Iterative Processing with Loops 讀書筆記的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!