DMSQL 程序中的各种控制结构

DMSQL程序提供了丰富的控制结构,包括分支结构、循环控制结构、顺序结构等。通过控制结构,可以编写更复杂的DMSQL程序。

4.1 语句块

语句块是DMSQL程序的基本单元。每个语句块由关键字DECLARE、BEGIN、EXCEPTION和END划分为声明部分、执行部分和异常处理部分。其中执行部分是必须的,说明和异常处理部分可以省略。您可能已经发现,事实上存储模块的模块体就是一个语句块。语句块可以嵌套,它可以出现在任何其他语句可以出现的位置。

语句块的语法如下:

[DECLARE <变量说明>{,<变量说明>};]

BEGIN

<执行部分>

[<异常处理部分>]

END
  • 声明部分

声明部分包含了变量和常量的数据类型和初始值。这个部分由关键字DECLARE开始。如果不需要声明变量或常量,那么可以忽略这一部分。游标的声明也放在这一部分。

  • 执行部分

执行部分是语句块中的指令部分,由关键字BEGIN开始,以关键字EXCEPTION结束,如果EXCEPTION不存在,那么将以关键字END结束。所有的可执行语句都放在这一部分,其他的语句块也可以放在这一部分。分号分隔每一条语句,使用赋值操作符:=或SELECT INTO或FETCH INTO给变量赋值,执行部分的错误将在异常处理部分解决,在执行部分中可以使用另一个语句块,这种程序块被称为嵌套块。

所有的SQL数据操作语句都可以用于执行部分;执行部分使用的变量和常量必须首先在声明部分声明;执行部分必须至少包括一条可执行语句,NULL是一条合法的可执行语句;事务控制语句COMMIT和ROLLBACK可以在执行部分使用。数据定义语言(Data Definition Language)不能在执行部分中使用,DDL语句与EXECUTE IMMEDIATE一起使用。

  • 异常处理部分

异常处理部分是可选的,在这一部分中处理异常或错误,对异常处理的详细讨论我们在后面进行。

需要强调的一点是,一个语句块意味着一个作用域范围。也就是说,在一个语句块的声明部分定义的任何对象,其作用域就是该语句块。请看下面的例子,该例中有一个全局变量X,同时子语句块中又定义了一个局部变量X。

CREATE OR REPLACE PROCEDURE PROC_BLOCK AS

	X INT := 0;

	COUNTER INT := 0;

BEGIN

	FOR I IN 1 .. 4 LOOP

		X := X + 1000;

		COUNTER := COUNTER + 1;

		PRINT CAST(X AS CHAR(10)) || CAST(COUNTER AS CHAR(10)) || 'OUTER LOOP';

/* 这里是一个嵌套的子语句块的开始 */

DECLARE

	X INT := 0; -- 局部变量X,与全局变量X同名

BEGIN

	FOR I IN 1 .. 4 LOOP

		X := X + 1;

		COUNTER := COUNTER + 1;

		PRINT CAST(X AS CHAR(10)) || CAST(COUNTER AS CHAR(10)) || 'INNER LOOP';

END LOOP;

END;

/* 子语句块结束 */

	END LOOP;

END;

执行这个存储过程:

CALL PROC_BLOCK;

执行结果为:

1000 1 OUTER LOOP

1 2 INNER LOOP

2 3 INNER LOOP

3 4 INNER LOOP

4 5 INNER LOOP

2000 6 OUTER LOOP

1 7 INNER LOOP

2 8 INNER LOOP

3 9 INNER LOOP

4 10 INNER LOOP

3000 11 OUTER LOOP

1 12 INNER LOOP

2 13 INNER LOOP

3 14 INNER LOOP

4 15 INNER LOOP

4000 16 OUTER LOOP

1 17 INNER LOOP

2 18 INNER LOOP

3 19 INNER LOOP

4 20 INNER LOOP

由执行结果可以看出,两个X的作用域是完全不同的。

4.2 分支结构

分支结构先执行一个判断条件,根据判断条件的执行结果执行对应的一系列语句。

