Skip to content

适用虚谷数据库版本

v12.9



适用虚谷数据库版本

v12.9


控制结构

📄字数 6.3K
👁️阅读量 加载中...

PL/SQL 的控制结构用于控制程序的执行流程,主要分为三大类:条件语句、循环语句和跳转语句。这些结构允许你根据条件执行不同的代码块,重复执行特定操作,或改变程序的执行顺序。

一、条件语句

在 PL/SQL 中,条件语句用于根据不同的条件执行不同的代码块。主要的条件控制结构包括 IF-THEN-ELSE 和 CASE 语句,它们提供了灵活的逻辑判断能力。

1.1 IF 语法格式

(pl_stmt_list::=)

1.2 IF 参数说明

  • bool_expr:布尔表达式,如果该表达式的值为真,则执行 THEN 后面的 pl_stmt_list;若为假且有 ELSIF 则依次进行条件判断,若均未匹配成功则执行 ELSE 分支(若有)pl_stmt_list 或退出该条件构造逻辑。
  • ELSIF:ELSIF 关键字用于添加额外的条件判断,可以有多个ELSIF子句。
  • ELSE:ELSE 关键字用于指定当所有 IF 和 ELSIF 条件都不满足时执行的语句。
  • name_space:结束 IF 语句后可带名空间(编译器不检查),用于标识具体的 IF 语句,通常用于嵌套的 IF 语句中。

1.3 CASE 语法格式

(pl_stmt_list::=)

1.4 CASE 参数说明

  • case_arg:可选参数。case_arg表达式为When_item中的条件清单,该参数类型同When_item中条件清单类型一致,通过与When_item中条件清单按顺序依次比较,若相同则为 true 并执行 THEN 后面的 pl_stmt_list,剩下的 CASE 部分将会被自动忽略。
  • WHEN bool_expr THEN pl_stmt_list:用于选择判断,类似于IF..ELSE。若case_arg为空,则仅对bool_expr进行判断,然后根据判断结果执行第一条判断为真的表达式对应pl_stmt_list
  • bool_expr:布尔表达式。
  • ELSE:默认操作,若无匹配成功的 WHEN 子句,则会执行 ELSE 后的pl_stmt_list

注意

  • NULL 值处理:CASE 语句中,WHEN NULL 不会匹配 NULL 值,需使用 IS NULL。
  • 选择与搜索:CASE 语句若指定了case_arg,则为选择型,使用一个选择器,其值用于选择几个备选方案之一;若未指定 case_arg,则为搜索型,它的 WHEN 子句包含产生布尔值的搜索条件。
  • 条件顺序:IF-THEN-ELSE 和 CASE 语句都会按顺序评估条件,一旦满足某个条件,后续条件将被忽略。
  • 异常处理:条件表达式中若出现异常,会终止整个条件语句。

1.5 条件语句示例

  • 示例1

    IF-THEN-ELSE使用演示。

    sql
    SQL> DECLARE
         v_age NUMBER := 25;
         BEGIN
             IF v_age < 18 THEN
                 DBMS_OUTPUT.PUT_LINE('未成年人');
             ELSIF v_age >= 18 AND v_age < 65 THEN
                 DBMS_OUTPUT.PUT_LINE('成年人');
             ELSE
                 DBMS_OUTPUT.PUT_LINE('老年人');
             END IF;
         END;
         /
    
      --输出
      成年人
  • 示例2

    嵌套 IF 语句使用演示。

    sql
    SQL> DECLARE
          salary NUMBER := 60;
          department VARCHAR := 'IT';
          bonus NUMBER;
         BEGIN
             IF salary > 50 THEN
                  IF department = 'IT' THEN
                      bonus := salary * 0.2;
                  ELSE
                      bonus := salary * 0.15;
                  END IF;
             ELSE
                  bonus := salary * 0.1;
             END IF;
             DBMS_OUTPUT.PUT_LINE(bonus);
         END;
         /
    
      --输出
      12
  • 示例3

    选择型 CASE 语句使用演示。

    sql
    SQL> DECLARE
         v_dept_id NUMBER := 20;
         v_dept_name VARCHAR2(50);
         BEGIN
             v_dept_name := 
                 CASE v_dept_id
                     WHEN 10 THEN '人力资源部'
                     WHEN 20 THEN '财务部'
                     WHEN 30 THEN '市场部'
                     ELSE '未知部门'
                 END;
             DBMS_OUTPUT.PUT_LINE('部门名称: ' || v_dept_name);
         END;
         /
      --输出
      部门名称: 财务部
  • 示例4

    搜索型 CASE 语句使用演示。

    sql
    SQL> DECLARE
         v_salary NUMBER := 80;
         v_level VARCHAR2(20);
         BEGIN
             v_level := 
                 CASE
                     WHEN v_salary < 50 THEN '低级'
                     WHEN v_salary >= 50 AND v_salary < 100 THEN '中级'
                     WHEN v_salary >= 100 THEN '高级'
                     ELSE '未知'
                 END;
             DBMS_OUTPUT.PUT_LINE('等级: ' || v_level);
         END;
         /
      --输出
      等级: 中级

