zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Oracle的学习心得和知识总结(十)|Oracle数据库PL/SQL语言循环控制语句之LOOP语句技术详解

2023-09-14 09:15:34 时间

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《Oracle Database SQL Language Reference》
2、参考书籍:《PostgreSQL中文手册》
3、EDB Postgres Advanced Server User Guides,点击前往
4、PostgreSQL数据库仓库链接,点击前往
5、PostgreSQL中文社区,点击前往
6、Oracle数据库 5.2 LOOP Statements 官方文档说明,点击前往
7、EDB数据库 Control structures v14官方文档说明,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文仅适于从事于PostgreSQL数据库内核开发者和数据库爱好者,对普通读者而言难度较大 但对于希望从事于数据库内核开发的初学者来说,是一次机会十分难得的学习案例 💪
6、本文内容基于PostgreSQL15.0源码开发而成




本人博客严正声明

是这样的,熟悉在下的小伙伴们都知道 我写博客主要目的就是分享和学习总结。至于CSDN的排名 排名什么的,我并不是很在意!

  • 一来 很不喜欢标题党
  • 二来 更反感灌些水文
  • 三来 痛恨无下限抄袭

本人博客都是认认真真写的,结果在CSDN并没有什么的太大的名气 关注度什么的也不高!前些天 一位好心的粉丝私聊了在下,反而一名某平台的哥们儿 快把我的《PostgreSQL的学习心得和知识总结》都给照搬过去了,甚至一个字都不改(连同在下 都是只字不提 好歹稍微提一下呀)!!!

实在是太过分,后来经过(友好)协商,现已经全部删除了!

本人是做PostgreSQL内核开发的,深感当下学风不正 大家都很浮躁,一向踏踏实实深耕的并不是很多!因为写代码这件事情上,欺骗不了任何人!本本分分老老实实地写好代码做好学问十分不易,容不得掺沙子和造假!这里把我喜欢的一句话送给各位以共勉:

非淡泊无以明志,
非宁静无以致远!


文章快速说明索引

学习目标:

目的:接下来这段时间我想做一些兼容Oracle数据库PL/SQL语言上的一些功能开发,本专栏这里主要是学习以及介绍Oracle数据库功能的使用场景、原理说明和注意事项等,基于PostgreSQL数据库的功能开发等之后 由新博客进行介绍和分享!今天我们主要看一下 PL/SQL语言 循环控制语句之LOOP语句 的相关内容!


学习内容:(详见目录)

1、Oracle数据库PL/SQL语言 循环控制语句之LOOP语句技术详解


学习时间:

2023-01-31 10:29:34


学习产出:

1、Oracle数据库PL/SQL语言 循环控制语句之LOOP语句技术详解
2、CSDN 技术博客 1篇


注:下面我们所有的学习环境是Centos7+PostgreSQL15.0+Oracle19c+MySQL5.7

postgres=# select version();
                                   version                                   
-----------------------------------------------------------------------------
 PostgreSQL 15.0 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 7.1.0, 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.19    |
+-----------+
1 row in set (0.06 sec)

mysql>

注:我们这里所用的Oracle环境是在线环境:


循环控制语句说明

循环语句使用一系列不同的值迭代地运行相同的语句。

一个 LOOP 语句包含三个部分:

  1. 一个 iterand,也称为循环变量,用于将值从循环头传递到循环体
  2. 为循环生成值的迭代控件
  3. 为每个值执行一次的循环体
loop_statement ::= [ iteration_scheme ] LOOP 
             loop_body 
END LOOP [ label ];

iteration_scheme ::= WHILE expression
                       | FOR iterator

循环语句有:

  1. Basic LOOP
  2. FOR LOOP and Cursor FOR LOOP
  3. WHILE LOOP

退出循环的语句有:

  1. EXIT
  2. EXIT WHEN

退出循环当前迭代的语句有:

  1. CONTINUE
  2. CONTINUE WHEN

注意事项如下:

  • EXIT、EXIT WHEN、CONTINUE 和 CONTINUE WHEN 可以出现在循环内的任何位置,但不能出现在循环外。Oracle 建议使用这些语句而不是 GOTO 语句,GOTO 语句可以通过将控制转移到循环外的语句来退出循环或循环的当前迭代
  • 引发的异常也会退出循环
  • LOOP 语句可以被标记,LOOP 语句可以嵌套。建议为嵌套循环使用标签以提高可读性。您必须确保 END LOOP 语句中的标签与同一循环语句开头的标签匹配(编译器不检查)

Basic LOOP Statement

Basic LOOP Statement的语法说明,如下:

[ label ] LOOP
  statements
