zl程序教程

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

当前栏目

Oracle的学习心得和知识总结(八)|Oracle数据库PL/SQL语言顺序控制语句之GOTO和NULL语句技术详解

Oracle数据库技术SQL语言 详解 总结 语句
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.3 Sequential Control Statements 官方文档说明,点击前往
7、Oracle数据库 GOTO Statement 官方文档说明,点击前往
8、EDB数据库 GOTO statement v14官方文档说明,点击前往


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


Oracle数据库PL/SQL语言顺序控制语句之GOTO和NULL语句技术详解



本人博客严正声明

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

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

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

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

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

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


文章快速说明索引

学习目标:

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


学习内容:(详见目录)

1、Oracle数据库PL/SQL语言 顺序控制语句之GOTO和NULL语句


学习时间:

2022-11-29 10:25:10


学习产出:

1、Oracle数据库PL/SQL语言 顺序控制语句之GOTO和NULL语句
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>

顺序控制语句说明

与IF和LOOP语句不同,顺序控制语句GOTO和NULL对于PL/SQL编程并不重要。很少需要GOTO语句,它指向一个指定的语句。偶尔,它会简化逻辑,以保证它的使用。NULL语句什么都不做,它可以通过明确条件语句的含义和操作来提高可读性。

  • GOTO子句:GOTO语句无条件地将控制权转移到标签。标签在其作用域中必须是唯一的,并且必须在可执行语句或PL/SQL块之前。运行时,GOTO语句将控制权转移到标记语句或块。有关GOTO语句的限制,下面详细说明。谨慎使用GOTO语句——过度使用会导致代码难以理解和维护。不要使用GOTO语句将控制从深度嵌套结构转移到异常处理程序。相反,抛出一个异常。有关PL/SQL异常处理机制的信息,请参见PL/SQL错误处理。GOTO语句将控制转移到出现引用标签的第一个封闭块。

  • NULL子句:NULL语句只将控制权传递给下一个语句。有些语言将这样的指令称为no-op(无操作)。

NULL语句的一些用法是:

  1. 为GOTO语句提供一个目标
  2. 通过明确条件语句的含义和作用来提高可读性
  3. 创建占位符和存根子程序
  4. 表示你意识到某种可能性,但不需要采取任何行动

GOTO语句的说明

GOTO 语句将控制转移到带标签的块或语句,即:该语句使执行点跳转到具有指定标签的语句。


GOTO 语句的限制如下:

如果 GOTO 语句过早地退出游标 FOR LOOP 语句,游标将关闭。

  • GOTO 语句不能将控制转移到 IF 语句、CASE 语句、LOOP 语句或子块中
  • GOTO 语句不能将控制从一个 IF 语句子句转移到另一个,或从一个 CASE 语句 WHEN 子句转移到另一个
  • GOTO 语句不能将控制转移出子程序
  • GOTO 语句无法将控制转移到异常处理程序中
  • GOTO 语句无法将控制从异常处理程序转移回当前块(但它可以将控制从异常处理程序转移到封闭块 即:父块)
  • GOTO 语句不能将控制转移到条件块或子块中,但可以从条件块或子块中转移控制

  • GOTO 语句不能跳转到声明(declaration)
  • GOTO 语句不能将控制转移到另一个函数或过程中
  • 标签(label)不应放在块、函数或过程的末尾

其语法格式如下:

在这里插入图片描述

GOTO <label>

标签:标识语句label 是分配给可执行语句或块的名称。在函数、过程或块的范围内必须是唯一的。要标记语句,请使用以下语法:

<<label>> <statement>

-- 其中 statement 是程序跳转到的执行点

如果 label 不在当前块中,则 GOTO 语句将控制转移到 label 出现的第一个封闭块。你可以标记(label)赋值语句、任何 SQL 语句和选定的过程语言语句。可以标记的过程语言语句有:

-- EDB (V14)

IF
EXIT
RETURN
RAISE
EXECUTE
PERFORM
GET DIAGNOSTICS
OPEN
FETCH
MOVE
CLOSE
NULL
COMMIT
ROLLBACK
GOTO
CASE
LOOP
WHILE
FOR

exit 被视为关键字,不能用作标签的名称。对应成语法如下:

