第四章 PL/SQL的控制结构

<div id="chapter">第四章 PL/SQL的控制结构</div>
<!-- InstanceEndEditable --><!-- InstanceBeginEditable name="EditRegion2" -->
<p class="title1">一、PL/SQL控制结构一览</p>
<p>根据结构定理(structure theorem),任何计算机程序都可以用下图中的基本控制结构来表示。它们可以任意组合来解决问题。 </p>
<p>
<img src="https://p-blog.csdn.net/images/p_blog_csdn_net/rcom10002/244670/o_4-1.gif" alt=""></p>
<p>选择结构是用于测试条件的,根据条件的真假,执行一系列语句。一个条件语句可以是任何能够返回布尔值(TRUE或FALSE)的变量或表达式。循环结构能在条件满足的情况下反复执行。序列结构只是简单的按照顺序执行语句。 </p>
<p class="title1">二、条件控制:IF和CASE语句</p>
<p>有时候,我们需要根据具体的条件来采取不同的对策。IF语句就能让我们按条件来执行语句序列。也就是说,语句序列的执行与否取决于某个给定的条件。
有三种IF语句:IF-THEN、IF-THEN-ELSE和IF-THEN-ELSIF。CASE语句是条件判断的精简形式,它能计算条件表达式的值并
在多个对应动作中做出选择。 </p>
<p class="title2">1、IF-THEN语句</p>
<p>IF语句最简单的形式就是把一个条件和一个语句序列用关键字THEN和END IF关联起来: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
condition<strong>THEN</strong>
<br>
sequence_of_statements<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>只有在条件值为真的时候语句序列才能被执行。如果条件值为假或是空,IF语句就什么都不做。无论哪种情况,控制权最后还是会被传递到下一个语句,如下例: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
sales>QUOTA<strong>THEN</strong>
<br>
compute_bonus(empid);<br><br><strong>UPDATE</strong>
payroll<br><strong>SET</strong>
pay=pay+bonus<br><strong>WHERE</strong>
empno=emp_id;<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>如果我们把IF语句放到一行,就可以像下面这样编写: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
x>y<strong>THEN</strong>
high:=x;<strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p class="title2">2、IF-THEN-ELSE语句</p>
<p>第二种形式的IF语句使用关键字ELSE添加了一个额外的处理选项,如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
condition<strong>THEN</strong>
<br>
sequence_of_statements1<br><strong>ELSE</strong>
<br>
sequence_of_statements2<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>当条件为假或空时,ELSE子句中的语句序列就会被执行。下例中,第一个UPDATE语句在条件为真的情况下执行,而第二个UPDATE语句在条件为假或为空的情况下才会被执行: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
trans_type=<em>'CR'</em>
<strong>THEN</strong>
<br><strong>UPDATE</strong>
accounts<br><strong>SET</strong>
balance=balance+credit<br><strong>WHERE</strong>
...<br><strong>ELSE</strong>
<br><strong>UPDATE</strong>
accounts<br><strong>SET</strong>
balance=balance-debit<br><strong>WHERE</strong>
...<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>THEN和ELSE子句中也可以包含IF语句。就是说IF语句能够被嵌套使用,如下例所示: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
trans_type=<em>'CR'</em>
<strong>THEN</strong>
<br><strong>UPDATE</strong>
accounts<br><strong>SET</strong>
balance=balance+credit<br><strong>WHERE</strong>
...<br><strong>ELSE</strong>
<br><strong>IF</strong>
new_balance>=minimum_balance<strong>THEN</strong>
<br><strong>UPDATE</strong>
accounts<br><strong>SET</strong>
balance=balance-debit<br><strong>WHERE</strong>
...<br><strong>ELSE</strong>
<br><strong>RAISE</strong>
insufficient_funds;<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p class="title2">3、IF-THEN-ELSIF语句</p>
<p>有时我们可能需要从几个选项中选择一个,这时我们就需要使用第三种IF语句,添加一个ELSIF关键字提供额外的条件选项,使用方法如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
condition1<strong>THEN</strong>
<br>
sequence_of_statements1<br><strong>ELSIF</strong>
condition2<strong>THEN</strong>
<br>
sequence_of_statements2<br><strong>ELSE</strong>
<br>
sequence_of_statements3<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>如果第一个条件为假或空,ELSIF子句就会检测另外一个条件。一个IF语句可以有多个ELSIF子句;最后一个ELSE子句是可选的。条件表达式
从上而下的计算。只要有满足的条件,与它关联的语句就会执行,然后控制权转到下一个语句。如果所有的条件都为假或是空,ELSE部分的语句就会执行。看一
下下面的例子: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>IF</strong>
sales>50000<strong>THEN</strong>
<br>
bonus:=1500;<br><strong>ELSIF</strong>
sales>35000<strong>THEN</strong>
<br>
bonus:=500;<br><strong>ELSE</strong>
<br>
bonus:=100;<br><strong>END</strong>
<strong>IF</strong>
;<br><br><strong>INSERT</strong>
<strong>INTO</strong>
payroll<br><strong>VALUES</strong>
(emp_id,bonus,...);<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>如果sales的值大于50000的话,第一个和第二个条件就为真。然而,bonus只会被赋予1500的值,因为第二个条件并没有执行到。当第一个条件为真的话,它关联的语句就会执行,然后控制权转到INSERT语句。 </p>
<p class="title2">4、CASE语句</p>
<p>同IF语句一样,CASE语句也是选出一个语句序列来执行。但是,为了选择出合适的语句序列,CASE会使用一个选择器,而不是多个布尔表达式。想要比较IF和CASE语句的话,请看下面对学校成绩的描述信息: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
grade=<em>'A'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Excellent'</em>
);<br><strong>ELSIF</strong>
grade=<em>'B'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'VeryGood'</em>
);<br><strong>ELSIF</strong>
grade=<em>'C'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Good'</em>
);<br><strong>ELSIF</strong>
grade=<em>'D'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Fair'</em>
);<br><strong>ELSIF</strong>
grade=<em>'F'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Poor'</em>
);<br><strong>ELSE</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Nosuchgrade'</em>
);<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>请注意这五个布尔表达式,在每一个实例中,我们只对同一变量的值进行检测,看它的分数值是否等于"A"、"B"、"C"、"D"、"E"或"F"。下面我们用CASE语句重新编写上面的程序: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>CASE</strong>
grade<br><strong>WHEN</strong>
<em>'A'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Excellent'</em>
);<br><strong>WHEN</strong>
<em>'B'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'VeryGood'</em>
);<br><strong>WHEN</strong>
<em>'C'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Good'</em>
);<br><strong>WHEN</strong>
<em>'D'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Fair'</em>
);<br><strong>WHEN</strong>
<em>'F'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Poor'</em>
);<br><strong>ELSE</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Nosuchgrade'</em>
);<br><strong>END</strong>
<strong>CASE</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>CASE语句的可读性高而且高效,所以,如果可能的话,尽量把IF-THEN-ELEIF都改写成CASE语句。 </p>
<p>CASE语句以关键字CASE开头,然后跟上一个选择器,也就是上例中的变量grade。选择器表达式可能是很复杂的。例如,它有可能是一个函数调
用。但在通常情况下,它只是一个独立的变量。选择器表达式只被计算一次。它的值可以是除BLOB、BFILE、对象类型、PL/SQL记录、索引表、变长
数组或嵌套表之外的任何有效的PL/SQL数据类型。 </p>
<p>选择器后面跟着一个或多个WHEN子句,它们是按顺序检测的。选择器的值决定了哪个子句被执行。如果选择器的值等于WHEN子句的表达式
值,WHEN子句中的语句序列就会被执行。例如在上面例子中,如果grade等于"C",程序就会输出"Good"。当WHEN子句中的语句序列被执行完
毕,控制权会转到下一个语句,而不会再执行后续的WHEN子句。 </p>
<p>ELSE子句的工作原理与IF中的类似。上例子中,如果grade的值不与任何一个WHEN子句匹配,ELSE部分就会被执行,"No such
grade"就会被输出。ELSE子语是可选的。但是,如果我们省略了ELSE子句,PL/SQL就会为我们添加隐式的ELSE子句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>ELSE</strong>
<strong>RAISE</strong>
CASE_NOT_FOUND; </td>
</tr></tbody></table>
</blockquote>
<p>如果CASE语句选择了隐式的ELSE子句,PL/SQL就会抛出预定义异常CASE_NOT_FOUND。所以,即使我们省略了ELSE子句,ELSE也会有一个默认的动作。 </p>
<p>关键字END CASE是CASE语句结束的标志。这两个关键字必须用空格分开。形式如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>[<<label_name>>]<br><strong>CASE</strong>
selector<br><strong>WHEN</strong>
expression1<strong>THEN</strong>
<br>
sequence_of_statements1;<br><strong>WHEN</strong>
expression2<strong>THEN</strong>
<br>
sequence_of_statements2;<br>
...<br><strong>WHEN</strong>
expressionn<strong>THEN</strong>
<br>
sequence_of_statementsn;<br>
[<strong>ELSE</strong>
sequence_of_statementsN+1;]<br><strong>END</strong>
<strong>CASE</strong>
[label_name]; </td>
</tr></tbody></table>
</blockquote>
<p>同PL/SQL块一样,CASE语句也是可以加标签的。标签是一个未声明的标识符,必须出现在CASE语句的开头,用双尖括号夹起来。标签的名称也可以出现在CASE语句的结尾处,但不是必须的。 </p>
<p>CASE语句中的异常会按正常的方法处理,就是说正常的执行语句停止,控制权转到PL/SQL块或子程序的异常控制部分。 </p>
<ul>
<li>搜寻式CASE语句 </li>
</ul>
<p>PL/SQL还提供下面搜寻形式的CASE语句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>[<<label_name>>]<br><strong>CASE</strong>
<br><strong>WHEN</strong>
search_condition1<strong>THEN</strong>
<br>
sequence_of_statements1;<br><strong>WHEN</strong>
search_condition2<strong>THEN</strong>
<br>
sequence_of_statements2;<br>
...<br><strong>WHEN</strong>
search_conditionn<strong>THEN</strong>
<br>
sequence_of_statementsn;<br>
[<strong>ELSE</strong>
sequence_of_statementsN+1;]<br><strong>END</strong>
<strong>CASE</strong>
[label_name]; </td>
</tr></tbody></table>
</blockquote>
<p>搜寻式CASE语句没有选择器。并且,它的WHEN子句只能包含结果为布尔类型的表达式,产生其它类型结果的表达式是不允许的。示例如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>CASE</strong>
<br><strong>WHEN</strong>
grade=<em>'A'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Excellent'</em>
);<br><strong>WHEN</strong>
grade=<em>'B'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'VeryGood'</em>
);<br><strong>WHEN</strong>
grade=<em>'C'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Good'</em>
);<br><strong>WHEN</strong>
grade=<em>'D'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Fair'</em>
);<br><strong>WHEN</strong>
grade=<em>'F'</em>
<strong>THEN</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Poor'</em>
);<br><strong>ELSE</strong>
<br>
DBMS_OUTPUT.put_line(<em>'Nosuchgrade'</em>
);<br><strong>END</strong>
<strong>CASE</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>搜寻条件是按顺序计算的。每个搜寻条件的布尔值决定了哪个WHEN子句被执行。一旦WHEN子句被执行,控制权就会被交给下一个语句,后续的搜寻条件就不会被考虑。 </p>
<p>如果没有找到搜寻条件为TRUE的子句,ELSE子句就会执行。ELSE虽然是可选的,但是,如果省略了ELSE,PL/SQL就会添加隐式的ELSE子句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>ELSE</strong>
<strong>RAISE</strong>
CASE_NOT_FOUND; </td>
</tr></tbody></table>
</blockquote>
<p>如果执行过程中有异常发生,我们可以在块或子程序的异常控制部分捕获到。 </p>
<p class="title2">5、PL/SQL条件控制语句使用准则</p>
<p>我们不应该像下面这样使用笨拙的IF语句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
new_balance<minimum_balance<strong>THEN</strong>
<br>
overdrawn:=<strong>TRUE</strong>
;<br><strong>ELSE</strong>
<br>
overdrawn:=<strong>FALSE</strong>
;<br><strong>END</strong>
<strong>IF</strong>
;<br><br>
...<br><br><strong>IF</strong>
overdrawn=<strong>TRUE</strong>
<strong>THEN</strong>
<br><strong>RAISE</strong>
insufficient_funds;<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>上面的代码忽视了两个地方。首先,布尔表达式的值可以直接赋给布尔变量。所以我们可以把第一个IF语句简化成下面的语句形式: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>overdrawn:=new_balance<minimum_balance; </td>
</tr></tbody></table>
</blockquote>
<p>第二,布尔变量本身就是TRUE或FALSE。所以,在IF的条件表达式中直接使用变量本身即可: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
overdrawn<strong>THEN</strong>
... </td>
</tr></tbody></table>
</blockquote>
<p>尽可能地使用ELSIF子句代替嵌套IF语句。这样我们的代码就更易读易理解。比较下面两个IF语句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
condition1<strong>THEN</strong>
<br>
statement1;<br><strong>ELSE</strong>
<br><strong>IF</strong>
condition2<strong>THEN</strong>
<br>
statement2;<br><strong>ELSE</strong>
<br><strong>IF</strong>
condition3<strong>THEN</strong>
<br>
statement3;<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>IF</strong>
; </td>
<td valign="top">
<strong>IF</strong>
condition1<strong>THEN</strong>
<br>
statement1;<br><strong>ELSIF</strong>
condition2<strong>THEN</strong>
<br>
statement2;<br><strong>ELSIF</strong>
condition3<strong>THEN</strong>
<br>
statement3;<br><strong>END</strong>
<strong>IF</strong>
;</td>
</tr></tbody></table>
</blockquote>
<p>这两个语句在逻辑上是等价的,但第一个语句看起来有些混乱,而第二个就较为明显。 </p>
<p>如果把单独一个表达式与多个值进行比较的话,我们可以使用CASE语句来代替多个ELSIF子句。 </p>
<p class="title1">三、循环控制:LOOP和EXIT语句</p>
<p>LOOP语句能让我们反复执行一个语句序列。有三种形式的LOOP语句:LOOP,WHILE-LOOP和FOR-LOOP。 </p>
<p class="title2">1、LOOP</p>
<p>LOOP语句最简单的形式就是把语句序列放到关键字LOOP和END LOOP之间,语法如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>LOOP</strong>
<br>
sequence_of_statements;<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>在每一个循环中,语句序列都会被顺序执行,然后再返回循环顶部从头执行。如果不想继续执行,可以使用EXIT语句退出循环。我们可以把一个或多个EXIT语句放到循环里,但不能放到循环外面。有两种形式的EXIT语句:EXIT和EXIT-WHEN。 </p>
<ul>
<li>EXIT </li>
</ul>
<p>EXIT语句会强迫循环无条件终止。当遇到EXIT语句时,循环会立即终止,并把控制权交给下面的语句。示例如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>LOOP</strong>
<br>
...<br><strong>IF</strong>
credit_rating<3<strong>THEN</strong>
<br>
...<br><strong>EXIT</strong>
;<em>--exitloopimmediately</em>
<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>LOOP</strong>
;<br><em>--controlresumeshere</em>
</td>
</tr></tbody></table>
</blockquote>
<p>下面再举一个不能在PL/SQL块中使用EXIT语句的例子: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>IF</strong>
credit_rating<3<strong>THEN</strong>
<br>
...<br><strong>EXIT</strong>
;<em>--notallowed</em>
<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>记住,EXIT语句必须放在循环内。如果想在PL/SQL块正常到达程序结尾之前而终止执行,可以使用RETURN语句。 </p>
<ul>
<li>EXIT-WHEN </li>
</ul>
<p>EXIT-WHEN语句可以根据给定的条件跳出循环。当遇到EXIT语句时,WHEN子句中的表达式值就会被计算。如果条件满足,循环就会被终止,控制权转到循环语句之后的语句。示例如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>LOOP</strong>
<br><strong>FETCH</strong>
c1<br><strong>INTO</strong>
...<br><br><strong>EXIT</strong>
<strong>WHEN</strong>
c1%NOTFOUND;<em>--exitloopifconditionistrue</em>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
;<br><br><strong>CLOSE</strong>
c1; </td>
</tr></tbody></table>
</blockquote>
<p>在条件满足之前,循环是不会结束的。所以,循环里的语句必须要改变循环条件的值。上例中,如果FETCH语句返回了一行值,WNEN子句中的条件就为假;如果不能返回结果,WNEN子句中的条件就为真,循环就会结束,控制权转入CLOSE语句。 </p>
<p>EXIT-WHEN语句可以替代简单的IF语句,例如,比较下面两段代码: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
count>100<strong>THEN</strong>
<br><strong>EXIT</strong>
;<br><strong>END</strong>
<strong>IF</strong>
; </td>
<td valign="top">
<strong>EXIT</strong>
<strong>WHEN</strong>
count>100;</td>
</tr></tbody></table>
</blockquote>
<p>这两个语句在逻辑上是等价的,但EXIT-WHEN语句更容易阅读和理解。 </p>
<ul>
<li>循环标签 </li>
</ul>
<p>跟PL/SQL块一样,循环也是可以添加标签。标签必须出现在LOOP语句的开端,语法如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td><<label_name>><br><strong>LOOP</strong>
<br>
sequence_of_statements<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>而在LOOP语句结束部分出现的标签名称是可选的,语法如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td><<my_loop>><br><strong>LOOP</strong>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
my_loop; </td>
</tr></tbody></table>
</blockquote>
<p>在LOOP结束部分使用标签名称能够改善可读性。</p>
<p>无论使用哪种EXIT语句形式,都可以结束一个封闭的LOOP块,而不仅仅局限于当前的LOOP块。只要在我们想结束的封闭LOOP块上添加一个标签,然后像下面这样在EXIT语句中使用这个标签就可以了:</p>
<blockquote>
<table border="0"><tbody><tr>
<td><<outer>><br><strong>LOOP</strong>
<br>
...<br><strong>LOOP</strong>
<br>
...<br><strong>EXIT</strong>
outer<strong>WHEN</strong>
...<em>--exitbothloops</em>
<br><strong>END</strong>
<strong>LOOP</strong>
;<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
outer; </td>
</tr></tbody></table>
</blockquote>
<p class="title2">2、WHILE-LOOP</p>
<p>WHILE-LOOP语句用关键字LOOP和END LOOP把语句序列封闭起来并与一个布尔条件表达式相关联: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>WHILE</strong>
condition<strong>LOOP</strong>
<br>
sequence_of_statements<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>每次循环之前,程序都是计算布尔表达式的值。如果条件为真,语句序列就会被执行,然后重新返回循环顶部计算布尔表达式的值;如果布尔表达式的值为假或空,控制权就会被交给循环之后的语句。下面看一个例子: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>WHILE</strong>
total<=25000<strong>LOOP</strong>
<br>
...<br><strong>SELECT</strong>
sal<br><strong>INTO</strong>
salary<br><strong>FROM</strong>
emp<br><strong>WHERE</strong>
x=x;...<br><br>
total:=total+salary;<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>循环的次数是与条件相关的,而且在循环结束之前是未知的。由于条件是在循环顶部测试的,所以语句序列有可能一次都没有执行。在上面的例子中,如果total的初始值比25000大,那么条件值就是假,循环就会被跳过。</p>
<p>有些语言有LOOP UNTIL或是REPEAT UNTIL这样的结构,在底部测试条件表达式的值。这样,语句序列就会至少执行一次。PL/SQL没有这样的结构,但我们可以变通地使用下面的方法来实现这样的功能: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>LOOP</strong>
<br>
sequence_of_statements<br><strong>EXIT</strong>
<strong>WHEN</strong>
boolean_expression;<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>要保证WHILE循环至少执行一次,在条件表达式中使用初始化过的布尔变量,如下例所示: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>done:=<strong>FALSE</strong>
;<br><br><strong>WHILE</strong>
<strong>NOT</strong>
done<strong>LOOP</strong>
<br>
sequence_of_statements;<br>
done:=boolean_expression;<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>在循环内的语句必须为布尔变量赋上一个新值。否则循环就会无限地执行下去。如下例的两个LOOP语句在逻辑上是等价的:</p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>WHILE</strong>
<strong>TRUE</strong>
<strong>LOOP</strong>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
<td>
<strong>LOOP</strong>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
;</td>
</tr></tbody></table>
</blockquote>
<p class="title2">3、FOR-LOOP</p>
<p>FOR语句会在指定的整数范围内进行循环操作。循环的内容被关键字FOR和LOOP封闭起来。两个"点"(..)作为范围操作符来使用。语法如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
counter<strong>IN</strong>
[<strong>REVERSE</strong>
]lower_bound..higher_bound<strong>LOOP</strong>
<br>
sequence_of_statements<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>当首次进入FOR循环时,循环的范围就会被确定下来,并且不会重新计算。如下例所示,语句序列会执行三次,每执行一次,循环因子就会增加1。 </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
i<strong>IN</strong>
1..3<strong>LOOP</strong>
<em>--assignthevalues1,2,3toi</em>
<br>
sequence_of_statements<em>--executesthreetimes</em>
<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>下例演示了如果下界值等于上界值,循环中的语句序列只执行一次: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
i<strong>IN</strong>
3..3<strong>LOOP</strong>
<em>--assignthevalues3toi</em>
<br>
sequence_of_statements<em>--executesonetime</em>
<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>默认情况下,循环总是从下界到上界。不过也可以使用REVERSE关键字,让循环从上界往下界执行。但是要记住,范围的书写格式仍旧是递增顺序的。 </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
i<strong>IN</strong>
<strong>REVERSE</strong>
1..3<strong>LOOP</strong>
<em>--assignthevalues3,2,1toi</em>
<br>
sequence_of_statements<em>--executesthreetimes</em>
<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>FOR循环里,循环计数器只能当作常量来引用且不能为它赋值,如下例: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
ctr<strong>IN</strong>
1..10<strong>LOOP</strong>
<br><strong>IF</strong>
<strong>NOT</strong>
finished<strong>THEN</strong>
<br><strong>INSERT</strong>
<strong>INTO</strong>
...<br><strong>VALUES</strong>
(ctr);<em>--legal</em>
<br><br>
factor:=ctr*2;<em>--legal</em>
<br><strong>ELSE</strong>
<br>
ctr:=10;<em>--notallowed</em>
<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<ul>
<li>迭代法 </li>
</ul>
<p>循环范围的边界可以是文字、变量或表达式,但它们都必须是数字。否则PL/SQL会抛出预定义异常VALUE_ERROR。如下例,下界不一定非得是1。但循环计数器只能是每次增加1。 </p>
<blockquote>
<table border="0"><tbody><tr>
<td>j<strong>IN</strong>
-5..5<br>
k<strong>IN</strong>
<strong>REVERSE</strong>
first..last<br>
step<strong>IN</strong>
0..TRUNC(high/low)*2 </td>
</tr></tbody></table>
</blockquote>
<p>在PL/SQL内部,它会把边界值赋给一个临时的PLS_INTEGER变量,并在需要的时候把值转换成最接近的整数。PLS_INTEGER的范围是-2**31到2**31之间。所以,如果边界值超过这个范围,我们就会得到一个数字溢出错误: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
hi<strong>NUMBER</strong>
:=2**32;<br><strong>BEGIN</strong>
<br><strong>FOR</strong>
j<strong>IN</strong>
1..hi<strong>LOOP</strong>
<em>--causesa鈥檔umericoverflow鈥?error</em>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>有些语言提供了STEP子句,它能让我们指定循环增量。PL/SQL没有这样的结构,但我们可以在FOR循环内扩大循环计数器的倍数来实现这样的功能。在下面的例子中,我们今天的日期赋给索引表的第一个、第五个和第十五个元素。 </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br><strong>TYPE</strong>
datelist<strong>IS</strong>
<strong>TABLE</strong>
<strong>OF</strong>
<strong>DATE</strong>
<br><strong>INDEX</strong>
<strong>BY</strong>
<strong>BINARY_INTEGER</strong>
;<br><br>
datesdatelist;<br>
k<strong>CONSTANT</strong>
<strong>INTEGER</strong>
:=5;<em>--setnewincrement</em>
<br><strong>BEGIN</strong>
<br><strong>FOR</strong>
j<strong>IN</strong>
1..3<strong>LOOP</strong>
<br>
dates(j*k):=<strong>SYSDATE</strong>
;<em>--multiplyloopcounterbyincrement</em>
<br><strong>END</strong>
<strong>LOOP</strong>
;<br>
...<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<ul>
<li>动态范围 </li>
</ul>
<p>PL/SQL允许我们在运行时决定循环的范围,如下例所示: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>SELECT</strong>
COUNT(empno)<br><strong>INTO</strong>
emp_count<br><strong>FROM</strong>
emp;<br><br><strong>FOR</strong>
i<strong>IN</strong>
1..emp_count<strong>LOOP</strong>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>emp_count在运行时是未知的;SELECT语句在运行时才返回结果值。 </p>
<p>如果循环范围中的下界值超过上界值会怎样呢?如下例所示,循环内的语句序列就不会被执行,控制权直接交给下一个语句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<em>--limitbecomes1</em>
<br><strong>FOR</strong>
i<strong>IN</strong>
2..LIMIT<strong>LOOP</strong>
<br>
sequence_of_statements<em>--executeszerotimes</em>
<br><strong>END</strong>
<strong>LOOP</strong>
;<br><em>--controlpasseshere</em>
</td>
</tr></tbody></table>
</blockquote>
<ul>
<li>作用域规则 </li>
</ul>
<p>循环计数器只在循环内部定义,我们不能在循环外引用它。循环退出后,循环计数器就会失效,如下例: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
ctr<strong>IN</strong>
1..10<strong>LOOP</strong>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
;<br>
sum:=ctr-1;<em>--notallowed</em>
</td>
</tr></tbody></table>
</blockquote>
<p>我们不需要显式声明循环计数器,因为它会被隐式地声明为INTEGER类型本地变量。下面的例子中本地声明覆盖了全局声明: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
ctr<strong>INTEGER</strong>
;<br><strong>BEGIN</strong>
<br>
...<br><strong>FOR</strong>
ctr<strong>IN</strong>
1..25<strong>LOOP</strong>
<br>
...<br><strong>IF</strong>
ctr>10<strong>THEN</strong>
...<em>--referstoloopcounter</em>
<br><strong>END</strong>
<strong>LOOP</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>如果想在上例中引用全局变量,我们就得借助标签和点标志,例如: </p>
<blockquote>
<table border="0"><tbody><tr>
<td><<main>><br><strong>DECLARE</strong>
<br>
ctr<strong>INTEGER</strong>
;<br>
...<br><strong>BEGIN</strong>
<br>
...<br><strong>FOR</strong>
ctr<strong>IN</strong>
1..25<strong>LOOP</strong>
<br>
...<br><strong>IF</strong>
main.ctr>10<strong>THEN</strong>
<em>--referstoglobalvariable</em>
<br>
...<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
<strong>LOOP</strong>
;<br><strong>END</strong>
main; </td>
</tr></tbody></table>
</blockquote>
<p>同样的作用域规则也适用于嵌套FOR循环。如下面的例子,两个循环计数器的名字相同,所以,用从内层循环引用外层循环的循环计数器,就必须使用标签和点标志: </p>
<blockquote>
<table border="0"><tbody><tr>
<td><<outer>><br><strong>FOR</strong>
step<strong>IN</strong>
1..25<strong>LOOP</strong>
<br><strong>FOR</strong>
step<strong>IN</strong>
1..10<strong>LOOP</strong>
<br>
...<br><strong>IF</strong>
outer.step>15<strong>THEN</strong>
...<br><strong>END</strong>
<strong>LOOP</strong>
;<br><strong>END</strong>
<strong>LOOP</strong>
outer; </td>
</tr></tbody></table>
</blockquote>
<ul>
<li>使用EXIT语句 </li>
</ul>
<p>EXIT语句可以立即结束一个FOR循环。例如,下面的循环语句正常情况应该执行十次,但是,如果FETCH语句取得数据失败,循环就会立即终止,无论它执行过多少次: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
j<strong>IN</strong>
1..10<strong>LOOP</strong>
<br><strong>FETCH</strong>
c1<strong>INTO</strong>
emp_rec;<br><strong>EXIT</strong>
<strong>WHEN</strong>
c1%NOTFOUND;<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>在使用EXIT时,我们可以结束任何封闭循环,而不仅仅是当前循环。只要在我们想结束的封闭循环上加上标签,然后在EXIT语句中引用它,就能结束做过标记的FOR循环了,如下例: </p>
<blockquote>
<table border="0"><tbody><tr>
<td><<outer>><br><strong>FOR</strong>
i<strong>IN</strong>
1..5<strong>LOOP</strong>
<br>
...<br><strong>FOR</strong>
j<strong>IN</strong>
1..10<strong>LOOP</strong>
<br><strong>FETCH</strong>
c1<strong>INTO</strong>
emp_rec;<br><strong>EXIT</strong>
outer<strong>WHEN</strong>
c1%NOTFOUND;<em>--exitbothFORloops</em>
<br>
...<br><strong>END</strong>
<strong>LOOP</strong>
;<br><strong>END</strong>
<strong>LOOP</strong>
outer;<br><em>--controlpasseshere</em>
</td>
</tr></tbody></table>
</blockquote>
<p class="title1">四、顺序控制:GOTO和NULL语句</p>
<p>与IF和LOOP语句不通,GOTO和NULL语句对PL/SQL编程来说不是必须的。PL/SQL结构中很少用到GOTO语句。有时,它就是用于简化逻辑的。NULL用于改善可读性使条件语句看起来更加清晰。 </p>
<p>滥用GOTO能使结构混乱,不易理解和维护(有时被称为意大利面条式代码 - spaghetti code)。所以,GOTO语句的使用一定要有节制。例如,要从一个深嵌套中跳到异常控制块,要用异常抛出而不是使用GOTO语句。 </p>
<p class="title2">1、GOTO语句</p>
<p>GOTO语句可以无条件跳到一个标签处。标签名称在它所处的作用范围内必须是唯一的。执行的时候,GOTO语句会把控制权交给一个做了标记的语句或块。GOTO语句可以向上或向下跳转,示例如下: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>GOTO</strong>
insert_row;<br>
...<br>
<<insert_row>><br><strong>INSERT</strong>
<strong>INTO</strong>
emp<strong>VALUES</strong>
...<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>再看一个向上跳转的例子:</p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br>
<<update_row>><br><strong>BEGIN</strong>
<br><strong>UPDATE</strong>
emp<strong>SET</strong>
...<br>
...<br><strong>END</strong>
;<br>
...<br><strong>GOTO</strong>
update_row;<br>
...<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>下例中的标签end_loop是不允许的,因为它并没有到达一个处理语句: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
done<strong>BOOLEAN</strong>
;<br><strong>BEGIN</strong>
<br>
...<br><strong>FOR</strong>
i<strong>IN</strong>
1..50<strong>LOOP</strong>
<br><strong>IF</strong>
done<strong>THEN</strong>
<br><strong>GOTO</strong>
end_loop;<br><strong>END</strong>
<strong>IF</strong>
;<br>
...<br>
<<end_loop>><em>--notallowed</em>
<br><strong>END</strong>
<strong>LOOP</strong>
;<em>--notanexecutablestatement</em>
<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>改进方法很简单,只需添加一个NULL语句即可: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>FOR</strong>
i<strong>IN</strong>
1..50<strong>LOOP</strong>
<br><strong>IF</strong>
done<strong>THEN</strong>
<br><strong>GOTO</strong>
end_loop;<br><strong>END</strong>
<strong>IF</strong>
;<br>
...<br>
<<end_loop>><br><strong>NULL</strong>
;<em>--anexecutablestatement</em>
<br><strong>END</strong>
<strong>LOOP</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>GOTO语句还能从当前块跳入一个封闭的块中: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
my_ename<strong>CHAR</strong>
(10);<br><strong>BEGIN</strong>
<br><br>
<<get_name>><br><strong>SELECT</strong>
ename<br><strong>INTO</strong>
my_ename<br><strong>FROM</strong>
emp<br><strong>WHERE</strong>
...<br><br><strong>BEGIN</strong>
<br>
...<br><strong>GOTO</strong>
get_name;<em>--branchtoenclosingblock</em>
<br><strong>END</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<ul>
<li>约束 </li>
</ul>
<p>有些情况下,GOTO语句是不能使用的。特别是想用GOTO语句跳到IF、CASE、LOOP或子块。例如,下面的GOTO语句是不允许的: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>GOTO</strong>
update_row;<em>--can’tbranchintoIFstatement</em>
<br>
...<br><strong>IF</strong>
valid<strong>THEN</strong>
<br>
...<br>
<<update_row>><br><strong>UPDATE</strong>
emp<strong>SET</strong>
...<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>如下例所示,GOTO不能从一个IF的一个分支跳到另一个分支。同样,也不能从CASE的一个WHEN子句跳到另一个WHEN子句。 </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>IF</strong>
valid<strong>THEN</strong>
<br>
...<br><strong>GOTO</strong>
update_row;<em>--can’tbranchinto<strong>ELSE</strong>
clause</em>
<br><strong>ELSE</strong>
<br>
...<br>
<<update_row>><br><strong>UPDATE</strong>
emp<strong>SET</strong>
...<br><strong>END</strong>
<strong>IF</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>下例中演示了GOTO语句不能从封闭的块跳入它的子块: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>BEGIN</strong>
<br>
...<br><strong>IF</strong>
status=<em>'OBSOLETE'</em>
<strong>THEN</strong>
<br><strong>GOTO</strong>
delete_part;<em>--can’tbranchintosub-block</em>
<br><strong>END</strong>
<strong>IF</strong>
;<br>
...<br><strong>BEGIN</strong>
<br>
...<br>
<<delete_part>><br><strong>DELETE</strong>
<strong>FROM</strong>
parts<strong>WHERE</strong>
...<br><strong>END</strong>
;<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>同样,GOTO也不能跳出子程序: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
...<br><strong>PROCEDURE</strong>
compute_bonus(emp_id<strong>NUMBER</strong>
)<strong>IS</strong>
<br><strong>BEGIN</strong>
<br>
...<br><strong>GOTO</strong>
update_row;<em>--can’tbranchoutofsubprogram</em>
<br><strong>END</strong>
;<br><strong>BEGIN</strong>
<br>
...<br>
<<update_row>><br><strong>UPDATE</strong>
emp<strong>SET</strong>
...<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>最后,GOTO不能从异常控制部分跳入当前块。例如,下面的GOTO语句就是不允许的: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>DECLARE</strong>
<br>
...<br>
pe_ratio<strong>REAL</strong>
;<br><strong>BEGIN</strong>
<br>
...<br><strong>SELECT</strong>
price/NVL(earnings,0)<br><strong>INTO</strong>
pe_ratio<br><strong>FROM</strong>
...<br><br>
<<insert_row>><br><strong>INSERT</strong>
<strong>INTO</strong>
stats<br><strong>VALUES</strong>
(pe_ratio,...);<br><strong>EXCEPTION</strong>
<br><strong>WHEN</strong>
ZERO_DIVIDE<strong>THEN</strong>
<br>
pe_ratio:=0;<br><strong>GOTO</strong>
insert_row;<em>--can'tbranchintocurrentblock</em>
<br><strong>END</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>但是,GOTO语句可以从一个异常控制程序中跳转到一个封闭块。</p>
<p class="title2">2、NULL语句</p>
<p>NULL语句本身什么都不做,只是简单的把控制权交给下一个语句而已。在控制结构中,NULL只是告诉阅读者一个可能会被考虑到的情况,而实际的没有任何动作。下面的例子中演示了忽视未命名异常的操作: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>EXCEPTION</strong>
<br><strong>WHEN</strong>
ZERO_DIVIDE<strong>THEN</strong>
<br><strong>ROLLBACK</strong>
;<br><strong>WHEN</strong>
VALUE_ERROR<strong>THEN</strong>
<br><strong>INSERT</strong>
<strong>INTO</strong>
errors<strong>VALUES</strong>
...<br><strong>COMMIT</strong>
;<br><strong>WHEN</strong>
<strong>OTHERS</strong>
<strong>THEN</strong>
<br><strong>NULL</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>在IF语句中或其他一些需要至少有一个可执行语句的地方,NULL语句就会被使用来满足这种语法。下例中,NULL语句强调了只有顶级(top-rated)雇员才能得到红利: </p>
<blockquote>
<table border="0"><tbody><tr>
<td>
<strong>IF</strong>
rating>90<strong>THEN</strong>
<br>
compute_bonus(emp_id);<br><strong>ELSE</strong>
<br><strong>NULL</strong>
;<br><strong>END</strong>
<strong>IF</strong>
; </td>
</tr></tbody></table>
</blockquote>
<p>在程序设计时,NULL语句很容易创建stub程序。stub程序是一段虚设的子程序,它能让我们推迟函数或过程的实现。因为子程序的可执行部分至少需要一句可执行语句,NULL语句就会派上用场,充当占位语句,如下例: </p>
<p>



<strong>PROCEDURE</strong>
debit_account(acct_id<strong>INTEGER</strong>
,amount<strong>REAL</strong>
)<strong>IS</strong>
<br><strong>BEGIN</strong>
<br><strong>NULL</strong>
;<br><strong>END</strong>
debit_account; </p>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值