控制结构可能是pl/sql中最有用(也是最重要)的部分。使用pl/sql的控制结构,可以以非常灵活和强大的方式操纵LightDB数据。
有一个命令可以让你从函数中返回数据:RETURN。
RETURN
RETURN expression;
使用带有表达式的RETURN终止函数并将表达式的值返回给调用者。这种形式用于不返回集合的pl/sql函数。
如果使用输出参数声明了函数,则只需编写没有表达式的RETURN。输出参数变量的当前值将被返回。当前不支持混合使用Oracle输出参数和返回子句。
函数的返回值不能未定义。如果控制到达函数的顶层块的末尾而没有命中RETURN语句,则会发生运行时错误。在这些情况下,如果顶层块完成,则会自动执行RETURN语句。
一些例子:
-- 返回一个标量类型的函数 RETURN 1 + 2; RETURN scalar_var;
过程没有返回值。因此,过程可以在没有RETURN语句的情况下结束。如果您希望使用RETURN语句提前退出代码,则只需编写没有表达式的RETURN语句。
存储过程参数使用out进行修饰,您可以使用只有RETURN语句但没有返回值来结束存储过程。
如果存储过程具有输出参数,则输出参数变量的最终值将返回给调用方。
pl/sql函数、过程或匿名块可以调用存储过程。例如:
CREATE PROCEDURE triple(x in int, y out int)
AS
BEGIN
y := x * 3;
END;
/
DECLARE
myvar int := 5;
myresult int := 10;
BEGIN
triple(myvar, myresult);
DBMS_OUTPUT.PUT_LINE('myresult = '||myresult); -- prints 15
END;
/
IF和CASE语句可让您根据某些条件执行替代命令。 pl/sql有三种IF形式:
IF ... THEN ... END IF
IF ... THEN ... ELSE ... END IF
IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF
和两种CASE形式:
CASE ... WHEN ... THEN ... ELSE ... END CASE
CASE WHEN ... THEN ... ELSE ... END CASE
IF-THENIFboolean-expressionTHENstatementsEND IF;
IF-THEN语句是IF语句的最简单形式。如果条件为真,则执行THEN和END IF之间的语句。否则,它们将被跳过。
示例:
IF v_user_id <> 0 THEN
UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;
IF-THEN-ELSEIFboolean-expressionTHENstatementsELSEstatementsEND IF;
IF-THEN-ELSE语句通过让您指定一组备选语句,以便在条件不为真时执行,增强了IF-THEN语句。(请注意,这包括条件评估为NULL的情况。)
示例:
IF parentid IS NULL OR parentid = ''
THEN
RETURN fullname;
ELSE
RETURN hp_true_filename(parentid) || '/' || fullname;
END IF;
IF v_count > 0 THEN
INSERT INTO users_count (count) VALUES (v_count);
RETURN 't';
ELSE
RETURN 'f';
END IF;
IF-THEN-ELSIFIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements] [ ELSIFboolean-expressionTHENstatements... ] [ ELSEstatements] END IF;
有时会有超过两个的备选方案。 IF-THEN-ELSIF提供了一种方便的方法,逐个检查几个备选方案。 IF条件逐个进行测试,直到找到第一个为真为止。 然后执行相关的语句,之后控制传递到 END IF之后的下一条语句。 (任何后续的 IF 条件都不会被测试。)如果没有任何 IF 条件为真,则执行 ELSE块(如果有的话)。
下面是一个例子:
IF number = 0 THEN
result := 'zero';
ELSIF number > 0 THEN
result := 'positive';
ELSIF number < 0 THEN
result := 'negative';
ELSE
-- 嗯,唯一的其他可能性是number为null
result := 'NULL';
END IF;
关键字ELSIF也可以拼写为ELSEIF。
完成相同任务的另一种方法是嵌套IF-THEN-ELSE语句,如下面的示例所示:
IF demo_row.sex = 'm' THEN
pretty_sex := 'man';
ELSE
IF demo_row.sex = 'f' THEN
pretty_sex := 'woman';
END IF;
END IF;
然而,这种方法需要为每个IF编写一个匹配的END IF,所以当有许多备选方案时,它比使用ELSIF更加麻烦。
CASECASEsearch-expressionWHENexpression[,expression[ ... ]] THENstatements[ WHENexpression[,expression[ ... ]] THENstatements... ] [ ELSEstatements] END CASE;
简单的CASE形式提供了基于操作数的相等性条件执行。 search-expression被评估(一次)并逐个与WHEN子句中的每个expression进行比较。 如果找到匹配项,则执行相应的statements,然后控制传递到 END CASE之后的下一条语句。(后续的WHEN表达式不会被评估。)如果找不到匹配项,则执行ELSEstatements;但是如果没有ELSE,则会引发CASE_NOT_FOUND异常。
下面是一个简单的例子:
CASE x
WHEN 1, 2 THEN
msg := 'one or two';
ELSE
msg := 'other value than one or two';
END CASE;
CASE
CASE
WHEN boolean-expression THEN
statements
[ WHEN boolean-expression THEN
statements
... ]
[ ELSE
statements ]
END CASE;
CASE 的搜索形式根据布尔表达式的真值提供条件执行。
每个 WHEN 子句的 boolean-expression 会依次被评估,
直到找到一个产生 true 的表达式。然后执行相应的 statements,
然后控制权转移到 END CASE 后的下一条语句。
(后续的 WHEN 表达式不会被评估。)
如果找不到真实结果,则会执行 ELSE statements;
但是如果没有 ELSE,则会引发 CASE_NOT_FOUND 异常。
下面是一个例子:
CASE
WHEN x BETWEEN 0 AND 10 THEN
msg := 'value is between zero and ten';
WHEN x BETWEEN 11 AND 20 THEN
msg := 'value is between eleven and twenty';
END CASE;
这种形式的 CASE 和 IF-THEN-ELSIF 完全等价,
唯一的区别是遗漏了 ELSE 子句会导致错误,而不是什么都不做。
使用 LOOP、EXIT、CONTINUE、WHILE、FOR 和 FOREACH 语句,
您可以安排 pl/sql 函数重复执行一系列命令。
LOOP[ <<label>> ] LOOPstatementsEND LOOP [label];
LOOP 定义了一个无条件循环,直到被 EXIT 或 RETURN 语句终止。
可选的 label 可以被嵌套循环内的 EXIT 和 CONTINUE 语句使用,以指定这些语句所引用的循环。
标签名称不能是保留字 label。
EXITEXIT [label] [ WHENboolean-expression];
如果没有给出 label,则最内层的循环将被终止,然后执行 END LOOP 后的语句。
如果给出了 label,则它必须是当前循环或某个外层嵌套循环或块的标签。
然后,命名的循环或块将被终止,并继续执行循环/块对应的 END 语句之后的语句。
如果指定了 WHEN,则只有在 boolean-expression 为 true 时才会退出循环。否则,控制权传递到 EXIT 后的语句。
EXIT 可以与所有类型的循环一起使用,它不仅限于与无条件循环一起使用。
例如:
LOOP
-- some computations
IF count > 0 THEN
EXIT; -- 退出循环
END IF;
END LOOP;
LOOP
-- some computations
EXIT WHEN count > 0; -- 与前面的例子产生相同的结果
END LOOP;
CONTINUECONTINUE [label] [ WHENboolean-expression];
如果没有给出 标签,则开始内层循环的下一次迭代。也就是说,跳过循环体中剩余的所有语句,控制返回循环控制表达式(如果有的话),以确定是否需要进行另一次循环迭代。
如果有 标签,则指定将继续执行的循环的标签。
如果指定了 WHEN,那么仅当 布尔表达式 为真时才开始循环的下一次迭代。否则,控制传递到 CONTINUE 之后的语句。
CONTINUE 可以与所有类型的循环一起使用,它不仅仅局限于无条件循环的使用。
示例:
LOOP
-- 一些计算
EXIT WHEN count > 100;
CONTINUE WHEN count < 50;
-- 一些计算,用于计数在 [50 .. 100] 范围内的情况
END LOOP;
WHILE[ <<label>> ] WHILEboolean-expressionLOOPstatementsEND LOOP [label];
WHILE 语句会重复执行一系列语句,只要
boolean-expression
评估为 true。该表达式在每次进入循环体之前进行检查。
例如:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- 在这里进行一些计算
END LOOP;
WHILE NOT done LOOP
-- 在这里进行一些计算
END LOOP;
FOR (整数变量)[ <<label>> ] FORnameIN [ REVERSE ]expression..expression[ BYexpression] LOOPstatementsEND LOOP [label];
这种形式的FOR创建一个循环,遍历整数值的范围。
变量name会自动定义为integer类型,
仅在循环内存在(循环内会忽略任何现有的该变量的定义)。
指定范围的两个表达式在进入循环时只会计算一次。
如果没有指定BY子句,则迭代步长为1,
否则为BY子句中指定的值,该值也会在循环进入时计算一次。
如果指定了REVERSE,则在每次迭代后,步长值会被减去而不是加上。
一些整数型FOR循环的例子:
FOR i IN 1..10 LOOP
-- i在循环内将取值1,2,3,4,5,6,7,8,9,10
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- i在循环内将取值10,9,8,7,6,5,4,3,2,1
END LOOP;
FOR i IN REVERSE 10..1 BY 2 LOOP
-- i在循环内将取值10,8,6,4,2
END LOOP;
如果下限大于上限(或者在REVERSE情况下,小于上限),则根本不执行循环体。不会引发任何错误。
如果FOR循环附有一个label,则可以使用该label来限定名称,从而引用整数型循环变量。
为了兼容Oracle数据库,end loop 后面可以有一个label,并且不检查label是否匹配。这个功能只是语法糖。
DECLARE
i NUMBER(38);
BEGIN
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE('Value: ' || i);
END LOOP i;
END;
DECLARE
i NUMBER(38);
BEGIN
FOR i IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE('Value: ' || i);
END LOOP xxxxxxxxxxxx;
END;
GOTO语句无条件地使控制权转到一个标签处。该标签在其作用域内必须是唯一的,并且必须位于一条可执行语句或一个PL/oraSQL块之前。当程序运行时,GOTO语句将控制权转到一个带标签的语句或块处。
语法如下:
GOTO labelName;
GOTO语句无法将控制权转入IF语句、CASE语句、LOOP语句或子块中。
GOTO语句无法将控制权从一个IF语句子句传到另一个子句,或者从一个CASE语句的WHEN子句传到另一个子句。
GOTO语句无法将控制权传出子程序。
GOTO语句无法将控制权传入异常处理程序。
GOTO语句无法将控制权从异常处理程序传回当前块。
LightDB的关键词无法作为标签名。
该示例可以在ltsql下运行。进入ltsql,执行下列命令。
create database test_oracle lightdb_syntax_compatible_type oracle; \c test_oracle select dbms_output.serveroutput(true);
DECLARE
p VARCHAR2(30);
n PLS_INTEGER := 37;
BEGIN
FOR j in 2..ROUND(SQRT(n)) LOOP
IF n % j = 0 THEN
p := ' is not a prime number';
GOTO print_now;
END IF;
END LOOP;
p := ' is a prime number';
<<print_now>>
DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p);
END;
/
结果:
37 is a prime number
DECLARE
done BOOLEAN;
j int;
BEGIN
done := true;
FOR i IN 1..50 LOOP
IF done THEN
j := 100;
GOTO end_loop;
END IF;
END LOOP;
<<end_loop>>
NULL;
DBMS_OUTPUT.PUT_LINE(j);
END;
/
结果:
100
BEGIN
DBMS_OUTPUT.PUT_LINE('out block 1');
goto testlabel;
DBMS_OUTPUT.PUT_LINE('out block 2');
<<testlabel>>
BEGIN
DBMS_OUTPUT.PUT_LINE('in block 1');
goto testlabel2;
DBMS_OUTPUT.PUT_LINE('in block 2');
END;
DBMS_OUTPUT.PUT_LINE('out block 3');
<<testlabel2>>
DBMS_OUTPUT.PUT_LINE('out block 4');
END;
/
结果:
out block 1
in block 1
out block 4
GOTO语句将控制权转入IF语句,导致出现错误。
DECLARE
done BOOLEAN := TRUE;
BEGIN
GOTO update_row;
IF done THEN
<<update_row>>
NULL;
END IF;
END;
/
结果:
ERROR: illegal GOTO statement; this GOTO cannot branch to label "update_row"
CONTEXT: pl/sql function inline_code_block
使用不同类型的FOR循环,可以遍历查询结果并相应地操作该数据。语法如下:
[ <<label>> ] FORtargetINqueryLOOPstatementsEND LOOP [label];
target是一个记录变量、行变量或逗号分隔的标量变量列表。
target依次分配query生成的每一行,并为每一行执行循环体。
如果循环由EXIT语句终止,则最后分配的行值在循环后仍然可访问。
在这种类型的FOR语句中使用的query可以是任何返回行的SQL命令:SELECT是最常见的情况,但您也可以使用带有RETURNING子句的INSERT、UPDATE或DELETE。一些实用程序命令,如EXPLAIN也可以使用。
pl/sql变量会被替换为查询文本,并且查询计划会被缓存以便可能重复使用。
FOR-IN-EXECUTE-IMMEDIATE语句是迭代行的另一种方式:
[ <<label>> ] FORtargetIN EXECUTE IMMEDIATEtext_expression[ USINGexpression[, ... ] ] LOOPstatementsEND LOOP [label];
这类似于前面的形式,但源查询指定为字符串表达式,在进入FOR循环的每个条目上进行评估和重新规划。这允许程序员选择预安排查询的速度或动态查询的灵活性,就像普通的EXECUTE IMMEDIATE语句一样。与EXECUTE IMMEDIATE一样,可以通过USING将参数值插入动态命令中。
另一种指定应遍历其结果的查询的方法是将其声明为游标。这在Section 44.8.3.2中有描述。
默认情况下,在pl/sql函数中发生任何错误都会中止函数的执行以及周围的事务。可以通过使用带有EXCEPTION子句的BEGIN块来捕获错误并从中恢复。语法是BEGIN块的正常语法的扩展形式:
[ DECLAREdeclarations] BEGINstatementsEXCEPTION WHENcondition[ ORcondition... ] THENhandler_statements[ WHENcondition[ ORcondition... ] THENhandler_statements... ] END; /
如果没有错误发生,此块形式只会执行所有statements,然后控制权转移到END后的下一条语句。但是,如果在statements中发生错误,则进一步处理statements被放弃,并且控制权转移到EXCEPTION列表。该列表会查找与发生的错误匹配的第一个condition。如果找到匹配项,则执行相应的handler_statements,然后控制权传递到END后的下一条语句。如果找不到匹配项,则错误会像根本没有EXCEPTION子句一样传播:错误可以由带有EXCEPTION 的封闭块捕获,如果没有,则会中止函数的处理。
特殊的条件名称OTHERS匹配每种错误类型。条件名称不区分大小写。
包含EXCEPTION子句的块进入和退出的成本要比没有该子句的块高得多。因此,在没有必要的情况下不要使用EXCEPTION。