4.2.1 IF语句

IF语句控制执行基于布尔条件的语句序列,以实现条件分支控制结构。

语法如下:

IF <条件表达式>

THEN <执行部分>;

[<ELSEIF_OR_ELSIF><条件表达式> THEN <执行部分>;{<ELSEIF_OR_ELSIF><条件表达式> THEN <执行部分>;}]

[ELSE <执行部分>;]

END IF;

<ELSEIF_OR_ELSIF> ::= ELSEIF | ELSIF

考虑到不同用户的编程习惯,ELSEIF子句的起始关键字既可写作ELSEIF,也可写作ELSIF。

条件表达式中的因子可以是布尔类型的参数、变量,也可以是条件谓词。存储模块的控制语句中支持的条件谓词有:比较谓词、BETWEEN、IN、LIKE和IS
NULL。

最简单的IF语句形如:

IF 条件 THEN

代码

END IF;

如果条件成立,则执行代码,否则什么也不执行。

如果需要在条件不成立时执行另外的代码,则应使用IF-ELSE,形如:

IF 条件 THEN

代码1

ELSE

代码2

END IF;

此时,若条件成立则执行代码1,条件不成立则执行代码2。

例如,下面例子的子过程proc_if中使用IF语句判断销售指标完成情况,从而计算bonus值。

DECLARE

	PROCEDURE proc_if (

		sales dec,

		quota dec,

		emp_id dec

	)

	IS

		bonus dec:= 0;

	BEGIN

		IF sales > (quota + 400) THEN

			bonus := (sales - quota)/4;

		ELSE

			bonus := 100;

		END IF;

		DBMS_OUTPUT.PUT_LINE('bonus = ' || bonus);

		UPDATE RESOURCES.EMPLOYEE SET SALARY = SALARY + bonus WHERE EMPLOYEEID = emp_id;

	END proc_if ;

BEGIN

	proc_if (30100, 20000, 1);

	proc_if (15000, 10000, 2);

END;

/

执行结果为:

bonus = 2525

bonus = 1250

还可以通过ELSEIF或ELSIF进行IF语句的嵌套,形如:

IF 条件1 THEN

代码1

ELSEIF 条件2 THEN

代码2

…

ELSE

代码N

END IF

在执行上面的IF语句时,首先判断条件1,当条件1成立时执行代码1,否则继续判断条件2,条件成立则执行代码2,否则继续判断下面的条件。如果前面的条件都不成立,则执行ELSE后面的代码N。

例如,在前面的例子中再增加一种计算bonus的情况:

DECLARE

	PROCEDURE proc_if (

		sales dec,

		quota dec,

		emp_id dec

	)

	IS

		bonus dec:= 0;

BEGIN

	IF sales > (quota + 400) THEN

		bonus := (sales - quota)/4;

	ELSE IF sales > quota THEN

		bonus := 100;

	ELSE

		bonus :=0;

		END IF;

	END IF;

	DBMS_OUTPUT.PUT_LINE('bonus = ' || bonus);

	UPDATE RESOURCES.EMPLOYEE SET SALARY = SALARY + bonus WHERE EMPLOYEEID = emp_id;

	END proc_if ;

BEGIN

	proc_if (30100, 20000, 1);

	proc_if (15000, 10000, 2);

	proc_if (9000, 10000, 3);

END;

/

执行结果为:

bonus = 2525

bonus = 1250

bonus = 0

4.2.2 CASE语句

CASE语句从系列条件中进行选择,并且执行相应的语句块,主要有下面两种形式:

  • 简单形式:将一个表达式与多个值进行比较

语法如下:

CASE <条件表达式>

WHEN <条件> THEN <执行部分>;

{WHEN <条件> THEN <执行部分>;}

[ ELSE <执行部分> ]

END [CASE];

语句中的每个条件可以是立即值,也可以是一个表达式。这种形式的CASE语句会选择第一个满足条件的对应的执行部分来执行,剩下的则不会计算,如果没有符合的条件,它会执行ELSE子句中的执行部分,但是如果ELSE子句不存在,则不会执行任何语句。