END LOOP [ label ];
  1. 基本的 LOOP 语句具有这种结构
  2. 随着循环的每次迭代,语句运行并且控制返回到循环的顶部。为防止无限循环,某个语句或引发的异常必须退出循环

随着基本 LOOP 语句的每次迭代,它的语句运行且控制返回到循环的顶部。当循环内的语句将控制转移到循环外或引发异常时,LOOP 语句结束。其语法格式如下:

在这里插入图片描述


语义解释如下:

  • statement:为防止无限循环,至少有一条语句必须将控制转移到循环外。可以将控制转移出循环的语句有:
  1. CONTINUE Statement (当它将控制转移到封闭标记循环的下一次迭代时)
  2. EXIT Statement
  3. GOTO Statement
  4. RAISE Statement
  • label:标识 basic_loop_statement 的标签。CONTINUE、EXIT 和 GOTO 语句可以引用此标签。标签提高了可读性,尤其是当 LOOP 语句嵌套时,但前提是您确保 END LOOP 语句中的标签与同一 LOOP 语句开头的标签匹配(编译器不检查)

示例1如下:Nested, Labeled Basic LOOP Statements with EXIT WHEN Statements (带有 EXIT WHEN 语句的嵌套、标记的基本 LOOP 语句)

set serveroutput on;

DECLARE
  s  PLS_INTEGER := 0;
  i  PLS_INTEGER := 0;
  j  PLS_INTEGER;
BEGIN
  <<outer_loop>>
  LOOP
    i := i + 1;
    j := 0;
    <<inner_loop>>
    LOOP
      j := j + 1;
      s := s + i * j; -- Sum several products
      EXIT inner_loop WHEN (j > 5);
      EXIT outer_loop WHEN ((i * j) > 15);
    END LOOP inner_loop;
  END LOOP outer_loop;
  DBMS_OUTPUT.PUT_LINE
    ('The sum of products equals: ' || TO_CHAR(s));
END;
/
Statement processed.
The sum of products equals: 166

示例2如下:Nested, Unabeled Basic LOOP Statements with EXIT WHEN Statements(带有 EXIT WHEN 语句的嵌套、未标记的基本 LOOP 语句)

DECLARE
 i PLS_INTEGER := 0;
 j PLS_INTEGER := 0;
BEGIN
 LOOP
  i := i + 1;
  DBMS_OUTPUT.PUT_LINE ('i = ' || i);
  LOOP
   j := j + 1;
   DBMS_OUTPUT.PUT_LINE ('j = ' || j);
   EXIT WHEN (j > 3);
  END LOOP;
  DBMS_OUTPUT.PUT_LINE ('Exited inner loop');
  EXIT WHEN (i > 2);
 END LOOP;
 
 DBMS_OUTPUT.PUT_LINE ('Exited outer loop');
END;
/
Statement processed.
i = 1
j = 1
j = 2
j = 3
j = 4
Exited inner loop
i = 2
j = 5
Exited inner loop
i = 3
j = 6
Exited inner loop
Exited outer loop

FOR LOOP Statement Overview

语法格式如下:

在这里插入图片描述

随着 FOR LOOP 语句的每次迭代,它的语句运行,它的索引增加或减少,并且控制返回到循环的顶部。

FOR LOOP 语句在其索引达到指定值时结束,或者当循环内的语句将控制转移到循环外或引发异常时结束。索引也称为 iterand。循环外的语句不能引用 iterand。FOR LOOP 语句运行后,iterand 未定义。


语义解释如下:

  • iterator:迭代器指定迭代对象和迭代控件。循环外的语句不能引用迭代器。循环内的语句可以引用迭代器,但不能改变它的值。FOR LOOP 语句运行后,iterator 未定义
  • statement:语句中的 EXIT、EXIT WHEN、CONTINUE 或 CONTINUE WHEN 会导致循环或循环的当前迭代提前结束
  • label:标识 for_loop_statement 的标签。CONTINUE、EXIT 和 GOTO 语句可以引用此标签。标签提高了可读性,尤其是当 LOOP 语句嵌套时,但前提是您确保 END LOOP 语句中的标签与同一 LOOP 语句开头的标签匹配(编译器不检查)

详细解释如下,FOR LOOP语句具有以下结构:

[ label ] for_loop_header 
  statements
END LOOP [ label ];

for_loop_header ::= FOR iterator LOOP

iterator ::= iterand_decl [, iterand_decl] IN iteration_ctl_seq 

iterand_decl  ::= pls_identifier [ MUTABLE | IMMUTABLE ] [ constrained_type ]          

iteration_ctl_seq ::= qual_iteration_ctl [,]...

qual_iteration_ctl ::= [ REVERSE ] iteration_control  pred_clause_seq