二、循环语句

在 PL/SQL 里,循环语句能够让代码块重复执行。下面介绍四种主要的循环结构(LOOP、WHILE、FOR、FORALL)及其使用方式。

  • LOOP:无条件循环,最基础的循环结构,它会一直执行代码块,直到遇到 EXIT 语句;
  • WHILE:条件判断在前,在每次执行循环体之前,都会先对条件进行检查,只有条件为真时,才会执行循环体;
  • FOR:范围式循环,按照指定的次数或游标行来执行循环,循环变量会自动递增或者递减;
  • FORALL:类似 FOR 循环,无游标操作,增加集合类型操作,且只能执行一个 DML 语句(UPDATE、INSERT、DELETE、EXECUTE)。

2.1 LOOP 语法格式

(pl_stmt_list::=)

2.2 LOOP 参数说明

  • name_space:结束 LOOP 语句后可带名空间(编译器不检查),用于标识具体的 LOOP 语句,通常用于嵌套的 LOOP 语句中。

2.3 WHILE 语法格式

(pl_stmt_list::=)

2.4 WHILE 参数说明

  • bool_expr:布尔表达式,在每次执行循环体之前,都会先对表达式条件进行检查,只有条件为真时,才会执行循环体。

2.5 FOR 语法格式

(select_with_parens::=, pl_stmt_list::=)

2.6 FOR 参数说明

  • ColumnName:循环变量,用于存储当前的循环值,无需提前声明,会自动创建并在循环结束后销毁。
  • REVERSE:倒序循环关键字,后续循环定义参数必须从大到小,否则将不进入循环体。
  • lower_bound..higher_bound:循环起始和结束值范围,ColId 在此范围内从小到大,步长为1,每次循环自增(若有 REVERSE 关键字则步长为-1自减),直到超过结束值后,程序退出循环。
  • IDENT:一般为游标名,需提前声明该游标,此时 ColumnName 用于存储从游标中提取的每一行数据集,每次循环取一条,直到最后一条止。
  • name_space:结束 FOR LOOP 语句后可带名空间(编译器不检查),用于标识具体的 FOR LOOP 语句,通常用于嵌套的 FOR LOOP 语句中。

2.7 FORALL 语法格式

2.8 FORALL 参数说明

  • ColumnName:循环记数变量,用于存储当前的循环值,无需提前声明,会自动创建并在循环结束后销毁。
  • lower_bound..higher_bound:循环起始和结束值范围,ColId 在此范围内从小到大,步长为1,每次循环自增,直到超过结束值后,程序退出循环。
  • INDICES OF:遍历集合的有效索引(适用于稀疏集合)。
  • IDENT:一般为集合名,需提前声明该集合,此时 ColumnName 用于存储集合的有效索引。
  • BETWEEN lower_bound AND higher_bound:限制有效索引的范围在 lower_bound 和 higher_bound 之间。
  • VALUES OF:直接遍历集合 IDENT 中的值,而非索引,目前要求集合的成员变量类型必须是整型。
  • dml_stmt1:一个 DML 语句,只能为 UPDATE、INSERT、DELETE、EXECUTE 其中之一。

注意

  • 避免出现无限循环,LOOP 循环内必须使用 EXIT 语句退出,WHILE 循环则需检查是否更新循环变量。