例如:

CREATE OR REPLACE PROCEDURE PROC_CASE(GRADE CHAR(10)) AS

DECLARE

	appraisal VARCHAR2(20);

BEGIN

	appraisal :=

	CASE GRADE

		WHEN NULL THEN 'IS NULL'

		WHEN 'A' THEN 'Excellent'

		WHEN 'B' THEN 'Good'

		WHEN 'C' THEN 'Fair'

		ELSE 'No such grade'

	END;

	DBMS_OUTPUT.PUT_LINE ('Grade ' ||grade|| ' is ' ||appraisal);

END;

/
  • 搜索形式:对多个条件进行计算,取第一个结果为真的条件

语法如下:

CASE

WHEN <条件表达式> THEN <执行部分>;

{ WHEN <条件表达式> THEN <执行部分>;}

[ ELSE <执行部分> ]

END [CASE];

搜索模式的CASE语句依次执行各条件表达式,当遇见第一个为真的条件时,执行其对应的执行部分,在第一个为真的条件后面的所有条件都不会再执行。如果所有的条件都不为真,则执行ELSE子句中的执行部分,如果ELSE子句不存在,则不执行任何语句。

例如:

CREATE OR REPLACE PROCEDURE PROC_CASE(GRADE CHAR(10)) AS

DECLARE

	appraisal VARCHAR2(20);

BEGIN

	appraisal :=

	CASE

		WHEN grade IS NULL THEN 'IS NULL'

		WHEN grade = 'A' THEN 'Excellent'

		WHEN grade = 'B' THEN 'Good'

		WHEN grade = 'C' THEN 'Fair'

		ELSE 'No such grade'

	END;

	DBMS_OUTPUT.PUT_LINE ('Grade ' ||grade|| ' is ' ||appraisal);

END;

/

4.2.3 SWITCH语句

DMSQL程序支持C语法风格的SWITCH分支结构语句,关于C语法DMSQL程序介绍请看7.1节。

SWITCH语句的功能与简单形式的CASE语句类似,用于将一个表达式与多个值进行比较,并执行相应的语句块。

语法如下:

SWITCH (<条件表达式>)

{

CASE <常量表达式> : <执行部分>; BREAK;

{ CASE <常量表达式> : <执行部分>; BREAK;}

[DEFAULT : <执行部分>; ]

}

每个CASE分支的执行部分后应有“BREAK”语句,否则SWITCH语句在执行了对应分支的执行部分后会继续执行后续分支的执行部分。

如果没有符合的CASE分支,会执行DEFAULT子句中的执行部分,如果DEFAULT子句不存在,则不会执行任何语句。

例如:

{

	VARCHAR appraisal='B';

	SWITCH (appraisal)

	{

		CASE NULL: PRINT 'IS NULL'; BREAK;

		CASE 'A': PRINT 'Excellent'; BREAK;

		CASE 'B': PRINT 'Good'; BREAK;

		CASE 'C': PRINT 'Fair'; BREAK;

		DEFAULT: PRINT 'No such grade';

	}

}

/

4.3 循环控制结构

DMSQL程序支持五种类型的循环语句:LOOP语句、WHILE语句、FOR语句、REPEAT语句和FORALL语句。其中前四种为基本类型的循环语句:LOOP语句循环重复执行一系列语句,直到EXIT语句终止循环为止;WHILE语句循环检测一个条件表达式,当表达式的值为TRUE时就执行循环体的语句序列;FOR语句对一系列的语句重复执行指定次数的循环;REPEAT语句重复执行一系列语句直至达到条件表达式的限制要求。FORALL语句对一条DML语句执行多次,当DML语句中使用数组或嵌套表时可进行优化处理,能大幅提升性能。

4.3.1 LOOP语句

LOOP语句的语法如下:

LOOP

<执行部分>;

END LOOP [标号名];

LOOP语句实现对一语句系列的重复执行,是循环语句的最简单形式。LOOP和END LOOP之间的执行部分将无限次地执行,必须借助EXIT、GOTO或RAISE语句来跳出循环。