iteration_control ::= stepped_control 
                      | single_expression_control 
                      | values_of_control 
                      | indices_of_control 
                      | pairs_of_control  
                      | cursor_control 

pred_clause_seq ::= [ stopping_pred ] [ skipping_pred ]

stopping_pred ::= WHILE boolean_expression  

skipping_pred ::= WHEN boolean_expression 

stepped_control ::= lower_bound .. upper_bound [ BY step ]

single_expression_control ::= [ REPEAT ] expr

如上,FOR LOOP 语句为循环索引的每个值运行一个或多个语句。一个FOR LOOP 标头指定这个迭代器。迭代器指定迭代对象和迭代控件。迭代控件向 iterand 提供一系列值,以便在循环体中访问。循环体包含针对 iterand 的每个值执行一次的语句。

可用的迭代控件有:

  1. Stepped Range 生成一系列步进数值的迭代控件。当没有指定step时,计数控件是一个步长为1的PLS_INTEGER类型的步进范围
  2. Single Expression 计算单个表达式的迭代控件
  3. Repeated Expression 重复计算单个表达式的迭代控件
  4. Values Of 一个迭代控件,它按顺序从集合中生成所有值。该集合可以是向量值表达式、游标、游标变量或动态 SQL
  5. Indices Of 一个迭代控件,它按顺序从集合中生成所有索引。虽然Values Of列出的所有集合类型都是允许的,但当集合是向量变量时,索引是最有用的
  6. Pairs Of 从集合中生成所有索引和值对的迭代控件。所有允许的值的集合类型都允许对。成对的迭代控制需要两个迭代对象。Cursor 从游标、游标变量或动态 SQL 生成所有记录的迭代控件

FOR LOOP Iterand

FOR LOOP 语句的索引或 iterand 被隐式或显式声明为循环的局部变量:

  • 循环中的语句可以读取 iterand 的值,但不能更改它
  • 循环外的语句不能引用 iterand
  • FOR LOOP 语句运行后,iterand 未定义

循环 iterand 有时称为循环计数器。


示例1如下:FOR LOOP Statement Tries to Change Index Value

-- 在此示例中,FOR LOOP 语句试图更改其索引的值,从而导致错误

BEGIN
  FOR i IN 1..3 LOOP
    IF i < 3 THEN
      DBMS_OUTPUT.PUT_LINE (TO_CHAR(i));
    ELSE
      i := 2;
    END IF;
  END LOOP;
END;
/
ORA-06550: line 6, column 7:
PLS-00363: expression 'I' cannot be used as an assignment target

示例2如下:Outside Statement References FOR LOOP Statement Index

-- 在此示例中,FOR LOOP 语句之外的语句引用了循环索引,从而导致错误

BEGIN
  FOR i IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i));
  END LOOP;
  
  DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i));
END;
/
ORA-06550: line 6, column 58:
PLS-00201: identifier 'I' must be declared 

示例3如下:FOR LOOP Statement Index with Same Name as Variable

-- 如果 FOR LOOP 语句的索引与封闭块中声明的变量同名,则局部隐式声明将隐藏其他声明,如本例所示

DECLARE
  i NUMBER := 5;
BEGIN
  FOR i IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i));
  END LOOP;
  
  DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i));
END;
/
Statement processed.
Inside loop, i is 1
Inside loop, i is 2
Inside loop, i is 3
Outside loop, i is 5

示例4如下:FOR LOOP Statement References Variable with Same Name as Index

-- 此示例说明如何更改上面示例 以允许循环内的语句引用封闭块中声明的变量

<<main>>  -- Label block.
DECLARE
  i NUMBER := 5;
BEGIN
  FOR i IN 1..3 LOOP
    DBMS_OUTPUT.PUT_LINE (
      'local: ' || TO_CHAR(i) || ', global: ' ||
      TO_CHAR(main.i)  -- Qualify reference with block label.
    );
  END LOOP;
END main;
/
Statement processed.
local: 1, global: 5
local: 2, global: 5
local: 3, global: 5

示例5如下:Nested FOR LOOP Statements with Same Index Name

-- 在此示例中,嵌套的 FOR 循环语句的索引具有相同的名称。 内循环通过使用外循环的标签限定引用来引用外循环的索引。 仅为了清楚起见,内部循环还使用自己的标签限定对其索引的引用

BEGIN
  <<outer_loop>>
  FOR i IN 1..3 LOOP
    <<inner_loop>>
    FOR i IN 1..3 LOOP
      IF outer_loop.i = 2 THEN
        DBMS_OUTPUT.PUT_LINE
          ('outer: ' || TO_CHAR(outer_loop.i) || ' inner: '
           || TO_CHAR(inner_loop.i));
      END IF;
    END LOOP inner_loop;
  END LOOP outer_loop;
