19.6. 控制结构

控制结构可能是 PL/pgSQL 中最有用的(以及最重要) 的部分了.利用 PL/pgSQL 的控制结构, 你可以以非常灵活而且强大的方法操纵 PostgreSQL 的数据.

19.6.1. 从函数返回

RETURN expression;

带表达式的 RETURN 是用于从一个不返回 结果集的 PL/pgSQL 函数中返回。 函数终止,然后 expression 的值返回给调用者。

要返回一个复合(行)数值,你必须写一个记录或者行变量 做 expression。如果返回的是 标量类型,那么可以使用任何表达式。表达式的结果将自动被 转换成函数声明的返回类型,就象赋值里说的那样。 (如果你声明了该函数返回 void,那么可以省略这个 表达式,并且在任何情况下都会被忽略。)

一个函数的返回值不能是未定义。如果控制到达了函数的最顶层 的块而没有碰到一个 RETURN 语句,那么它 就会发生一个错误。

如果一个 PL/pgSQL 函数声明为返回 SETOF sometype,那么遵循的过程则略有不同。在这种 情况下,要返回的独立的项是在 RETURN NEXT 命令里声明的,然后最后有一个不带参数的 RETURN 命令 用于告诉我们这个函数已经完成执行了。RETURN NEXT 可以用于标量和复合数据类型;对于后者,将返回一个完整的结果“表”。 使用 RETURN NEXT 的函数应该按照下面的风格 调用:

SELECT * FROM some_func();

也就是说,这个函数是用做 FROM 子句里面的一个表数据源的。

RETURN NEXT expression;

RETURN NEXT 实际上并不从函数中返回; 它只是简单地把表达式的值(或者记录或者行变量,只要是对 返回的数据类型合适的东西)保存起来。然后执行继续执行 PL/pgSQL 函数里的下一条语句。随着后继 的 RETURN NEXT 命令的执行,结果集 就建立起来了。最后的一个不需要参数的 RETURN, 导致控制退出该函数。

注意: 目前的 PL/pgSQLRETURN NEXT 实现在从函数返回之前把整个结果集都保存起来,就象上面描述的那样。 这意味着如果一个 PL/pgSQL 函数生成一个非常大 的结果集,性能可能会很差:数据将被写到磁盘上以避免内存耗尽, 但是函数在完成整个结果集的生成之前不会退出。将来的 PL/pgSQL 版本可能会允许用户定义没有这样限制的返回集合的函数。 目前,数据开始向磁盘里写的时刻是由配置变量 SORT_MEM 控制的。拥有足够内存的管理员如果想在内存里存储更大的结果集, 则可以考虑把这个参数增大一些。

19.6.2. 条件

IF 语句让你可以根据某种条件执行命令. PL/pgSQL有四种形式的IF

19.6.2.1. IF-THEN

IF boolean-expression THEN
    statements
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;

19.6.2.2. IF-THEN-ELSE

IF boolean-expression THEN
    statements
ELSE
    statements
END IF;

IF-THEN-ELSE 语句增加了 IF-THEN 的分支,让你可以声明 在条件计算为 FALSE 的时候执行的语句.

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;

19.6.2.3. IF-THEN-ELSE IF

IF 语句可以嵌套并且在下面的例子中∶

IF demo_row.sex = ''m'' THEN
  pretty_sex := ''man'';
ELSE
  IF demo_row.sex = ''f'' THEN
    pretty_sex := ''woman'';
  END IF;
END IF;

如果你使用这种形式,那么你实际上就是在另外一个 IF 语句的 ELSE 部分嵌套了一个 IF 语句.因此你需要一个 END IF 语句 给每个嵌套的 IF,另外还要一个给父 IF-ELSE 用. 这么干是可以的,但是如果我们有太多候选项需要检查,那么就会变得很乏味.

19.6.2.4. IF-THEN-ELSIF-ELSE

IF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
[ ELSIF boolean-expression THEN
    statements
    ...]]
[ ELSE
    statements ]
END IF;

IF-THEN-ELSIF-ELSE提供了一种更方便的方法用于在一条语句中 检查许多候选.形式上它和嵌套的IF-THEN-ELSE-IF-THEN命令相同, 但是只需要一个END IF

这里是一个例子∶

IF number = 0 THEN
    result := ''zero'';
ELSIF number > 0 THEN
    result := ''positive'';
ELSIF number < 0 THEN
    result := ''negative'';
ELSE
    -- 另外一个唯一的可能是它是 NULL
    result := ''NULL'';
END IF;

最后的 ELSE 段是可选的.

19.6.3. 简单循环

