pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
控制结构也许是 PL/pgSQL 最有用(也最重要) 的部分。使用 PL/pgSQL 的控制结构,你可以 非常灵活而强大地操纵 PostgreSQL 数据。
有两个命令可用于从函数返回数据:RETURN 和 RETURN NEXT。
RETURN
RETURN expression;
带表达式的 RETURN 终止函数并把 expression 的值返回给调用者。这种形式用于 不返回集合的 PL/pgSQL 函数。
返回标量类型时,可以使用任何表达式。表达式的结果会像赋值所描述的 那样自动转换为函数的返回类型。要返回复合(行)值,必须把一个记录 或行变量写作 expression。
函数的返回值不能没有定义。如果控制到达函数顶层块的末尾而没有遇到 RETURN 语句,将发生运行时错误。
如果你已把函数声明为返回 void,仍必须指定 RETURN 语句;但这种情况下 RETURN 后面的表达式是可选的,如果存在也会被忽略。
RETURN NEXT
RETURN NEXT expression;
当 PL/pgSQL 函数被声明为返回 SETOF 时,要遵循的过程 略有不同。这种情况下,要返回的各个项在 sometypeRETURN NEXT 命令中指定,然后用一条不带参数的最终 RETURN 命令 表示函数已执行完毕。RETURN NEXT 既可用于标量数据 类型,也可用于复合数据类型;后者将返回整张“表”的结果。
使用 RETURN NEXT 的函数应按以下方式调用:
SELECT * FROM some_func();
也就是说,函数在 FROM 子句中用作表源。
RETURN NEXT 实际上并不从函数返回;它只是保存表达式 (或记录或行变量,视返回的数据类型而定)的值。然后执行继续到 PL/pgSQL 函数中的下一条语句。随着相继的 RETURN NEXT 命令被执行,结果集逐渐建立。最后的 RETURN(不应带参数)使控制退出函数。
PL/pgSQL 当前对 RETURN NEXT 的实现是在从函数返回之前保存整个结果集,如上所述。这意味着,如果 PL/pgSQL 函数产生非常大的结果集,性能 可能不佳:为避免耗尽内存,数据会被写入磁盘,但函数本身要等到整个 结果集生成之后才会返回。PL/pgSQL 的未来 版本可能允许用户定义没有这一限制的返回集合的函数。目前,数据开始 写入磁盘的时机由 sort_mem 配置变量控制。有足够内存把更大 结果集保存在内存中的管理员应考虑增大此参数。
IF 语句让你根据某些条件执行命令。 PL/pgSQL 有四种 IF 形式:
IF ... THEN
IF ... THEN ... ELSE
IF ... THEN ... ELSE IF
IF ... THEN ... ELSIF ... THEN ... ELSE
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 的基础上增加了对条件求值为假时应执行的另一组语句的指定。
例子:
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-ELSE IFIF 语句可以嵌套,如下例所示:
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 语句。因此,每个嵌套的 IF 需要一个 END IF 语句,父级 IF-ELSE 也需要一个。这可行,但当要检查的备选很多时就变得乏味。因此有下 一种形式。
IF-THEN-ELSIF-ELSEIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements...]] [ ELSEstatements] 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
-- hmm, the only other possibility is that number is null
result := ''NULL'';
END IF;
使用 LOOP、EXIT、WHILE 和 FOR 语句,你可以让 PL/pgSQL 函数重复执行一系列命令。
LOOP[<<label>>] LOOPstatementsEND LOOP;
LOOP 定义一个无限重复的无条件循环,直到被 EXIT 或 RETURN 语句终止。可选的标签 可供嵌套循环中的 EXIT 语句用来指定应终止哪一层嵌套。
EXITEXIT [label] [ WHENexpression];
如果没有给出 label,最内层的循环被终止, 接下来执行 END LOOP 之后的语句。如果给出了 label,它必须是当前层或某个外层嵌套循环 或块的标签。然后命名的循环或块被终止,控制继续到该循环/块对应的 END 之后的语句。
如果存在 WHEN,只有当指定条件为真时才退出循环,否则 控制传递到 EXIT 之后的语句。
例子:
LOOP
-- some computations
IF count > 0 THEN
EXIT; -- exit loop
END IF;
END LOOP;
LOOP
-- some computations
EXIT WHEN count > 0; -- same result as previous example
END LOOP;
BEGIN
-- some computations
IF stocks > 100000 THEN
EXIT; -- invalid; cannot use EXIT outside of LOOP
END IF;
END;
WHILE[<<label>>] WHILEexpressionLOOPstatementsEND LOOP;
WHILE 语句在条件表达式求值为真期间重复一个语句序列。 条件在每次进入循环体之前检查。
例如:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- some computations here
END LOOP;
WHILE NOT boolean_expression LOOP
-- some computations here
END LOOP;
FOR(整数变体)[<<label>>] FORnameIN [ REVERSE ]expression..expressionLOOPstatementsEND LOOP;
这种形式的 FOR 创建一个在整数值范围上迭代的循环。变量 name 自动定义为 integer 类型, 且只存在于循环内部。给出范围下界和上界的两个表达式在进入循环时求值 一次。迭代步长通常为 1,指定 REVERSE 时为 -1。
整数 FOR 循环的一些例子:
FOR i IN 1..10 LOOP
-- some computations here
RAISE NOTICE ''i is %'', i;
END LOOP;
FOR i IN REVERSE 10..1 LOOP
-- some computations here
END LOOP;
如果下界大于上界(REVERSE 情况下则是小于),循环体 完全不执行。不会引发错误。
使用另一种类型的 FOR 循环,你可以遍历查询的结果并相应地 操纵那些数据。语法为:
[<<label>>] FORrecord_or_rowINqueryLOOPstatementsEND 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
-- Now "mviews" has one record from cs_materialized_views
PERFORM cs_log(''Refreshing materialized view '' || quote_ident(mviews.mv_name) || ''...'');
EXECUTE ''TRUNCATE TABLE '' || quote_ident(mviews.mv_name);
EXECUTE ''INSERT INTO '' || quote_ident(mviews.mv_name) || '' '' || mviews.mv_query;
END LOOP;
PERFORM cs_log(''Done refreshing materialized views.'');
RETURN 1;
END;
' LANGUAGE plpgsql;
如果循环被 EXIT 语句终止,最后赋的行值在循环之后仍可 访问。
FOR-IN-EXECUTE 语句是遍历记录的另一种方式:
[<<label>>] FORrecord_or_rowIN EXECUTEtext_expressionLOOPstatementsEND LOOP;
这与前面的形式类似,不同之处在于源 SELECT 语句 是作为字符串表达式指定的,它在每次进入 FOR 循环时被求值 并重新规划。这使程序员可以在预规划查询的速度和动态查询的灵活性之间 做出选择,就像普通的 EXECUTE 语句一样。
PL/pgSQL 解析器目前通过检查 FOR 之后提到 的目标变量是否被声明为记录或行变量来区分两类 FOR 循环 (整数或查询结果)。如果不是,就推定为整数 FOR 循环。 当真正的问题比如说只是 FOR 后面的变量名拼错时,这可能 导致相当不直观的错误消息。通常报的错类似于 missing ".." at end of SQL expression。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。