例如,下面的例子在LOOP循环中打印参数A的值,并将A的值减1,直到A小于等于0时跳出循环。

CREATE OR REPLACE PROCEDURE PROC_LOOP(a IN OUT INT) AS

BEGIN

	LOOP

		IF a<=0 THEN

			EXIT;

		END IF;

		PRINT a;

		a:=a-1;

	END LOOP;

END;

/

CALL PROC_LOOP(5);

4.3.2 WHILE语句

WHILE语句的语法如下:

WHILE <条件表达式> LOOP

<执行部分>;

END LOOP [标号名];

WHILE循环语句在每次循环开始之前,先计算条件表达式,若该条件为TRUE,执行部分被执行一次,然后控制重新回到循环顶部。若条件表达式的值为FALSE,则结束循环。当然,也可以通过EXIT语句来终止循环。

例如,使用WHILE语句实现上一小节中LOOP循环相同的功能。

CREATE OR REPLACE PROCEDURE PROC_WHILE(a IN OUT INT) AS

BEGIN

	WHILE a>0 LOOP

		PRINT a;

		a:=a-1;

	END LOOP;

END;

/

CALL PROC_WHILE(5);

4.3.3 FOR语句

FOR语句的语法如下:

FOR <循环计数器> IN [REVERSE] <下限表达式> .. <上限表达式> LOOP

<执行部分>;

END LOOP [标号名];

循环计数器是一个标识符,它类似于一个变量,但是不能被赋值,且作用域限于FOR语句内部。下限表达式和上限表达式用来确定循环的范围,它们的类型必须和整型兼容。循环次数是在循环开始之前确定的,即使在循环过程中下限表达式或上限表达式的值发生了改变,也不会引起循环次数的变化。

执行FOR语句时,首先检查下限表达式的值是否小于上限表达式的值,如果下限数值大于上限数值,则不执行循环体。否则,将下限数值赋给循环计数器(语句中使用了REVERSE关键字时,则把上限数值赋给循环计数器);然后执行循环体内的语句序列;执行完后,循环计数器值加1(如果有REVERSE关键字,则减1);检查循环计数器的值,若仍在循环范围内,则重新继续执行循环体;如此循环,直到循环计数器的值超出循环范围。同样,也可以通过EXIT语句来终止循环。

例如,使用FOR语句实现前面LOOP语句和WHILE语句例子相同的功能。

CREATE OR REPLACE PROCEDURE PROC_FOR1 (a IN OUT INT) AS

BEGIN

	FOR I IN REVERSE 1 .. a LOOP

		PRINT I;

		a:=I-1;

	END LOOP;

END;

/

CALL PROC_FOR1(5);

FOR语句中的循环计数器可与当前语句块内的参数或变量同名,这时该同名的参数或变量在FOR语句的范围内将被屏蔽。如下例所示:

DECLARE

	v1 DATE:=DATE '2017-03-17';

BEGIN

	FOR v1 IN 3.. 5 LOOP

		PRINT v1;

	END LOOP;

	PRINT v1;

END;

/

打印结果为:

3

4

5

2017-03-17

4.3.4 REPEAT语句

REPEAT语句的语法如下:

REPEAT

<执行部分>;

UNTIL <条件表达式>;

REPEAT语句先执行执行部分,然后判断条件表达式,若为TRUE则控制重新回到循环顶部,若为FALSE则退出循环。可以看出,REPEAT语句的执行部分至少会执行一次。

例如:

DECLARE

	a INT;

BEGIN

	a := 0;

	REPEAT

		a := a+1;

		PRINT a;

	UNTIL a>10;

END;

/

4.3.5 FORALL语句

FORALL语句的语法如下:

FORALL<循环计数器> IN <bounds_clause> [SAVE EXCEPTIONS] <forall_dml_stmt>;

<bounds_clause> ::= <下限表达式>..<上限表达式>

| INDICES OF <集合> [BETWEEN ] <下限表达式> AND <上限表达式>

| VALUES OF <集合>