statement ::= <Assign_Statement>
 | <If_Statement>
 | <Loop_Statement>
 | <While_Statement>
 | <For_Statement>
 | <Goto_Statement> // 这就是我们这次要开发的
 | <Case_Statement>
 | <Exit_Statement>
 | <Return_Statement>
 | <Raise_Statement>
 | <Perform_Statement>
 | <Execute_Statement>
 | <Commit_Statement>
 | <RollBack_Statement>
 | <Close_Statement>
 | <Open_Statement>
 | <Fetch_Statement>
 | <Sql_Statement>
 | <Null_Statement> 
 | <Get_Diagnostics_Statement>

-- 注:在PostgreSQL15中只会更多,详细请见:
-- src/pl/plpgsql/src/pl_gram.y 的 proc_stmt

其使用案例如下:

示例一:标签可以出现在语句之前:

-- 判断一个数是否为素数

SQL> set serveroutput on; 
SQL> DECLARE
  p  VARCHAR2(30);
  n  PLS_INTEGER := 37;
BEGIN
  FOR j in 2..ROUND(SQRT(n)) LOOP
    IF n MOD j = 0 THEN
      p := ' is not a prime number';
      GOTO print_now;
    END IF;
  END LOOP;

  p := ' is a prime number';
 
  <<print_now>> --here
  DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p);
END;
/
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17  37 is a prime number

PL/SQL procedure successfully completed.

SQL>
SQL> DECLARE
  p  VARCHAR2(30);
  n  PLS_INTEGER := 64;
BEGIN
  FOR j in 2..ROUND(SQRT(n)) LOOP
    IF n MOD j = 0 THEN
      p := ' is not a prime number';
      GOTO print_now;
    END IF;
  END LOOP;

  p := ' is a prime number';
 
  <<print_now>> --here
  DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p);
END;
/
 
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17  64 is not a prime number

PL/SQL procedure successfully completed.

SQL>

示例二:标签只能出现在块之前或语句之前:

SQL> DECLARE
  done  BOOLEAN;
BEGIN
  FOR i IN 1..50 LOOP
    IF done THEN
       GOTO end_loop;
    END IF;
    <<end_loop>> --here
  END LOOP;
END;
/
 
  2    3    4    5    6    7    8    9   10   11    END LOOP;
  *
