控制流

控制流构造使您可以控制语句块应运行的次数和条件。

条件

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;

例如,此过程输出从 counterupper_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) 循环在表达式 leftright 指定的整数范围内迭代。

[ <<label>> ]
FOR loop_counter IN RANGE [ REVERSE ] left..right [ BY step ] LOOP
    statements
END LOOP [ label ];

loop_counter

  • 不必声明,它使用 left 值进行初始化

  • 仅在 FOR 循环范围内可用

loop_counterleft 迭代到 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)

此过程检索每行中的元组,并将它们存储在变量 abc 中,在每次迭代后输出它们:

=>
=> 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 语句的结果存储在变量 xy 中。此类语句的结果集只有一行,因此它只迭代一次。

=> 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:如果 conditiontrue,则执行 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