<forall_dml_stmt> ::= <INSERT语句> | <UPDATE语句> | <DELETE语句> |
<MERGE INTO语句>

SAVE EXCEPTIONS:指定即使一些DML语句失败,直到FORALL执行结束才抛出异常。

INDICES OF \<集合\>:跳过集合中没有赋值的元素,可用于指向稀疏数组的实际下标。

VALUES OF \<集合\>:把该集合中的值作为下标。

下面的例子说明了如何使用FORALL语句:

CREATE TABLE t1_forall(C1 INT, C2 VARCHAR(50));

CREATE OR REPLACE PROCEDURE p1_forall AS

BEGIN

	FORALL I IN 1..10

		INSERT INTO t1_forall SELECT TOP 1 EMPLOYEEID, TITLE FROM RESOURCES.EMPLOYEE;

END;

/

DM可对FORALL中的INSERT、UPDATE和DELETE语句中的数组或嵌套表引用进行优化处理。需要注意的是,优化处理会影响游标的属性值,导致其不可使用。dm.ini中的USE_FORALL_ATTR参数控制是否进行优化处理:值为0表示可以优化,不使用游标属性;值为1表示不优化,使用游标属性,默认值为0。

4.3.6 EXIT语句

EXIT语句与循环语句一起使用,用于终止其所在循环语句的执行,将控制转移到该循环语句外的下一个语句继续执行。

EXIT语句的语法如下:

EXIT [<标号名>] [WHEN <条件表达式>];

EXIT语句必须出现在一个循环语句中,否则将报错。

当EXIT后面的标号名省略时,该语句将终止直接包含它的那条循环语句;当EXIT后面带有标号名时,该语句用于终止标号名所标识的那条循环语句。需要注意的是,该标号名所标识的语句必须是循环语句,并且EXIT语句必须出现在此循环语句中。当EXIT语句位于多重循环中时,可以用该功能来终止其中的任何一重循环。

下面的例子中EXIT后没有带标号,其所在的那层循环被终止。

DECLARE

	a INT;

	b INT;

BEGIN

	a := 0;

	LOOP

		FOR b in 1 .. 2 LOOP

			PRINT '内层循环' ||b;

			EXIT WHEN a > 3;

		END LOOP;

		a := a + 2;

		PRINT '---外层循环' ||a;

		EXIT WHEN a> 5;

	END LOOP;

END;

/

执行结果为:

内层循环1

内层循环2

---外层循环2

内层循环1

内层循环2

---外层循环4

内层循环1

---外层循环6

下面的例子中EXIT后带有标号,则其终止了指定的那层循环。

DECLARE

	a INT;

	b INT;

BEGIN

	a := 0;

	<<flag1>>

	LOOP

		FOR b in 1 .. 2 LOOP

			PRINT '内层循环' ||b;

		EXIT flag1 WHEN a > 3;

		END LOOP;

		a := a + 2;

		PRINT '---外层循环' ||a;

		EXIT WHEN a> 5;

	END LOOP;

END;

/

执行结果为:

内层循环1

内层循环2

---外层循环2

内层循环1

内层循环2

---外层循环4

内层循环1

当WHEN子句省略时,EXIT语句无条件地终止该循环语句;否则,先计算WHEN子句中的条件表达式,当条件表达式的值为真时,终止该循环语句。如下例所示:

DECLARE

	a INT;

	b INT;

BEGIN

	a := 0;

	LOOP

		FOR b in 1 .. 2 LOOP

			PRINT '内层循环' ||b;

			EXIT;

		END LOOP;

		a := a + 2;

		PRINT '---外层循环' ||a;

		EXIT;

	END LOOP;

END;

/

执行结果为:

内层循环1

---外层循环2

4.3.7 CONTINUE语句

CONTINUE语句的作用是退出当前循环,并且将语句控制转移到这次循环的下一次循环迭代或者是一个指定标签的循环的开始位置并继续执行。

CONTINUE语句的语法为:

CONTINUE [[标号名] WHEN <条件表达式>];

