控制流
控制流构造使您可以控制语句块应运行的次数和条件。
条件
IF/ELSIF/ELSE
IF/ELSIF/ELSE 语句允许您根据指定的条件执行不同的操作。
IF condition_1 THEN
statement_1;
[ ELSIF condition_2 THEN
statement_2 ]
...
[ ELSE
statement_n; ]
END IF;
Vertica 将每个条件作为布尔值依次求值,直到找到一个为 true 的条件,然后执行语句块并退出 IF 语句。如果任何条件均不为 true,则执行 ELSE 块(如果存在)。
IF i = 3 THEN...
ELSIF 0 THEN...
ELSIF true THEN...
ELSIF x <= 4 OR x >= 10 THEN...
ELSIF y = 'this' AND z = 'THAT' THEN...
例如,此过程演示简单的 IF...ELSE 分支。由于 b
声明为 true,Vertica 执行第一个分支。
=> DO LANGUAGE PLvSQL $$
DECLARE
b bool := true;
BEGIN
IF b THEN
RAISE NOTICE 'true branch';
ELSE
RAISE NOTICE 'false branch';
END IF;
END;
$$;
NOTICE 2005: true branch
CASE
CASE 表达式通常比 IF…ELSE 链更可读。在执行 CASE 表达式的分支之后,控制将跳转到封闭 END CASE 后的语句。
PL/vSQL CASE 表达式比 SQL Case 表达式更灵活、更强大,但后者更高效;在均可使用的情况下,您应该更偏好 SQL Case 表达式。
CASE [ search_expression ]
WHEN expression_1 [, expression_2, ...] THEN
when_statements
[ ... ]
[ ELSE
else_statements ]
END CASE;
search_expression 求值一次,然后从上到下与每个分支中的 expression_n 进行比较。如果 search_expression 与给定 expression_n 相等,则 Vertica 针对 expression_n 执行 WHEN 块,并退出 CASE 块。如果找不到匹配的表达式,则执行 ELSE 分支(如果存在)。
Case 表达式必须具有匹配的 Case 或 ELSE 分支,否则 Vertica 将引发 Case_NOT_FOUND 错误。
如果忽略 search_expression,则其值默认为 true
。
例如,此过程进行游戏 FizzBuzz。如果实参可被 3 整除,则输出 Fizz;如果实参可被 5 整除,则输出 Buzz;如果实参可被 3 和 5 整除,则输出 FizzBuzz。
=> CREATE PROCEDURE fizzbuzz(IN x int) LANGUAGE PLvSQL AS $$
DECLARE
fizz int := x % 3;
buzz int := x % 5;
BEGIN
CASE fizz
WHEN 0 THEN -- if fizz = 0, execute WHEN block
CASE buzz
WHEN 0 THEN -- if buzz = 0, execute WHEN block
RAISE INFO 'FizzBuzz';
ELSE -- if buzz != 0, execute WHEN block
RAISE INFO 'Fizz';
END CASE;
ELSE -- if fizz != 0, execute ELSE block
CASE buzz
WHEN 0 THEN
RAISE INFO 'Buzz';
ELSE
RAISE INFO '';
END CASE;
END CASE;
END;
$$;
=> CALL fizzbuzz(3);
INFO 2005: Fizz
=> CALL fizzbuzz(5);
INFO 2005: Buzz
=> CALL fizzbuzz(15);
INFO 2005: FizzBuzz
循环
循环重复执行代码块,直到满足给定条件。
WHILE
WHILE 循环检查给定条件,如果条件为 true,则执行循环体,然后再次检查条件:如果为 true,循环体再次执行;如果为 false,则控制跳到循环体的末尾。
[ <<label>> ]
WHILE condition LOOP
statements;
END LOOP;
例如,此过程计算实参的阶乘:
=> CREATE PROCEDURE factorialSP(input int) LANGUAGE PLvSQL AS $$
DECLARE
i int := 1;
output int := 1;
BEGIN
WHILE i <= input loop
output := output * i;
i := i + 1;
END LOOP;
RAISE INFO '%! = %', input, output;
END;
$$;
=> CALL factorialSP(5);
INFO 2005: 5! = 120
LOOP
此循环类型等同于 WHILE true
,仅在遇到 RETURN 或 EXIT 语句或引发异常时终止。
[ <<label>> ]
LOOP
statements;
END LOOP;
例如,此过程输出从 counter
到 upper_bound
(含)的整数:
DO $$
DECLARE
counter int := 1;
upper_bound int := 3;
BEGIN
LOOP
RAISE INFO '%', counter;
IF counter >= upper_bound THEN
RETURN;
END IF;
counter := counter + 1;
END LOOP;
END;
$$;
INFO 2005: 1
INFO 2005: 2
INFO 2005: 3
FOR
FOR 循环在集合上迭代,集合可以是整体范围、查询或游标。
如果 FOR 循环至少迭代一次,则在循环结束后,特殊的 FOUND 变量设置为 true。否则,FOUND 设置为 false。
FOUND 变量可用于区分返回 NULL 和返回 0 行,或者在 LOOP 未运行时创建 IF 分支。
FOR (RANGE)
FOR (RANGE) 循环在表达式 left 和 right 指定的整数范围内迭代。
[ <<label>> ]
FOR loop_counter IN RANGE [ REVERSE ] left..right [ BY step ] LOOP
statements
END LOOP [ label ];
loop_counter:
-
不必声明,它使用 left 值进行初始化
-
仅在 FOR 循环范围内可用
loop_counter 从 left 迭代到 right(包含),在每次迭代结尾以 step 递增。
相反,REVERSE
选项从 right 迭代到 left(包含),以 step 递减。
例如,下面是一个标准的递增 FOR 循环,其 step = 1:
=> DO $$
BEGIN
FOR i IN RANGE 1..4 LOOP -- loop_counter i does not have to be declared
RAISE NOTICE 'i = %', i;
END LOOP;
RAISE NOTICE 'after loop: i = %', i; -- fails
END;
$$;
NOTICE 2005: i = 1
NOTICE 2005: i = 2
NOTICE 2005: i = 3
NOTICE 2005: i = 4
ERROR 2624: Column "i" does not exist -- loop_counter i is only available inside the FOR loop
在下方示例中,loop_counteri
从 4 开始,在每次迭代结尾以 2 递减:
=> DO $$
BEGIN
FOR i IN RANGE REVERSE 4..0 BY 2 LOOP
RAISE NOTICE 'i = %', i;
END LOOP;
END;
$$;
NOTICE 2005: i = 4
NOTICE 2005: i = 2
NOTICE 2005: i = 0
FOR (query)
FOR (QUERY) 循环在查询结果上进行迭代。
[ <<label>> ]
FOR target IN QUERY statement LOOP
statements
END LOOP [ label ];
您可以在查询中包含 ORDER BY 子句,以使排序具有确定性。
与 FOR (RANGE) 循环不同,您必须声明 target 变量。这些变量的值在循环结束后保持不变。
例如,假设给定表 tuple
:
=> SELECT * FROM tuples ORDER BY x ASC;
x | y | z
---+---+---
1 | 2 | 3
4 | 5 | 6
7 | 8 | 9
(3 rows)
此过程检索每行中的元组,并将它们存储在变量 a
、b
和 c
中,在每次迭代后输出它们:
=>
=> DO $$
DECLARE
a int; -- target variables must be declared
b int;
c int;
i int := 1;
BEGIN
FOR a,b,c IN QUERY SELECT * FROM tuples ORDER BY x ASC LOOP
RAISE NOTICE 'iteration %: a = %, b = %, c = %', i,a,b,c;
i := i + 1;
END LOOP;
RAISE NOTICE 'after loop: a = %, b = %, c = %', a,b,c;
END;
$$;
NOTICE 2005: iteration 1: a = 1, b = 2, c = 3
NOTICE 2005: iteration 2: a = 4, b = 5, c = 6
NOTICE 2005: iteration 3: a = 7, b = 8, c = 9
NOTICE 2005: after loop: a = 7, b = 8, c = 9
您还可以使用通过 EXECUTE 动态构建的查询:
[ <<label>> ]
FOR target IN EXECUTE 'statement' [ USING expression [, ... ] ] LOOP
statements
END LOOP [ label ];
下面的过程使用 EXECUTE 构建 FOR (QUERY) 循环,并将 SELECT 语句的结果存储在变量 x
和 y
中。此类语句的结果集只有一行,因此它只迭代一次。
=> SELECT 'first string', 'second string';
?column? | ?column?
--------------+---------------
first string | second string
(1 row)
=> DO $$
DECLARE
x varchar; -- target variables must be declared
y varchar;
BEGIN
-- substitute the placeholders $1 and $2 with the strings
FOR x, y IN EXECUTE 'SELECT $1, $2' USING 'first string', 'second string' LOOP
RAISE NOTICE '%', x;
RAISE NOTICE '%', y;
END LOOP;
END;
$$;
NOTICE 2005: first string
NOTICE 2005: second string
FOR (cursor)
FOR (CURSOR) 循环在绑定的、未打开的游标上进行迭代,为每次迭代执行一组 statements。
[ <<label>> ]
FOR loop_variable [, ...] IN CURSOR bound_unopened_cursor [ ( [ arg_name := ] arg_value [, ...] ) ] LOOP
statements
END LOOP [ label ];
这种类型的 FOR 循环在循环开始时打开游标,在循环结束时关闭游标。
例如,此过程创建游标 c
。该过程将 6
作为实参传递给游标,因此游标仅检索 y 坐标为 6 的行,将坐标存储在变量 x_
、y_
和 z_
中,并在每次迭代结束时输出它们:
=> SELECT * FROM coordinates;
x | y | z
----+---+----
14 | 6 | 19
1 | 6 | 2
10 | 6 | 39
10 | 2 | 1
7 | 1 | 10
67 | 1 | 77
(6 rows)
DO $$
DECLARE
c CURSOR (key int) FOR SELECT * FROM coordinates WHERE y=key;
x_ int;
y_ int;
z_ int;
BEGIN
FOR x_,y_,z_ IN CURSOR c(6) LOOP
RAISE NOTICE 'cursor returned %,%,% FOUND=%', x_,y_,z_,FOUND;
END LOOP;
RAISE NOTICE 'after loop: %,%,% FOUND=%', x_,y_,z_,FOUND;
END;
$$;
NOTICE 2005: cursor returned 14,6,19 FOUND=f -- FOUND is only set after the loop ends
NOTICE 2005: cursor returned 1,6,2 FOUND=f
NOTICE 2005: after loop: 10,6,39 FOUND=t -- x_, y_, and z_ retain their values, FOUND is now true because the FOR loop iterated at least once
操作循环
RETURN
您可以使用 RETURN 退出整个过程(因此退出循环)。RETURN 是可选语句,可以添加它来向读取器发出过程结束的信号。
RETURN;
EXIT
与其他编程语言中的 break
或带标签 break
类似,EXIT 语句允许您提早退出循环,可以选择指定:
-
loop_label:退出的循环的名称
-
condition:如果 condition 为
true
,则执行 EXIT 语句
EXIT [ loop_label ] [ WHEN condition ];
CONTINUE
CONTINUE 跳到循环的下一次迭代,而不执行 CONTINUE 本身之后的语句。您可以指定具有 loop_label 的特定循环:
CONTINUE [loop_label] [ WHEN condition ];
例如,此过程在其前两次迭代期间不输出,因为 CONTINUE 语句将在控制到达 RAISE NOTICE 语句之前执行并移动到循环的下一次迭代:
=> DO $$
BEGIN
FOR i IN RANGE 1..5 LOOP
IF i < 3 THEN
CONTINUE;
END IF;
RAISE NOTICE 'i = %', i;
END LOOP;
END;
$$;
NOTICE 2005: i = 3
NOTICE 2005: i = 4
NOTICE 2005: i = 5