END;
/
Statement processed.
outer: 2 inner: 1
outer: 2 inner: 2
outer: 2 inner: 3

Iterand Mutability

iterand 的可变性属性决定了它是否可以在循环体中赋值。如果迭代器中指定的所有迭代控件都是游标控件,则默认情况下 iterand 是可变的。否则,iterand 是不可变的。通过在 iterand 变量后指定 MUTABLE 或 IMMUTABLE 关键字,可以在 iterand 声明中更改 iterand 的默认可变性属性。

声明 iterand 可变时的注意事项:

  1. 对迭代控制值的 iterand 或一对迭代控制的 iterand 值的任何修改都不会影响该迭代控制产生的值序列
  2. 对步进范围迭代控制或重复单表达式迭代控制的 iterand 的任何修改都可能影响该控件的行为及其产生的值序列
  3. 当 PL/SQL 编译器可以确定使 iterand 可变可能会对运行时性能产生不利影响时,它可能会报告警告

Multiple Iteration Controls

多个迭代控件可以通过用逗号分隔来链接在一起。

每个迭代控件都有一组控制表达式(有些控件没有),这些表达式在控件启动时计算一次。评估这些表达式或将评估值转换为 iterand 类型可能会引发异常。在这种情况下,将放弃循环并进行正常的异常处理。iterand 可在迭代控件列表中访问。它最初设置为其类型的默认值。如果该类型具有非空约束,则在第一次迭代控制的控制表达式中对 iterand 的任何引用都将产生语义错误,因为 iterand 不能被隐式初始化。当迭代控制耗尽时,iterand 包含在处理该迭代控制时分配给它的最终值,并且执行前进到下一个迭代控制。如果迭代控制没有为 iterand 分配任何值,它会保留在该迭代控制开始之前的值。如果在循环体中修改了可变 iterand 的最终值,则在评估来自以下迭代控制的控制表达式时,修改后的值将是可见的。

Expanding Multiple Iteration Controls Into PL/SQL:第一个迭代控件被初始化。评估第一个迭代控制的循环。评估来自下一个迭代控制的控制表达式。评估第二次迭代控制的循环。依次评估每个迭代控制和循环,直到没有更多的迭代控制。


示例1如下:Using Multiple Iteration Controls(19C 不支持,高版本支持)

-- 这个例子展示了循环变量 i 连续三个迭代控制取值。 打印迭代器的值用于演示目的。 它表明当一个循环控制用完时,下一个迭代控制开始。 当最后一次迭代控制用完时,循环完成

DECLARE
   i PLS_INTEGER;
BEGIN
   FOR i IN 1..3, REVERSE i+1..i+10, 51..55 LOOP
      DBMS_OUTPUT.PUT_LINE(i);
   END LOOP;
END;
/
1
2
3
13
12
11
10
9
8
7
6
5
4
51
52
53
54
55

Stepped Range Iteration Controls

步进范围迭代控件生成一系列 numeric 类型数值。控制表达式是下限、上限和步长。

stepped_control ::= [ REVERSE ] lower_bound..upper_bound [ BY step ]
lower_bound ::= numeric_expression
upper_bound ::= numeric_expression
step ::= numeric_expression

Expanding Stepped Range Iteration Controls Into PL/SQL:当迭代控制被初始化时,每个控制表达式被计算并转换为迭代对象的类型。Step 必须具有严格的正数值。如果在评估控制表达式时发生任何异常,则放弃循环并进行正常的异常处理。当没有指定步骤时,它的值为一。步进范围迭代控制生成的值从下限逐步到上限。当指定 REVERSE 时,值从上限到下限逐步递减。如果 iterand 具有浮点类型,则循环控制值的某些组合可能会由于舍入错误而创建无限循环。没有语义或动态分析会报告这一点。当 iterand 是可变的并且在循环体中被修改时,修改后的值用于下一个 iterand 更新中的增量和循环耗尽测试。这可能会改变循环处理的值的顺序。


示例1如下:FOR LOOP Statements Range Iteration Control

-- 在这个例子中,iterand i 的 lower_bound 为 1,upper_bound 为 3。循环打印从 1 到 3 的数字

BEGIN
  FOR i IN 1..3 LOOP
     DBMS_OUTPUT.PUT_LINE (i);
  END LOOP;
END;
/
Statement processed.
1
2
3

示例2如下:Reverse FOR LOOP Statements Range Iteration Control

-- 此示例中的 FOR LOOP 语句打印从 3 到 1 的数字。循环变量 i 被隐式声明为 PLS_INTEGER(计数和索引循环的默认值)