若CONTINUE后没有跟WHEN子句,则无条件立即退出当前循环,并且将语句控制转移到这次循环的下一次循环迭代或者是一个指定标号名的循环的开始位置并继续执行。如下例所示:

DECLARE

	x INT:= 0;

BEGIN

	<<flag1>> -- CONTINUE跳出之后,回到这里

	FOR I IN 1..4 LOOP

		DBMS_OUTPUT.PUT_LINE ('循环内部,CONTINUE之前: x = ' || TO_CHAR(x));

		x := x + 1;

		CONTINUE flag1;

		DBMS_OUTPUT.PUT_LINE ('循环内部,CONTINUE之后: x = ' || TO_CHAR(x));

	END LOOP;

	DBMS_OUTPUT.PUT_LINE (' 循环外部: x = ' || TO_CHAR(x));

END;

/

执行结果为:

循环内部,CONTINUE之前: x = 0

循环内部,CONTINUE之前: x = 1

循环内部,CONTINUE之前: x = 2

循环内部,CONTINUE之前: x = 3

循环外部: x = 4

若CONTINUE语句中包含WHEN子句,则当WHEN子句的条件表达式为TURE时才退出当前循环,将语句控制转移到下一次循环迭代或者是一个指定标号名的循环的开始位置并继续执行。当每次循环到达CONTINUE-WHEN时,都会对WHEN的条件进行计算,如果条件为FALSE,则CONTINUE-WHEN语句不执行任何动作,为了防止出现死循环,应将WHEN条件设置为一个肯定可以为TRUE的表达式。

下面的例子说明了CONTINUE-WHEN语句的使用。

DECLARE

	x INT:= 0;

BEGIN

	-- CONTINUE跳出之后,回到这里

	FOR I IN 1..4 LOOP

		DBMS_OUTPUT.PUT_LINE ('循环内部,CONTINUE之前: x = ' || TO_CHAR(x));

		x := x + 1;

		CONTINUE WHEN x > 3;

		DBMS_OUTPUT.PUT_LINE ('循环内部,CONTINUE之后: x = ' || TO_CHAR(x));

	END LOOP;

	DBMS_OUTPUT.PUT_LINE (' 循环外部: x = ' || TO_CHAR(x));

END;

/

执行结果为:

循环内部,CONTINUE之前: x = 0

循环内部,CONTINUE之后: x = 1

循环内部,CONTINUE之前: x = 1

循环内部,CONTINUE之后: x = 2

循环内部,CONTINUE之前: x = 2

循环内部,CONTINUE之后: x = 3

循环内部,CONTINUE之前: x = 3

循环外部: x = 4

4.4 顺序结构

4.4.1 GOTO语句

GOTO语句无条件地跳转到一个标号所在的位置,其语法为:

GOTO <标号名>;

GOTO语句将控制权交给带有标号的语句或语句块。标号的定义在一个语句块中必须是唯一的,且必须指向一条可执行语句或语句块。

为了保证GOTO语句的使用不会引起程序的混乱,我们对GOTO语句的使用有下列限制:

  • GOTO语句不能跳入一个IF语句、CASE语句、循环语句或下层语句块中;
  • GOTO语句不能从一个异常处理器跳回当前块,但是可以跳转到包含当前块的上层语句块。

下面的例子说明了如何正确使用GOTO语句。

DECLARE

	v_name VARCHAR2(25);

	v_empid INT := 1;

BEGIN

	<<get_name>>

	SELECT NAME INTO v_name FROM RESOURCES.EMPLOYEE A,PERSON.PERSON B WHERE
A.PERSONID=B.PERSONID AND A.EMPLOYEEID=v_empid;

	BEGIN

		DBMS_OUTPUT.PUT_LINE(v_name);

		v_empid := v_empid + 1;

		IF v_empid <=5 THEN

			GOTO get_name;

		END IF;

	END;

END;

下面两个例子则是非法使用GOTO语句的示例。

例1:

BEGIN

...

	GOTO update_row; /* 错误,企图跳入一个IF语句 */

