↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2
历史版本PostgreSQL 7.4 已于 2010 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本。

37.7. 控制结构 #

控制结构也许是 PL/pgSQL 最有用(也最重要) 的部分。使用 PL/pgSQL 的控制结构,你可以 非常灵活而强大地操纵 PostgreSQL 数据。

37.7.1. 从函数返回 #

有两个命令可用于从函数返回数据:RETURN 和 RETURN NEXT。

37.7.1.1. RETURN

RETURN expression;

带表达式的 RETURN 终止函数并把 expression 的值返回给调用者。这种形式用于 不返回集合的 PL/pgSQL 函数。

返回标量类型时,可以使用任何表达式。表达式的结果会像赋值所描述的 那样自动转换为函数的返回类型。要返回复合(行)值,必须把一个记录 或行变量写作 expression。

函数的返回值不能没有定义。如果控制到达函数顶层块的末尾而没有遇到 RETURN 语句,将发生运行时错误。

如果你已把函数声明为返回 void,仍必须指定 RETURN 语句;但这种情况下 RETURN 后面的表达式是可选的,如果存在也会被忽略。

37.7.1.2. RETURN NEXT

RETURN NEXT expression;

当 PL/pgSQL 函数被声明为返回 SETOF sometype 时,要遵循的过程 略有不同。这种情况下,要返回的各个项在 RETURN 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 配置变量控制。有足够内存把更大 结果集保存在内存中的管理员应考虑增大此参数。

37.7.2. 条件 #

IF 语句让你根据某些条件执行命令。 PL/pgSQL 有四种 IF 形式:

  • IF ... THEN

  • IF ... THEN ... ELSE

  • IF ... THEN ... ELSE IF

  • IF ... THEN ... ELSIF ... THEN ... ELSE

37.7.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;

37.7.2.2. IF-THEN-ELSE

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

37.7.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 语句。因此,每个嵌套的 IF 需要一个 END IF 语句,父级 IF-ELSE 也需要一个。这可行,但当要检查的备选很多时就变得乏味。因此有下 一种形式。

37.7.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
    -- hmm, the only other possibility is that number is null
    result := ''NULL'';
END IF;

37.7.3. 简单循环 #

使用 LOOP、EXIT、WHILE 和 FOR 语句,你可以让 PL/pgSQL 函数重复执行一系列命令。

37.7.3.1. LOOP

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

LOOP 定义一个无限重复的无条件循环,直到被 EXIT 或 RETURN 语句终止。可选的标签 可供嵌套循环中的 EXIT 语句用来指定应终止哪一层嵌套。

37.7.3.2. EXIT

EXIT [ label ] [ WHEN expression ];

如果没有给出 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;

37.7.3.3. WHILE

[<<label>>]
WHILE expression LOOP
    statements
END 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;

37.7.3.4. FOR(整数变体)

[<<label>>]
FOR name IN [ REVERSE ] expression .. expression LOOP
    statements
END 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 情况下则是小于),循环体 完全不执行。不会引发错误。

37.7.4. 遍历查询结果 #

使用另一种类型的 FOR 循环,你可以遍历查询的结果并相应地 操纵那些数据。语法为:

[<<label>>]
FOR record_or_row IN 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

        -- 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>>]
FOR record_or_row IN EXECUTE text_expression LOOP 
    statements
END LOOP;

这与前面的形式类似,不同之处在于源 SELECT 语句 是作为字符串表达式指定的,它在每次进入 FOR 循环时被求值 并重新规划。这使程序员可以在预规划查询的速度和动态查询的灵活性之间 做出选择,就像普通的 EXECUTE 语句一样。

注意

PL/pgSQL 解析器目前通过检查 FOR 之后提到 的目标变量是否被声明为记录或行变量来区分两类 FOR 循环 (整数或查询结果)。如果不是,就推定为整数 FOR 循环。 当真正的问题比如说只是 FOR 后面的变量名拼错时,这可能 导致相当不直观的错误消息。通常报的错类似于 missing ".." at end of SQL expression。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。