BEGIN
   FOR i IN REVERSE 1..3 LOOP
      DBMS_OUTPUT.PUT_LINE (i);
   END LOOP;
END;
/ 
Statement processed.
3
2
1

示例3如下:Stepped Range Iteration Controls(19C 不支持,高版本支持)

-- 此示例显示了一个显式声明为 NUMBER(5,1) 的循环变量 n。 计数器的增量为 0.5

BEGIN
   FOR n NUMBER(5,1) IN 1.0 .. 3.0 BY 0.5 LOOP
      DBMS_OUTPUT.PUT_LINE(n);
   END LOOP;
END;
/
1
1.5
2
2.5
3

示例4如下:STEP Clause in FOR LOOP Statement(19C 不支持,高版本支持)

-- 在此示例中,FOR 循环有效地将索引递增 5

BEGIN
  FOR i IN 5..15 BY 5 LOOP
    DBMS_OUTPUT.PUT_LINE (i);
  END LOOP;
END;
5
10
15

示例5如下:Simple Step Filter Using FOR LOOP Stepped Range Iterator

-- 此示例说明了一个简单的步进过滤器。 该滤波器用于信号处理和其他缩减应用。 
-- 谓词指定将原始集合的第 K 个元素传递给正在创建的集合

FOR i IN start..finish LOOP
   IF (i - start) MOD k = 0 THEN
      newcol(i) := col(i)
   END IF;
END LOOP;  

您可以使用步进范围迭代器实现步进过滤器:

FOR i IN start..finish BY k LOOP
   newcol(i) := col(i)
END LOOP;

您可以通过使用嵌入在限定表达式中的逐步迭代控件创建新集合来实现相同的过滤器:

newcol := col_t(FOR I IN start..finish BY k => col(i));

Single Expression Iteration Controls

单个表达式迭代控件生成单个值:

single_expression_control ::= [ REPEAT ] expr

单个表达式迭代控件没有控制表达式。当 iterand 是可变的时,在重新评估 repeat 形式的表达式时,将看到在循环体中对其所做的更改。

Expanding Single Expression Iteration Controls Into PL/SQL:计算表达式,将其转换为 iterand 类型以创建下一个值。评估任何停止谓词。如果它未能评估为 TRUE,则迭代控制用尽。评估任何跳过的谓词。如果它无法评估为 TRUE,请跳过下一步。评估循环体。如果指定了 REPEAT,则再次计算表达式。否则,迭代控制用尽。


示例1如下:Single Expression Iteration Control(19C 不支持,高版本支持)

-- 此示例显示循环体被执行一次

BEGIN
   FOR i IN 1 LOOP
      DBMS_OUTPUT.PUT_LINE(i);
   END LOOP;
END;
/
1

此示例显示 iterand 从 1 开始,然后重复计算 i*2,直到停止谓词计算为真:(19C 不支持,高版本支持)

BEGIN
   FOR i IN 1, REPEAT i*2 WHILE i < 100 LOOP
      DBMS_OUTPUT.PUT_LINE(i);
   END LOOP;
END;
/
1
2
4
8
16
32
64

Collection Iteration Controls

VALUES OF、INDICES OF 和 PAIRS OF 迭代控件为从集合派生的 iterand 生成值序列。

collection_iteration_control ::= values_of_control 
                                 | indices_of_control 
                                 | pairs_of_control 

values_of_control ::= VALUES OF expr 
                      | VALUES OF (cursor_object)
                      | VALUES OF (sql_statement)
                      | VALUES OF cursor_variable  
                      | VALUES OF (dynamic_sql) 

indices_of_control ::= INDICES OF expr 
                      | INDICES OF (cursor_object)
                      | INDICES OF (sql_statement)
                      | INDICES OF cursor_variable 
                      | INDICES OF (dynamic_sql)  

pairs_of_control ::= PAIRS OF expr 
                      | PAIRS OF (cursor_object) 
                      | PAIRS OF (sql_statement)
                      | PAIRS OF cursor_variable
                      | PAIRS OF (dynamic_sql) 

集合本身就是控制表达式。该集合可以是向量值表达式、游标对象、游标变量或动态 SQL。如果集合为空,则将其视为已定义且为空。

cursor_object 是一个显式的 PL/SQL 游标对象。sql_statement 是为迭代控制中直接指定的 SQL 语句创建的隐式 PL/SQL 游标对象。cursor_variable 是一个 PL/SQL REF CURSOR 对象。

当迭代控制值的 iterand 或 VALUES OF 迭代控制的值 iterand 在循环体中被修改时,这些更改不会影响迭代控制生成的下一个值。