...

	IF valid THEN

...

		<<update_row>>

		UPDATE emp SET ...

	END IF;

END;

/

例2:

BEGIN

...

	IF status = 'OBSOLETE' THEN

		GOTO delete_part; /* 错误,企图跳入一个下层语句块 */

	END IF;

...

	BEGIN

...

		<<delete_part>>

		DELETE FROM parts WHERE ...

	END;

END;

/

4.4.2 NULL语句

NULL语句的语法为:

NULL;

NULL语句不做任何事情,只是用于保证语法的正确性,或增加程序的可读性。

例如,下面例子中的NULL语句说明程序已经考虑了其他的异常,只是并不做处理。

CREATE OR REPLACE FUNCTION func_null (

	a INT,

	b INT

) RETURN INT

AS

BEGIN

	RETURN (a/b);

EXCEPTION

	WHEN ZERO_DIVIDE THEN

		DBMS_OUTPUT.PUT_LINE('除0错误');

	WHEN OTHERS THEN

		NULL;

END;

/

4.5 其他语句

4.5.1 赋值语句

赋值语句的语法为:

<赋值对象> := <值表达式>;

或

SET <赋值对象> = <值表达式>;

使用赋值语句可以给各种数据类型的对象赋值。被赋值的对象可以是变量,也可以是OUT参数或IN OUT参数。表达式的数据类型必须与赋值对象的数据类型兼容。

注意

如果赋值对象和值表达式是类类型,赋值采用的是对象指向逻辑,赋值对象指向值表达式的对象,而不会创建新的对象。

4.5.2 调用语句

存储模块可以被别的存储模块或应用程序调用。同样,在存储模块中也可以调用其他存储模块。调用存储模块时,应给存储模块提供输入参数值,并获取存储模块的输出参数值。

调用语句的语法为:

[CALL] [<模式名>.]<存储模块名>[@dblink_name] [(<参数>{, <参数>})];

<参数> ::= <参数值>|<参数名=参数值>

使用说明:

  • 如果被调用的存储模块不属于当前模式,必须在语句中指明存储模块的模式名;
  • 参数的个数和类型必须与被调用的存储模块一致;
  • 存储模块的输入参数可以是嵌入式变量,也可以是值表达式;存储模块的输出参数必须是可赋值对象,如嵌入式变量;
  • “dblink_name”表示创建的dblink名字,如果添加了该选项,则表示调用远程实例的存储模块;
  • 在调用过程中,服务器将以存储模块创建者的模式和权限来执行过程中的语句。

一般情况下,用户调用存储模块时通过实际参数位置关系和形式参数相对应,这种方式被称为“位置标识法”。系统还支持另一种存储模块调用方式:每个参数中包含形式参数和实际参数,这样的方法被称为“名字标识法”。对结果而言,这两种调用方式是等价的,但在具体使用时存在一些差异,如表4.1所示。

表4.1 位置标识法与名字标识法的使用比较
位置标识法 名字标识法
依赖于实际参数的名称 清楚地说明了实际参数和形式参数间的对应关系
给出的实际参数必须依照形式参数的次序 指定实际参数时可不按照形式参数的顺序
调用比较简洁,符合大多数第三代语言的使用习惯 需要更多的编码,调用时要需要指定形式参数和实际参数
使用参数缺省值时必须在参数列表的末尾 无论哪个参数拥有缺省值都不会对调用产生影响
维护的代价在于参数的定义次序发生了变化 维护的代价在于参数名的定义发生了变化

下面的例子分别使用位置标识法和名字标识法调用存储模块。

CREATE OR REPLACE PROCEDURE proc_call (a INT, b IN OUT INT) AS

	v1 INT:=a;

BEGIN

	b:=0;

	FOR C IN 1 .. v1 LOOP

		b:=b+c;

	END LOOP;

	print b;

END;

/

CALL proc_call(10,0); --以位置标识法调用

CALL proc_call(b=0,a=10); --以名字标识法调用

下面的例子演示了如何通过DBLINK调用远程实例的存储模块。