ERROR at line 9:
ORA-06550: line 9, column 3:
PLS-00103: Encountered the symbol "END" when expecting one of the following:
( begin case declare exit for goto if loop mod null raise
return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
json_exists json_value json_query json_object json_array


SQL>

示例三:标签可以出现在 NULL 语句之前:

SQL> DECLARE
  done  BOOLEAN;
BEGIN
  FOR i IN 1..50 LOOP
    IF done THEN
      GOTO end_loop;
    END IF;
    <<end_loop>> --here
    NULL;
  END LOOP;
END;
/
  2    3    4    5    6    7    8    9   10   11   12  
PL/SQL procedure successfully completed.

SQL>
SQL> BEGIN
	FOR i IN 1..50 LOOP
		IF i = 30 THEN
			GOTO end_loop;
		END IF;
	END LOOP;
	<<end_loop>>
	NULL;
END;
/
  2    3    4    5    6    7    8    9   10  
PL/SQL procedure successfully completed.

SQL>

示例四:GOTO 语句可以将控制权从当前块转移到封闭块。如下使用GOTO将分出一个环绕块:

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');

SQL> select * from employees;

EMPLOYEE_ID LAST_NAME
----------- -------------------------
	120 Weiss
	121 Weiss1
	122 Weiss2
	123 Weiss3
	124 Weiss4

SQL>
SQL> DECLARE
  v_last_name  VARCHAR2(25);
  v_emp_id     NUMBER(6) := 120;
BEGIN
  <<get_name>>
  SELECT last_name INTO v_last_name
  FROM employees
  WHERE employee_id = v_emp_id;
  
  BEGIN
    DBMS_OUTPUT.PUT_LINE (v_last_name);
    v_emp_id := v_emp_id + 2;
 
    IF v_emp_id < 125 THEN
      GOTO get_name;
    END IF;
  END;
END;
/
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19  Weiss
Weiss2
Weiss4

PL/SQL procedure successfully completed.

SQL>

示例五:GOTO 语句将控制转移到 IF 语句,导致错误:

SQL> DECLARE
  valid BOOLEAN := TRUE;
BEGIN
  GOTO update_row;
  
  IF valid THEN
  <<update_row>>
    NULL;
  END IF;
END;
/
  2    3    4    5    6    7    8    9   10   11    GOTO update_row;
  *
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'UPDATE_ROW'
ORA-06550: line 6, column 12:
PL/SQL: Statement ignored


SQL>

示例六:GOTO用在循环当中。作用类似于continue:

SQL> create or replace procedure test111 is
  i integer;
begin
  i := 2;
  loop
    <<next_step>>
    i := i * 2;
    if i > 100 then
      exit;
    end if; 
    if i > 50 then
      goto next_step;
    end if;
    dbms_output.put_line(i);
  end loop;
end; 
/
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17  
Procedure created.

SQL>
SQL> call test111();
4
8
16
32

Call completed.

SQL>

如上:

  1. <<next_step>>:这是循环标签,next_step是名字,可以自己定义
  2. goto next_step:表示i > 50后继续走到标签所在的位置执行代码
  3. 特别注意:<<next_step>> 后面不能直接跟EXCEPTION、END LOOP这种关键字类的语句,可以用NULL把标签跟关键字隔开

示例七:下面这个示例验证员工记录是否包含姓名、职位描述和员工雇用日期;如果缺少任何信息,则语句将执行点转移到打印一条消息的语句,该消息表明该员工无效:

-- EDB数据库提供

CREATE OR REPLACE PROCEDURE verify_emp (
    p_empno         NUMBER
)
IS
    v_ename         emp.ename%TYPE;
    v_job           emp.job%TYPE;
    v_hiredate      emp.hiredate%TYPE;
BEGIN
    SELECT ename, job, hiredate
        INTO v_ename, v_job, v_hiredate FROM emp
        WHERE empno = p_empno;
    IF v_ename IS NULL THEN
        GOTO invalid_emp;
    END IF;
    IF v_job IS NULL THEN
        GOTO invalid_emp;
    END IF;
    IF v_hiredate IS NULL THEN
        GOTO invalid_emp;
    END IF;
    DBMS_OUTPUT.PUT_LINE('Employee ' || p_empno ||
        ' validated without errors.');
    RETURN;
    <<invalid_emp>> DBMS_OUTPUT.PUT_LINE('Employee ' || p_empno ||
        ' is not a valid employee.');
END;

示例八:GOTO不能跳转到 exception 中:

SQL> BEGIN
	GOTO LB3; --expected error
	EXCEPTION
	WHEN OTHERS THEN
	<<LB3>>
	NULL;
END;
/
  2    3    4    5    6    7    8  	GOTO LB3; --expected error
	*
ERROR at line 2:
ORA-06550: line 2, column 2:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'LB3'
ORA-06550: line 4, column 14:
PL/SQL: Statement ignored


SQL>

示例九:

SQL> DECLARE
	I INT := 0;
BEGIN
	<<LB1>>
	I := I + 1;
	dbms_output.put_line(I);
	BEGIN
		<<LB2>>
		iF I = 1 THEN
			RAISE INVALID_NUMBER;
		ELSE
			RAISE ZERO_DIVIDE;
		END IF;
		EXCEPTION
		WHEN INVALID_NUMBER THEN
		GOTO LB1; -- 如果跳转到 LB2 则报错,即:goto 语句不能从 EXCEPTION 中跳转到当前的语句块中
		WHEN ZERO_DIVIDE THEN
		dbms_output.put_line('ZERO_DIVIDE');
	END;
END;
/
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19   20   21  1
2
ZERO_DIVIDE

PL/SQL procedure successfully completed.

SQL>
SQL> DECLARE
	I INT := 0;
BEGIN
	<<LB1>>
	I := I + 1;
	dbms_output.put_line(I);
	BEGIN
		<<LB2>>
		iF I = 1 THEN
			RAISE INVALID_NUMBER;
		ELSE
			RAISE ZERO_DIVIDE;
		END IF;
		EXCEPTION
		WHEN INVALID_NUMBER THEN
		GOTO LB2; -- 如果跳转到 LB2 则报错,即:goto 语句不能从 EXCEPTION 中跳转到当前的语句块中
		WHEN ZERO_DIVIDE THEN
		dbms_output.put_line('ZERO_DIVIDE');
	END;
END;
/

  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19   20   21  		GOTO LB2; -- 如果跳转到 LB2 则报错,即:goto 语句不能从 EXCEPTION 中跳转到当前的语句块中
		*
ERROR at line 16:
ORA-06550: line 16, column 3:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'LB2'
ORA-06550: line 16, column 3:
PL/SQL: Statement ignored


SQL>

示例十:goto 到 goto标签

SQL> BEGIN
  GOTO cmd1;
  DBMS_OUTPUT.PUT_LINE('i am cm  2    3  d.');
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd1.'  4  );
  GOTO cmd3;
  <<cmd2>> DBMS_OUTPUT.PUT_LINE('i  5    6   am cmd2.');
  <<cmd3>> GOTO cmd4;
  <<cmd4>> DBMS  7    8  _OUTPUT.PUT_LINE('i am cmd4.');
END;
/  9   10  
i am cmd1.
i am cmd4.

PL/SQL procedure successfully completed.

SQL>

当然这样的话,很容易就造成死循环,如下:

-- 这种情况,只能手动停止

SQL> BEGIN
  GOTO cmd1;
  DBMS_OUTPUT.PUT_LINE('i am cm  2    3  d.');
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd1.'  4  );
  GOTO cmd3;
  <<cmd2>> DBMS_OUTPUT.PUT_LINE('i  5    6   am cmd2.');
  <<cmd3>> GOTO cmd1; -- 又上去了,造成循环
  <<cmd4>> DBMS  7    8  _OUTPUT.PUT_LINE('i am cmd4.');
END;
/  9   10  
^CBEGIN
*
ERROR at line 1:
ORA-01013: user requested cancel of current operation



SQL>

示例十一:goto到相同标签

SQL> BEGIN
  GOTO cmd1;
  <<cmd1>> DBMS_OUTPUT.PUT_LINE  2    3  ('i am cmd2.');
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i  4   am cmd4.');
END;
/  5    6  
  GOTO cmd1;
       *
ERROR at line 2:
ORA-06550: line 2, column 8:
PLS-00371: at most one declaration for 'CMD1' is permitted
ORA-06550: line 2, column 3:
PL/SQL: Statement ignored


SQL> BEGIN
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd2.'  2  );
  GOTO cmd1;
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i  3    4   am cmd4.');
END;
/  5    6  
  GOTO cmd1;
       *
ERROR at line 3:
ORA-06550: line 3, column 8:
PLS-00371: at most one declaration for 'CMD1' is permitted
ORA-06550: line 3, column 3:
PL/SQL: Statement ignored


SQL> BEGIN
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd2.'  2  );
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd4.');
  3    4    GOTO cmd1;
END;
/  5    6  
  GOTO cmd1;
       *
ERROR at line 4:
ORA-06550: line 4, column 8:
PLS-00371: at most one declaration for 'CMD1' is permitted
ORA-06550: line 4, column 3:
PL/SQL: Statement ignored


SQL>
BEGIN
  GOTO cmd1;
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd2.');
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd4.');
END;
/

BEGIN
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd2.');
  GOTO cmd1;
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd4.');
END;
/

BEGIN
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd2.');
  <<cmd1>> DBMS_OUTPUT.PUT_LINE('i am cmd4.');
  GOTO cmd1;
END;
/

NULL 语句的说明

注意:如果启用了警告,使用NULL语句可能会引发不可达(unreachable code)代码警告。

示例一:NULL表示没有动作的语句

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_last_name  VARCHAR2(10);
   v_emp_id     NUMBER(6) := 123;
BEGIN
  SELECT last_name INTO v_last_name FROM employees WHERE employee_id = v_emp_id;
  
  IF v_last_name = 'Weiss3' THEN
    UPDATE employees SET last_name = 'Weiss321';
  ELSE
    NULL;  -- Employee is not a sales rep
  END IF;
END;
/

select * from employees;

在这里插入图片描述


示例二:在子程序创建期间作为占位符的NULL语句

-- NULL语句允许您编译这个子程序,并在以后填充真正的主体

CREATE OR REPLACE PROCEDURE award_bonus (
  emp_id NUMBER,
  bonus NUMBER
) AUTHID DEFINER AS
BEGIN    -- Executable part starts here
  NULL;  -- Placeholder
  -- (raises "unreachable code" if warnings enabled)
END award_bonus;
/

在这里插入图片描述


示例三:简单CASE语句ELSE子句中的NULL语句

CREATE OR REPLACE PROCEDURE print_grade (
  grade CHAR
) AUTHID DEFINER AS
BEGIN
  CASE grade
    WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
    WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
    WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');
    WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair');
    WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');
    ELSE NULL;
  END CASE;
END;
/
BEGIN
  print_grade('A');
  print_grade('S');
END;
/

在这里插入图片描述