如果在循环体中修改集合,则行为未指定。如果在循环体执行期间通过 iterand 以外的方式访问游标变量,则行为未指定。大多数 INDICES OF 迭代控件产生一个数字序列,除非集合是一个向量变量。


Expanding VALUES OF Iteration Controls into PL/SQL:该集合被评估并分配给一个向量。如果集合为空,则迭代控制耗尽。临时隐藏索引用第一个元素的索引初始化(如果指定了 REVERSE,则为最后一个元素)。根据临时索引从集合中获取一个值,为 iterand 创建下一个值。评估任何停止谓词。如果它未能评估为 TRUE,则迭代控制用尽。评估任何跳过的谓词。如果它无法评估为 TRUE,请跳过下一步。评估循环体。将临时索引推进到向量中下一个元素的索引(REVERSE 的前一个元素)。确定下一个值并用每个 iterand 值重复,直到迭代控制耗尽。

示例1如下:VALUES OF Iteration Control(19C 不支持,高版本支持)

-- 此示例打印集合 vec 中的值:[11, 10, 34]。迭代控制变量 i 的 iterand 值是向量中第一个元素的值,然后是下一个元素,最后一个元素


DECLARE
   TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
   vec intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
BEGIN
   FOR i IN VALUES OF vec LOOP
      DBMS_OUTPUT.PUT_LINE(i);
   END LOOP;
END;
/
11 10 34

Expanding INDICES OF Iteration Controls into PL/SQL:该集合被评估并分配给一个向量。如果集合为空,则迭代控制耗尽。确定 iterand 的下一个值(如果指定了 REVERSE,则为第一个元素或最后一个元素的索引)。下一个值被分配给 iterand。评估任何停止谓词。如果它未能评估为 TRUE,则迭代控制用尽。评估任何跳过的谓词。如果它无法评估为 TRUE,请跳过下一步。评估循环体。将 iterand 推进到下一个值,该值是向量中下一个元素的索引(REVERSE 的前一个元素)。 重复每个 iterand 值(分配下一个或前一个元素的索引),直到迭代控制耗尽。

示例1如下:INDICES OF Iteration Control(19C 不支持,高版本支持)

-- 此示例打印集合 vec 的索引:[1, 3, 100]。迭代控制变量 i 的 iterand 值是向量中第一个元素的索引,然后是下一个元素和最后一个元素

DECLARE
   TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
   vec intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
BEGIN
   FOR i IN INDICES OF vec LOOP 
       DBMS_OUTPUT.PUT_LINE(i);  
   END LOOP;
END;
/
1 3 100

Expanding PAIRS OF Iteration Controls into PL/SQL:该集合被评估并分配给一个向量。如果集合为空,则迭代控制耗尽。确定 iterand 的下一个索引值(如果指定了 REVERSE,则为第一个元素或最后一个元素的索引)。由下一个值索引的元素的下一个值被分配给 iterand。评估任何停止谓词。如果它未能评估为 TRUE,则迭代控制用尽。评估任何跳过的谓词。如果它无法评估为 TRUE,请跳过下一步。评估循环体。将 iterand 推进到下一个索引值,该值是向量中下一个元素的索引(REVERSE 的前一个元素)。重复每个 iterand 值,直到用尽迭代控制。

示例1如下:PAIRS OF Iteration Control(19C 不支持,高版本支持)

-- 此示例将集合 vec 反转为集合结果并打印生成的索引值对 (10 => 3, 11 => 1, 34 => 100)


DECLARE
   TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
   vec  intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
   result intvec_t;
BEGIN
   result := intvec_t(FOR i,j IN PAIRS OF vec INDEX j => i);
   FOR i,j IN PAIRS OF result LOOP
      DBMS_OUTPUT.PUT_LINE(i || '=>'|| j);
   END LOOP;
END;
/
10=>3 11=>1 34=>100

Cursor Iteration Controls

游标迭代控件生成由显式或隐式游标返回的记录序列。游标定义是控制表达式。您不能将 REVERSE 与游标迭代控件一起使用。

cursor_iteration__control ::=  { cursor _object
                    | sql_statement
                    | cursor_variable
                    | dynamic_sql }

cursor_object是一个显式的 PL/SQL 游标对象。sql_statement是为迭代控制中直接指定的 SQL 语句创建的隐式 PL/SQL 游标对象。cursor_variable 是一个 PL/SQL REF CURSOR 对象。游标迭代控件等同于其集合是游标的 VALUES OF 迭代控件。在循环体内修改iterand时,对迭代控制产生的下一个值没有影响。

当集合为游标变量时,遇到迭代控制时必须打开,否则会抛出异常。当迭代控制用尽时,它保持打开状态。如果在循环体执行期间通过 iterand 以外的方式访问游标变量,则行为未指定。