一,假设远程数据库(实例名dmserver2,端口号5237)已创建DBLINK,名为TEST_LINK。该远程数据库上存在存储过程dm_get_next_val。

CREATE OR REPLACE PROCEDUREdm_get_next_val(a IN OUT INT) AS

BEGIN

	a := a + 1;

END;

/

二,在本地(实例名dmserver,端口号5236)通过DBLINK名字TEST_LINK,调用上述远程过程:

DECLARE

	x INT;

BEGIN

	x := 1;

	dm_get_next_val@TEST_LINK(x);

	PRINT x;

END;

/

4.5.3 RETURN语句

RETURN语句的语法为:

RETURN [<返回值>];

RETURN语句结束DMSQL程序的执行,将控制返回给DMSQL程序的调用者。如果是从存储函数返回,则同时将函数的返回值提供给调用环境。

除管道表函数外,函数的执行必须以RETURN语句结束。确切地说,函数中应至少有一个被执行的返回语句。由于程序中有分支结构,在编译时很难保证其每条路径都有返回语句,因此DM在函数执行时才对其进行检查,如果某个分支缺少RETURN,DM会自动为其隐式增加一条RETURN NULL语句。

例如:

CREATE OR REPLACE FUNCTION func_return(a INT) RETURN VARCHAR(10) AS

BEGIN

	IF (a<0) THEN

		RETURN ' a<0';

	ELSE

		PRINT '没有RUTURN';

	END IF;

END;

在这个例子中,当参数a大于或等于0时,函数func_return没有以RETURN语句结束。因此,执行下面的语句:

SELECT func_return(2);

执行结果为:

行号 			FUNC_RETURN(2)

---------- --------------

1 				NULL

4.5.4 PRINT语句

PRINT语句的语法为:

PRINT <表达式>;

PRINT语句用于从DMSQL程序中向客户端输出一个字符串,语句中的表达式可以是各种数据类型,系统自动将其转换为字符类型。

PRINT语句便于用户调试DMSQL程序代码。当DMSQL程序的行为与预期不一致时,可以在其中加入PRINT语句来观察各个阶段的运行情况。

之前章节的示例中有很多已经使用了PRINT语句,这里不再另外举例。用户也可以使用DM系统包方法DBMS_OUTPUT.PUT_LINE()将信息打印到客户端。

4.5.5 PIPE ROW语句

PIPE ROW语句只能在管道表函数中使用,其语法为:

PIPE ROW ( <值表达式> );

管道表函数是可以返回行集合的函数,用户可以像查询数据库表一样查询它。目前DM管道表函数的返回值类型暂时只支持VARRAY类型和嵌套表类型。

PIPE ROW语句将返回一行到管道表函数的结果行集中。如果值表达式是类类型的表达式,会复制一个对象输入到管道函数的结果集中,保证将同一个对象多次输入到管道函数的结果集中时,后文的修改不会影响前面的输入。

下面是一个关于管道表函数的例子:

CREATE TYPE mytype AS OBJECT (

	COL1 INT,

	COL2 VARCHAR (64)

);

/

CREATE TYPE mytypelist AS TABLE OF mytype;

/

CREATE OR REPLACE FUNCTION func_piperow RETURN mytypelist PIPELINED

IS

	v_mytype mytype;

BEGIN

	FOR I IN 1 .. 5 LOOP

		v_mytype := mytype (I, 'Row ' || I);

		PIPE ROW (v_mytype);

	END LOOP;

EXCEPTION

	WHEN OTHERS THEN NULL;

END;

/

查询管道表函数:

SELECT * FROM TABLE (FUNC_piperow);

查询结果为:

行号 			COL1 	COL2

---------- ----------- -----

1 			1 		Row 1

2 			2 		Row 2

3 			3 		Row 3

4 			4		Row 4

5 			5 		Row 5

如果管道表函数中没有进行PIPE ROW操作,则管道表函数的返回结果为一个没有元素数据的集合,而不是NULL,即返回的是空集而不是空。

微信扫码
分享文档
扫一扫
联系客服