2.9 循环语句示例

  • 示例1

    LOOP 语句使用演示。

    sql
    SQL> DECLARE
             v_counter NUMBER := 1;
         BEGIN
           LOOP
             DBMS_OUTPUT.PUT_LINE('当前数值为:' || v_counter);
             v_counter := v_counter + 1;
             EXIT WHEN v_counter > 5;  -- 当v_counter大于5时,结束循环
           END LOOP;
         END;
         /
    
      --输出
      当前数值为:1
      当前数值为:2
      当前数值为:3
      当前数值为:4
      当前数值为:5
  • 示例2

    WHILE 语句使用演示。

    sql
    SQL> DECLARE
           v_counter NUMBER := 1;
         BEGIN
           WHILE v_counter <= 5 LOOP  -- 先判断条件,再执行循环
             DBMS_OUTPUT.PUT_LINE('当前数值为:' || v_counter);
             v_counter := v_counter + 1;
           END LOOP;
         END;
         /
    
      --输出
      当前数值为:1
      当前数值为:2
      当前数值为:3
      当前数值为:4
      当前数值为:5
  • 示例3

    FOR 语句使用演示,数值范围型,正向和反向。

    sql
    SQL> BEGIN
           -- 正向循环,从1递增到5
           FOR i IN 1..5 LOOP
             DBMS_OUTPUT.PUT_LINE('正向数值:' || i);
           END LOOP;
           -- 反向循环,从5递减到1
           FOR i IN REVERSE 5..1 LOOP
             DBMS_OUTPUT.PUT_LINE('反向数值:' || i);
           END LOOP;
         END;
         /
      --输出
      正向数值:1
      正向数值:2
      正向数值:3
      正向数值:4
      正向数值:5
      反向数值:5
      反向数值:4
      反向数值:3
      反向数值:2
      反向数值:1
  • 示例4

    FOR 语句使用演示,游标型。

    sql
    SQL> CREATE TABLE tab_forcur (table_name VARCHAR2(100), table_type VARCHAR2(50));
    SQL> INSERT INTO tab_forcur (table_name, table_type) VALUES ('TABLE1', 'TABLE')('TABLE2', 'TABLE')('TABLE3', 'VIEW');
    SQL> DECLARE
           CURSOR cur IS
             SELECT * FROM tab_forcur;
         BEGIN
           FOR i IN cur LOOP --使用显式游标
             SEND_MSG(i.table_name);
           END LOOP;
           FOR j IN (SELECT * FROM tab_forcur) LOOP --使用隐式游标
             SEND_MSG(j.table_type);
           END LOOP;
         END;
         /
    
      -- 输出
      TABLE1
      TABLE2
      TABLE3
      TABLE
      TABLE
      VIEW
  • 示例5

    FORALL 语句使用演示,数值范围型。

    sql
    SQL> CREATE TABLE tab_forall (id INT, name VARCHAR2(50));
    SQL> BEGIN
             FORALL i IN 1..5
               INSERT INTO tab_forall VALUES(i,'test'||i);
         END;
         /
    
    SQL> SELECT * FROM tab_forall;
    +----+-------+
    | ID | NAME  |
    +----+-------+
    | 1  | test1 |
    | 2  | test2 |
    | 3  | test3 |
    | 4  | test4 |
    | 5  | test5 |
    +----+-------+
    
    SQL> DECLARE
           TYPE ty_forall IS TABLE OF tab_forall%ROWTYPE INDEX BY BINARY_INTEGER;
           tab_ty_forall ty_forall;
         BEGIN
             FOR i IN 1..5 LOOP
               tab_ty_forall(i).id:=i;
               tab_ty_forall(i).name:='NAME'||i;
             END LOOP;
             FORALL i IN 1..tab_ty_forall.count
               INSERT INTO tab_forall VALUES(tab_ty_forall(i).id,tab_ty_forall(i).name);
         END;
         /
         
    SQL> SELECT * FROM tab_forall;
    +----+-------+
    | ID | NAME  |
    +----+-------+
    | 1  | test1 |
    | 2  | test2 |
    | 3  | test3 |
    | 4  | test4 |
    | 5  | test5 |
    | 1  | NAME1 |
    | 2  | NAME2 |
    | 3  | NAME3 |
    | 4  | NAME4 |
    | 5  | NAME5 |
    +----+-------+
    • 示例6

    FORALL 语句使用演示,INDICES 和 VALUES。

    sql
    SQL> CREATE TABLE tab_forall2(id INT);
    SQL> CREATE TABLE tab_forall3(id INT);
    SQL> DECLARE
           TYPE ty_forall2 IS VARRAY(100) OF INT;
           tab_ty_forall2 ty_forall2 := ty_forall2(1, 3, 2, 5, 3);
         BEGIN
           FORALL i IN INDICES OF tab_ty_forall2 --i取值为1,2,3,4,5
             INSERT INTO tab_forall2 (id) VALUES (tab_ty_forall2(i));
           FORALL i IN VALUES OF tab_ty_forall2 --i取值为1,3,2,5,3
             INSERT INTO tab_forall3 (id) VALUES (tab_ty_forall2(i));
         END;
         /
    
    SQL> SELECT * FROM tab_forall2;
    +----+
    | ID |
    +----+
    | 1  |
    | 3  |
    | 2  |
    | 5  |
    | 3  |
    +----+
    
    SQL> SELECT * FROM tab_forall3;
    +----+
    | ID |
    +----+
    | 1  |
    | 2  |
    | 3  |
    | 3  |
    | 2  |
    +----+