Expanding Cursor Iteration Controls Into PL/SQL:计算游标以创建一个迭代向量。如果向量为空,则迭代控制用尽。在向量中获取一个值以创建 iterand 的下一个值。评估任何停止谓词。如果它未能评估为 TRUE,则迭代控制用尽。评估任何跳过的谓词。如果它无法评估为 TRUE,请跳过下一步。评估循环体。对获取的每个 iterand 值重复相同的操作,直到迭代控制用尽。

示例1如下:Cursor Iteration Controls(19C 不支持,高版本支持)

OPEN c FOR SELECT id, data FROM T;
FOR r rec_t IN c LOOP
   result(r.id) := r.data;
END LOOP;
CLOSE c;

Using Dynamic SQL in Iteration Controls

...
dynamic_sql ::= EXECUTE IMMEDIATE dynamic_sql_stmt [ using_clause ]

using_clause ::= USING [ [ IN ] (bind_argument [,])+ ]

如上,动态 SQL 可用于游标或集合迭代控制。这样的构造不能提供默认类型;如果它被用作第一个迭代控件,则必须为 iterand(或一对控件的值 iterand)指定一个显式类型。using_clause 是唯一允许的子句。不得使用 INTO 或动态返回子句。如果指定的 SQL 语句是一种不能返回任何行的类型,将报告运行时错误,类似于在普通的立即执行语句上指定批量收集到或进入子句所报告的错误。


示例1如下:Using Dynamic SQL As An Iteration Control(19C 不支持,高版本支持)

-- 此示例显示迭代控件从动态 SQL 生成所有记录。 它打印 employee_id 小于 103 的所有员工的 last_name 和 employee_id。它在停止谓词为 TRUE 时执行循环体

DECLARE
   cursor_str VARCHAR2(500) := 'SELECT last_name, employee_id FROM hr.employees ORDER BY last_name';
   TYPE rec_t IS RECORD (last_name VARCHAR2(25),
                         employee_id NUMBER);
BEGIN
   FOR r rec_t IN VALUES OF (EXECUTE IMMEDIATE cursor_str) WHEN r.employee_id < 103 LOOP
      DBMS_OUTPUT.PUT_LINE(r.last_name || ', ' || r.employee_id);
   END LOOP;
END;
/
De Haan, 102
King, 100
Kochhar, 101

示例2如下:Using Dynamic SQL As An Iteration Control In a Qualified Expression

v := vec_rec_t( FOR r rec_t IN (EXECUTE IMMEDIATE query_var) SEQUENCE => r); 

Stopping and Skipping Predicate Clauses

停止谓词子句可能导致迭代控制耗尽,而跳过谓词子句可能导致某些值跳过循环体。这些谓词子句中的表达式不是控制表达式。

pred_clause_seq ::= [stopping_pred] [skipping_pred]

stopping_pred ::= WHILE boolean_expression  

skipping_pred ::= WHEN boolean_expression 

停止谓词子句可能导致迭代控制耗尽。boolean_expression 在循环的每次迭代开始时计算。如果它未能评估为 TRUE,则迭代控制用尽。
跳过谓词子句可能会导致某些值跳过循环体。计算 boolean_expression。如果它的计算结果为 TRUE 失败,迭代控制将跳到下一个值。


示例1如下:Using FOR LOOP Stopping Predicate Clause(19C 不支持,高版本支持)

-- 此示例显示带有 WHILE 停止谓词子句的迭代控制 如果停止谓词的计算结果不为 TRUE,则迭代控制用尽

BEGIN
   FOR power IN 1, REPEAT power*2 WHILE power <= 64 LOOP
      DBMS_OUTPUT.PUT_LINE(power);
   END LOOP;
END;
/
1
2
4
8
16
32
64

示例2如下:Using FOR LOOP Skipping Predicate Clause

-- 此示例显示带有 WHEN 跳过谓词子句的迭代控制。如果跳过谓词的计算结果不为真,则迭代控制跳到下一个值

BEGIN
   FOR power IN 2, REPEAT power*2 WHILE power <= 64 WHEN MOD(power, 32)= 0 LOOP
      DBMS_OUTPUT.PUT_LINE(power);
   END LOOP;
END;
/
2
32
64

EXIT CONTINUE

示例1如下:EXIT WHEN Statement in FOR LOOP Statement

create table employees (employee_id number(6), last_name VARCHAR2(25));

insert into employees values (120, 'Weiss');
insert into employees values (121, 'Weiss1');
insert into employees values (122, 'Weiss2');
insert into employees values (123, 'Weiss3');
insert into employees values (124, 'Weiss4');