使用 LOOP,WHILE,FOR 和 EXIT 语句,你可以控制你的 PL/pgSQL 函数重复一系列明令.

19.6.3.1. LOOP

[<<label>>]
LOOP
    statements
END LOOP;

LOOP 定义一个无条件的循环,无限循环,直到由 EXIT 或者 RETURN 语句终止. 可选的标签可以由 EXIT 语句使用,用于在嵌套循环中声明应该结束哪一层循环.

19.6.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

如果没有给出 label, 那么退出最内层的循环,然后执行跟在 END LOOP 后面的语句. 如果给出 label, 那么它必须是当前或者更高层的嵌套循环块或者块的标签. 然后该命名块或者循环就会终止,而控制落到对应循环/块的 END 语句后面的语句上.

如果出现了 WHEN,循环退出只发生在声明的条件为真的时候, 否则控制会落到 EXIT 后面的语句上.

例子∶

LOOP
    -- 一些计算
    IF count > 0 THEN
        EXIT;  -- exit loop
    END IF;
END LOOP;

LOOP
    -- 一些计算
    EXIT WHEN count > 0;
END LOOP;

BEGIN
    -- 一些计算
    IF stocks > 100000 THEN
        EXIT;  -- 非法,不能用 EXIT 退出到 LOOP 外面.
    END IF;
END;

19.6.3.3. WHILE

[<<label>>]
WHILE expression LOOP
    statements
END LOOP;

只要条件表达式为真,WHILE 语句就会不停在一系列语句上进行循环. 条件是在每次进入循环体的时候检查的.

比如∶

WHILE amount_owed > 0 AND gIFt_certIFicate_balance > 0 LOOP
    -- 可以在这里做些计算
END LOOP;

WHILE NOT BOOLEAN_expression LOOP
    -- 可以在这里做些计算
END LOOP;

19.6.3.4. FOR (整数 for 循环)

[<<label>>]
FOR name IN [ REVERSE ] expression .. expression LOOP
    statements
END LOOP;

这种形式的 FOR 对一定范围的整数数值进行迭代的循环.变量 name 会自动定义为整数类型并且只在 循环里存在.给出范围上下界的两个表达式在进入循环的时候计算一次. 迭代步进值总是为 1,但如果声明了 REVERSE 就是 -1.

一些整数 FOR 循环的例子∶

FOR i IN 1..10 LOOP
  -- 这里可以放一些表达式

    RAISE NOTICE ''i IS %'',i;
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- 这里可以放一些表达式
END LOOP;

19.6.4. 遍历查询结果

使用不同类型的 FOR 循环,你可以遍历一个查询的结果并且相应地 操作哪些数据.语法是∶

[<<label>>]
FOR record | row IN select_query LOOP
    statements
END LOOP;

这里的记录或者行变量将相继被赋予所有来自 SELECT 查询的行, 并且循环体将为每行执行一次.下面是一个例子∶

CREATE FUNCTION cs_refresh_mviews () RETURNS INTEGER AS '
DECLARE
     mviews RECORD;

BEGIN
     PERFORM cs_log(''Refreshing materialized views...'');

     FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP

         -- 现在 "mviews" 里有了一条来自 cs_materialized_views 的记录

         PERFORM cs_log(''Refreshing materialized view '' || quote_ident(mview.mv_name) || ''...'');
         TRUNCATE TABLE quote_ident(mview.mv_name);
         INSERT INTO quote_ident(mview.mv_name) || '' '' || mview.mv_query;
     END LOOP;

     PERFORM cs_log(''Done refreshing materialized views.'');
     RETURN 1;
END;
' LANGUAGE 'plpgsql';

如果循环是用一个 EXIT 语句终止的,那么在循环之后你仍然可以 访问最后赋值的行.

FOR-IN-EXECUTE 语句是遍历所有记录的另外一种方法∶

[<<label>>]
FOR record | row IN EXECUTE text_expression LOOP 
    statements
END LOOP;

这个例子类似前面的形式,只不过源SELECT语句声明为了一个 字串表达式,这样它在每次进入FOR循环的时候都会重新计算和 生成查询规划.这样就允许程序员在一个预先规划好了的查询所获得 的速度和一个动态查询所获得的灵活性(就象一个简单的EXECUTE 语句那样)之间进行选择.

注意: PL/pgSQL 分析器目前区分两种类型的FOR循环 (整数或者返回记录的)∶方法是检查紧跟在FOR后面的目标变量是否 声明为了记录/行变量.如果不是,那么它假设是一次FOR循环. 这样,在出现真正的问题的时候可能会导致相当不明确的错误信息, 比如我们不小心拼错了FOR变量的名字的时候.