三、跳转语句

在 PL/SQL 里,跳转语句能够改变程序的正常执行流程。下面介绍四种主要的跳转语句(CONTINUE、EXIT、RETURN、GOTO)及其使用方式。

  • CONTINUE:通常在循环语句中使用,用于跳过本次循环的后续内容,继续下一次循环;
  • EXIT:通常在循环语句中使用,用于立即退出当前循环,不再执行循环体内的剩余部分,并且不再进行下一次循环(IF THEN EXIT 同 EXIT WHEN);
  • GOTO:允许程序无条件跳转到代码中的另一个标签位置,需定义 Label 标签名,PL/SQL 中对 GOTO 语句有一些限制,对于块、循环、IF 语句而言,从外层跳转到内层是非法的;
  • RETURN:立刻结束当前子程序的执行,并返回结果(在存储函数中必须使用)。

3.1 CONTINUE 语法格式

3.2 CONTINUE 参数说明

  • CONTINUE:目前仅可单独使用此关键字,用于跳过本次循环的后续内容,继续下一次循环。

3.3 EXIT 语法格式

3.4 EXIT 参数说明

  • bool_expr:布尔表达式,当值为真时,则执行 EXIT,是 IF bool_expr THEN EXIT 的简写。

3.5 GOTO 语法格式

(pl_stmt::=)

3.6 GOTO 参数说明

  • ColumnName:标签名,可跳转到该标签指定的位置或语句;

3.7 RETURN 语法格式

3.8 RETURN 参数说明

  • b_expr:返回值,在存储函数中必须有返回值,且类型需与函数定义的返回类型一致;其余 PL/SQL 子程序中也可包含 RETURN 语句,用于立即结束子程序,且返回值只能为整数,或为空则默认为 NULL。

3.9 跳转语句示例

  • 示例1

    CONTINUE 语句使用演示。

    sql
    SQL> BEGIN
           FOR i IN 1..6 LOOP
             IF i = 5 THEN
               CONTINUE;
             END IF;
             SEND_MSG('VALUE: ' || i);
           END LOOP;
         END;
         /
    
      --输出,可与 EXIT 结果对比理解
      VALUE: 1
      VALUE: 2
      VALUE: 3
      VALUE: 4
      VALUE: 6
  • 示例2

    EXIT 语句使用演示。

    sql
    SQL> BEGIN
           FOR i IN 1..6 LOOP
             IF i = 5 THEN 
               EXIT;
             END IF;
             SEND_MSG('VALUE: ' || i);
           END LOOP;
         END;
         /
    SQL> BEGIN
           FOR i IN 1..6 LOOP
             EXIT WHEN i = 5; --简写效果相同
             SEND_MSG('VALUE: ' || i);
           END LOOP;
         END;
         /
    
      --两种写法输出相同
      VALUE: 1
      VALUE: 2
      VALUE: 3
      VALUE: 4
  • 示例3

    GOTO 语句使用演示,搭配 IF 判断语句实现循环。

    sql
    SQL> DECLARE
            v_num INT := 1;
         BEGIN
             <<loop_start>>
             IF v_num <= 5 THEN
                 DBMS_OUTPUT.PUT_LINE('当前数字为: ' || v_num);
                 v_num := v_num + 1;
                 GOTO loop_start;
             END IF;
         END;
         /
    SQL> DECLARE
            v_num INT := 1;
         BEGIN
             loop_start:
             IF v_num <= 5 THEN
                 DBMS_OUTPUT.PUT_LINE('当前数字为: ' || v_num);
                 v_num := v_num + 1;
                 GOTO loop_start;
             END IF;
         END;
         /
    
      --两种写法输出相同
      当前数字为: 1
      当前数字为: 2
      当前数字为: 3
      当前数字为: 4
      当前数字为: 5
  • 示例4

    RETUEN 语句使用演示。

    sql
    SQL> CREATE FUNCTION func_sum(a INT, b INT) RETURN INT IS
         BEGIN
             IF a < 0 OR b < 0 THEN
                 RETURN NULL;  -- 参数为负数时返回 NULL
             END IF;
             RETURN a + b;  -- 返回两数之和
         END;
         /
    SQL> SELECT func_sum(-2,3) AS RES;
    +--------+
    |  RES   |
    +--------+
    | <NULL> |
    +--------+
    
    SQL> SELECT func_sum(2,3) AS RES;
    +--------+
    |  RES   |
    +--------+
    |   5    |
    +--------+