DECLARE
  v_employees employees%ROWTYPE;
  CURSOR c1 is SELECT * FROM employees;
BEGIN
  OPEN c1;
  -- Fetch entire row into v_employees record:
  FOR i IN 1..10 LOOP
    FETCH c1 INTO v_employees;
    EXIT WHEN c1%NOTFOUND;
 DBMS_OUTPUT.PUT_LINE (v_employees.last_name);
    -- Process data here
  END LOOP;
  CLOSE c1;
END;
/
Table created.

1 row(s) inserted.

1 row(s) inserted.

1 row(s) inserted.

1 row(s) inserted.

1 row(s) inserted.

Statement processed.
Weiss
Weiss1
Weiss2
Weiss3
Weiss4

示例2如下:EXIT WHEN Statement in Inner FOR LOOP Statement

DECLARE
  v_employees employees%ROWTYPE;
  CURSOR c1 is SELECT * FROM employees;
BEGIN
  OPEN c1;
  
  -- Fetch entire row into v_employees record:
  <<outer_loop>>
  FOR i IN 1..10 LOOP
    -- Process data here
    FOR j IN 1..10 LOOP
      FETCH c1 INTO v_employees;
      EXIT outer_loop WHEN c1%NOTFOUND;
   DBMS_OUTPUT.PUT_LINE (v_employees.last_name);
      -- Process data here
    END LOOP;
  END LOOP outer_loop;
 
  CLOSE c1;
END;
/
Statement processed.
Weiss
Weiss1
Weiss2
Weiss3
Weiss4

示例3如下:CONTINUE WHEN Statement in Inner FOR LOOP Statement

DECLARE
  v_employees employees%ROWTYPE;
  CURSOR c1 is SELECT * FROM employees;
BEGIN
  OPEN c1;
  
  -- Fetch entire row into v_employees record:
  <<outer_loop>>
  FOR i IN 1..10 LOOP
    -- Process data here
    FOR j IN 1..10 LOOP
      FETCH c1 INTO v_employees;
      CONTINUE outer_loop WHEN c1%NOTFOUND;
   DBMS_OUTPUT.PUT_LINE (v_employees.last_name);
      -- Process data here
    END LOOP;
  END LOOP outer_loop;
 
  CLOSE c1;
END;
/
Statement processed.
Weiss
Weiss1
Weiss2
Weiss3
Weiss4

WHILE LOOP Statement

WHILE LOOP 语句在条件为真时运行一个或多个语句。

[ label ] WHILE condition LOOP
  statements
END LOOP [ label ];

语法格式如下:

在这里插入图片描述

语义解释如下:

  • boolean_expression:值为 TRUE、FALSE 或 NULL 的表达式。boolean_expression 在循环的每次迭代开始时计算。如果其值为 TRUE,则运行 LOOP 之后的语句。否则,控制转移到 WHILE LOOP 语句之后的语句

  • Statement:为防止无限循环,至少有一条语句必须将 boolean_expression 的值更改为 FALSE 或 NULL,将控制转移到循环外,或引发异常。可以在循环外转移控制的语句是:

  1. CONTINUE Statement (when it transfers control to the next iteration of an enclosing labeled loop)
  2. EXIT Statement
  3. GOTO Statement
  4. RAISE Statement
  • label:标识 while_loop_statement 的标签。 CONTINUE、EXIT 和 GOTO 语句可以引用此标签。标签提高了可读性,尤其是当 LOOP 语句嵌套时,但前提是您确保 END LOOP 语句中的标签与同一 LOOP 语句开头的标签匹配(编译器不检查)

示例1如下:WHILE LOOP Statements

-- 第一个 WHILE LOOP 语句中的语句永远不会运行,而第二个 WHILE LOOP 语句中的语句只运行一次

DECLARE
  done  BOOLEAN := FALSE;
BEGIN
  WHILE done LOOP
    DBMS_OUTPUT.PUT_LINE ('This line does not print.');
    done := TRUE;  -- This assignment is not made.
  END LOOP;

  WHILE NOT done LOOP
    DBMS_OUTPUT.PUT_LINE ('Hello, world!');
    done := TRUE;
  END LOOP;
END;
/
Statement processed.
Hello, world!

语句中的 EXIT、EXIT WHEN、CONTINUE 或 CONTINUE WHEN 会导致循环或循环的当前迭代提前结束。

一些语言有一个 LOOP UNTIL 或 REPEAT UNTIL 结构,它在循环底部而不是顶部测试条件,以便语句至少运行一次。要在 PL/SQL 中模拟此结构,请使用带有 EXIT WHEN 语句的基本 LOOP 语句:

LOOP
  statements
  EXIT WHEN condition;
END LOOP;