控制结构可能是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-THEN
IFboolean-expression
THENstatements
END 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-ELSE
IFboolean-expression
THENstatements
ELSEstatements
END 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-ELSIF
IFboolean-expression
THENstatements
[ ELSIFboolean-expression
THENstatements
] [ ELSIFboolean-expression
THENstatements
... ] [ 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
更加麻烦。
CASE
CASEsearch-expression
WHENexpression
[,expression
[ ... ]] THENstatements
[ WHENexpression
[,expression
[ ... ]] THENstatements
... ] [ ELSEstatements
] END CASE;
简单的CASE
形式提供了基于操作数的相等性条件执行。 search-expression
被评估(一次)并逐个与WHEN
子句中的每个expression
进行比较。 如果找到匹配项,则执行相应的statements
,然后控制传递到 END CASE
之后的下一条语句。(后续的WHEN
表达式不会被评估。)如果找不到匹配项,则执行ELSE
statements
;但是如果没有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 WHENboolean-expression
THENstatements
[ WHENboolean-expression
THENstatements
... ] [ ELSEstatements
] 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
>> ] LOOPstatements
END LOOP [label
];
LOOP
定义了一个无条件循环,直到被 EXIT
或 RETURN
语句终止。
可选的 label
可以被嵌套循环内的 EXIT
和 CONTINUE
语句使用,以指定这些语句所引用的循环。
标签名称不能是保留字 label
。
EXIT
EXIT [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;
CONTINUE
CONTINUE [label
] [ WHENboolean-expression
];
如果没有给出 标签
,则开始内层循环的下一次迭代。也就是说,跳过循环体中剩余的所有语句,控制返回循环控制表达式(如果有的话),以确定是否需要进行另一次循环迭代。
如果有 标签
,则指定将继续执行的循环的标签。
如果指定了 WHEN
,那么仅当 布尔表达式
为真时才开始循环的下一次迭代。否则,控制传递到 CONTINUE
之后的语句。
CONTINUE
可以与所有类型的循环一起使用,它不仅仅局限于无条件循环的使用。
示例:
LOOP -- 一些计算 EXIT WHEN count > 100; CONTINUE WHEN count < 50; -- 一些计算,用于计数在 [50 .. 100] 范围内的情况 END LOOP;
WHILE
[ <<label
>> ] WHILEboolean-expression
LOOPstatements
END 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
>> ] FORname
IN [ REVERSE ]expression
..expression
[ BYexpression
] LOOPstatements
END 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
>> ] FORtarget
INquery
LOOPstatements
END LOOP [label
];
target
是一个记录变量、行变量或逗号分隔的标量变量列表。
target
依次分配query
生成的每一行,并为每一行执行循环体。
如果循环由EXIT
语句终止,则最后分配的行值在循环后仍然可访问。
在这种类型的FOR
语句中使用的query
可以是任何返回行的SQL命令:SELECT
是最常见的情况,但您也可以使用带有RETURNING
子句的INSERT
、UPDATE
或DELETE
。一些实用程序命令,如EXPLAIN
也可以使用。
pl/sql变量会被替换为查询文本,并且查询计划会被缓存以便可能重复使用。
FOR-IN-EXECUTE-IMMEDIATE
语句是迭代行的另一种方式:
[ <<label
>> ] FORtarget
IN EXECUTE IMMEDIATEtext_expression
[ USINGexpression
[, ... ] ] LOOPstatements
END LOOP [label
];
这类似于前面的形式,但源查询指定为字符串表达式,在进入FOR
循环的每个条目上进行评估和重新规划。这允许程序员选择预安排查询的速度或动态查询的灵活性,就像普通的EXECUTE IMMEDIATE
语句一样。与EXECUTE IMMEDIATE
一样,可以通过USING
将参数值插入动态命令中。
另一种指定应遍历其结果的查询的方法是将其声明为游标。这在Section 44.8.3.2中有描述。
默认情况下,在pl/sql函数中发生任何错误都会中止函数的执行以及周围的事务。可以通过使用带有EXCEPTION
子句的BEGIN
块来捕获错误并从中恢复。语法是BEGIN
块的正常语法的扩展形式:
[ DECLAREdeclarations
] BEGINstatements
EXCEPTION WHENcondition
[ ORcondition
... ] THENhandler_statements
[ WHENcondition
[ ORcondition
... ] THENhandler_statements
... ] END; /
如果没有错误发生,此块形式只会执行所有statements
,然后控制权转移到END
后的下一条语句。但是,如果在statements
中发生错误,则进一步处理statements
被放弃,并且控制权转移到EXCEPTION
列表。该列表会查找与发生的错误匹配的第一个condition
。如果找到匹配项,则执行相应的handler_statements
,然后控制权传递到END
后的下一条语句。如果找不到匹配项,则错误会像根本没有EXCEPTION
子句一样传播:错误可以由带有EXCEPTION
的封闭块捕获,如果没有,则会中止函数的处理。
特殊的条件名称OTHERS
匹配每种错误类型。条件名称不区分大小写。
包含EXCEPTION
子句的块进入和退出的成本要比没有该子句的块高得多。因此,在没有必要的情况下不要使用EXCEPTION
。