受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

41.6. 控制结构 #

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

41.6.1. 从函数返回 #

有两个命令让我们能够从函数中返回数据:RETURNRETURN NEXT

41.6.1.1. RETURN #

RETURN expression;

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

如果函数返回的是标量类型,表达式结果会自动转换为函数的返回类型。但如果要返回一个组合(行)值,你必须写出一个恰好生成所需列集合的表达式。这可能需要显式类型转换。

如果你声明带输出参数的函数,那么就只需要写不带表达式的RETURN。输出参数变量的当前值将被返回。

如果你声明函数返回void,一个RETURN语句可以被用来提前退出函数;但是不要在RETURN后面写一个表达式。

一个函数的返回值不能是未定义。如果控制到达了函数最顶层的块而没有碰到一个RETURN语句,那么会发生一个运行时错误。不过,这个限制不适用于带输出参数的函数以及返回void的函数。在这些情况中,如果顶层的块结束,将自动执行一个RETURN语句。

一些示例:

-- 返回一个标量类型的函数
RETURN 1 + 2;
RETURN scalar_var;

-- 返回一个复合类型的函数
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- 必须把列类型转换成正确的类型

41.6.1.2. RETURN NEXTRETURN QUERY #

RETURN NEXT expression;
RETURN QUERY query;
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];

PL/pgSQL 函数被声明为返回 SETOF sometype 时,返回过程会略有不同。在这种情况下,要返回的各个项通过一系列 RETURN NEXTRETURN QUERY 命令指定,最后再用一个不带参数的 RETURN 命令表明函数已经执行完毕。RETURN NEXT 可用于标量和组合数据类型;对于组合结果类型,会返回完整的结果RETURN QUERY 会把查询执行结果追加到函数的结果集中。在同一个集合返回函数中,RETURN NEXTRETURN QUERY 可以自由混用,此时它们的结果会被串接起来。

RETURN NEXTRETURN QUERY实际上不会从函数中返回 — 它们简单地向函数的结果集中追加零或多行。然后会继续执行PL/pgSQL函数中的下一条语句。随着后继的RETURN NEXTRETURN QUERY命令的执行,结果集就建立起来了。最后一个RETURN(应该没有参数)会导致控制退出该函数(或者你可以让控制到达函数的结尾)。

RETURN QUERY有一种变体RETURN QUERY EXECUTE,它可以动态指定要被执行的查询。可以通过USING向计算出的查询字符串插入参数表达式,这和在EXECUTE命令中的方式相同。

如果你声明函数带有输出参数,只需要写不带表达式的RETURN NEXT。在每一次执行时,输出参数变量的当前值将被保存下来用于最终返回为结果的一行。注意为了创建一个带有输出参数的集合返回函数,在有多个输出参数时,你必须声明函数为返回SETOF record;或者如果只有一个类型为sometype的输出参数时,声明函数为SETOF sometype

下面是一个使用RETURN NEXT的函数示例:

CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');

CREATE OR REPLACE FUNCTION get_all_foo() RETURNS SETOF foo AS
$BODY$
DECLARE
    r foo%rowtype;
BEGIN
    FOR r IN
        SELECT * FROM foo WHERE fooid > 0
    LOOP
        -- 这里可以做一些处理
        RETURN NEXT r; -- 返回 SELECT 的当前行
    END LOOP;
    RETURN;
END;
$BODY$
LANGUAGE plpgsql;

SELECT * FROM get_all_foo();

这里是一个使用RETURN QUERY的函数的示例:

CREATE FUNCTION get_available_flightid(date) RETURNS SETOF integer AS
$BODY$
BEGIN
    RETURN QUERY SELECT flightid
                   FROM flight
                  WHERE flightdate >= $1
                    AND flightdate < ($1 + 1);

    -- 因为执行还未结束,我们可以检查是否有行被返回
    -- 如果没有就抛出异常。
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END;
$BODY$
LANGUAGE plpgsql;

-- 返回可用的航班或者在没有可用航班时抛出异常。
SELECT * FROM get_available_flightid(CURRENT_DATE);

Note

如上所述,目前RETURN NEXTRETURN QUERY的实现在从函数返回之前会把整个结果集都保存起来。这意味着如果一个PL/pgSQL函数生成一个非常大的结果集,性能可能会很差:数据将被写到磁盘上以避免内存耗尽,但是函数本身在整个结果集都生成之前不会退出。将来的PL/pgSQL版本可能会允许用户定义没有这种限制的集合返回函数。目前,数据开始被写入到磁盘的时机由配置变量work_mem控制。拥有足够内存来存储大型结果集的管理员可以考虑增大这个参数。

41.6.2. 从过程返回 #

过程没有返回值。因此,过程的结束可以不用RETURN语句。 如果想用一个RETURN语句提前退出代码,只需写一个没有表达式的RETURN

如果过程有输出参数,那么输出参数最终的值会被返回给调用者。

41.6.3. 调用过程 #

PL/pgSQL函数、过程或DO块都可以使用 CALL调用过程。 输出参数的处理方式与普通 SQL 中CALL的工作方式不同。 过程的每个OUTINOUT参数都必须对应CALL语句中的一个变量, 并且过程返回的任何值都会在返回后赋回该变量。 例如:

CREATE PROCEDURE triple(INOUT x int)
LANGUAGE plpgsql
AS $$
BEGIN
    x := x * 3;
END;
$$;

DO $$
DECLARE myvar int := 5;
BEGIN
  CALL triple(myvar);
  RAISE NOTICE 'myvar = %', myvar;  -- prints 15
END;
$$;

与输出参数对应的变量可以是简单变量,也可以是复合类型变量的字段。目前它不能是数组元素。

41.6.4. 条件语句 #

IFCASE 语句让你可以根据条件执行不同的命令。PL/pgSQL 有三种形式的 IF

  • IF ... THEN ... END IF

  • IF ... THEN ... ELSE ... END IF

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

以及两种形式的CASE

  • CASE ... WHEN ... THEN ... ELSE ... END CASE

  • CASE WHEN ... THEN ... ELSE ... END CASE

41.6.4.1. IF-THEN #

IF boolean-expression THEN
    statements
END IF;

IF-THEN语句是IF的最简单形式。 如果条件为真,在THENEND IF之间的语句将被执行。否则,将忽略它们。

示例:

IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
END IF;

41.6.4.2. IF-THEN-ELSE #

IF boolean-expression THEN
    statements
ELSE
    statements
END IF;

IF-THEN-ELSE语句对IF-THEN进行了增加,它让你能够指定一组在条件不为真时应该被执行的语句(注意这也包括条件为 NULL 的情况)。

示例:

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;

41.6.4.3. IF-THEN-ELSIF #

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

有时会有多于两种选择。IF-THEN-ELSIF则提供了一个简便的方法来检查多个条件。IF条件会被一个接一个测试,直到找到第一个为真的。然后执行相关语句,然后控制会被交给END IF之后的下一个语句(后续的任何IF条件不会被测试)。如果没有一个IF条件为真,那么ELSE块(如果有)将被执行。

这里有一个示例:

IF number = 0 THEN
    result := 'zero';
ELSIF number > 0 THEN
    result := 'positive';
ELSIF number < 0 THEN
    result := 'negative';
ELSE
    -- 嗯,唯一的其他可能性是数字为空
    result := 'NULL';
END IF;

关键词ELSIF也可以被拼写成ELSEIF

另一个可以完成相同任务的方法是嵌套IF-THEN-ELSE语句,如下例:

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

不过,这种方法需要为每个IF都写一个匹配的END IF,因此当有很多选择时,这种方法比使用ELSIF要麻烦得多。

41.6.4.4. 简单 CASE #

CASE search-expression
    WHEN expression [, expression [ ... ]] THEN
      statements
  [ WHEN expression [, expression [ ... ]] THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

CASE 的简单形式提供了基于操作数等值判断的条件执行。search-expression 会被计算一次,然后依次与各个 WHEN 子句中的 expression 比较。如果找到匹配,就执行相应的 statements,随后控制转到 END CASE 之后的下一条语句(后续的 WHEN 表达式不会再被计算)。如果没有找到匹配,则执行 ELSE statements;但如果没有 ELSE,则会抛出 CASE_NOT_FOUND 异常。

这里是一个简单的示例:

CASE x
    WHEN 1, 2 THEN
        msg := 'one or two';
    ELSE
        msg := 'other value than one or two';
END CASE;

41.6.4.5. 搜索式 CASE #

CASE
    WHEN boolean-expression THEN
      statements
  [ WHEN boolean-expression THEN
      statements
    ... ]
  [ ELSE
      statements ]
END CASE;

CASE的搜索式形式根据布尔表达式的真假进行条件执行。每个WHEN子句的boolean-expression都会依次求值,直到找到一个结果为true的表达式。然后执行相应的statements,控制接着转到END CASE之后的下一条语句。(后续的WHEN表达式不会再被求值。)如果没有找到为真的结果,就执行ELSE statements;但如果不存在ELSE,则会抛出CASE_NOT_FOUND异常。

这里是一个示例:

CASE
    WHEN x BETWEEN 0 AND 10 THEN
        msg := 'value is between zero and ten';
    WHEN x BETWEEN 11 AND 20 THEN
        msg := 'value is between eleven and twenty';
END CASE;

这种形式的CASEIF-THEN-ELSIF完全等价,唯一的区别是:如果省略了ELSE子句,CASE会报错,而不是简单地什么也不做。

41.6.5. 简单循环 #

使用LOOPEXITCONTINUEWHILEFORFOREACH语句,你可以安排PL/pgSQL重复一系列命令。

41.6.5.1. LOOP #

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

LOOP定义一个无条件的循环,它会无限重复直到被EXITRETURN语句终止。可选的label可以被EXITCONTINUE语句用在嵌套循环中指定这些语句引用的是哪一层循环。

41.6.5.2. EXIT #

EXIT [ label ] [ WHEN boolean-expression ];

如果没有给出label,那么最内层的循环会被终止,然后跟在END LOOP后面的语句会被执行。如果给出了label,那么它必须是当前或者更高层的嵌套循环或者语句块的标签。然后该命名循环或块就会被终止,并且控制会转移到该循环/块相应的END之后的语句上。

如果指定了WHEN,只有boolean-expression为真时才会发生循环退出。否则,控制会转移到EXIT之后的语句。

EXIT可以被用在所有类型的循环中,它并不限于在无条件循环中使用。

BEGIN 块一起使用时,EXIT 会把控制转交给该块结束后的下一条语句。需要注意的是,为此必须使用标签;未加标签的 EXIT 永远不会被视为匹配某个 BEGIN 块。这与 PostgreSQL 8.4 之前的版本不同,旧版本允许未加标签的 EXIT 匹配 BEGIN 块。

示例:

LOOP
    -- 一些计算
    IF count > 0 THEN
        EXIT;  -- 退出循环
    END IF;
END LOOP;

LOOP
    -- 一些计算
    EXIT WHEN count > 0;  -- 和前一个示例相同的结果
END LOOP;

<<ablock>>
BEGIN
    -- 一些计算
    IF stocks > 100000 THEN
        EXIT ablock;  -- 导致从 BEGIN 块中退出
    END IF;
    -- 当stocks > 100000时,这里的计算将被跳过
END;

41.6.5.3. CONTINUE #

CONTINUE [ label ] [ WHEN boolean-expression ];

如果没有给出label,最内层循环的下一次迭代会开始。也就是,循环体中剩余的所有语句将被跳过,并且控制会返回到循环控制表达式(如果有)来决定是否需要另一次循环迭代。如果label存在,它指定应该继续执行的循环的标签。

如果指定了WHEN,该循环的下一次迭代只有在boolean-expression为真时才会开始。否则,控制会传递给CONTINUE后面的语句。

CONTINUE可以被用在所有类型的循环中,它并不限于在无条件循环中使用。

示例:

LOOP
    -- 一些计算
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- 一些用于 count IN [50 .. 100] 的计算
END LOOP;

41.6.5.4. WHILE #

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

只要boolean-expression被计算为真,WHILE语句就会重复一个语句序列。在每次进入到循环体之前都会检查该表达式。

例如:

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- 这里是一些计算
END LOOP;

WHILE NOT done LOOP
    -- 这里是一些计算
END LOOP;

41.6.5.5. FOR(整型变体) #

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

这种形式的FOR会创建一个在一个整数范围上迭代的循环。变量name会自动定义为类型integer并且只在循环内存在(任何该变量名的现有定义在此循环内都将被忽略)。给出范围上下界的两个表达式在进入循环的时候计算一次。如果没有指定BY子句,迭代步长为 1,否则步长是BY中指定的值,该值也只在循环进入时计算一次。如果指定了REVERSE,那么在每次迭代后步长值会被减除而不是增加。

整数FOR循环的一些示例:

FOR i IN 1..10 LOOP
    -- 我在循环中将取值 1,2,3,4,5,6,7,8,9,10 
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- 我在循环中将取值 10,9,8,7,6,5,4,3,2,1 
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- 我在循环中将取值 10,8,6,4,2 
END LOOP;

如果下界大于上界(或者在REVERSE情况下是小于),循环体根本不会被执行。而且不会抛出任何错误。

如果一个label被附加到FOR循环,那么整数循环变量可以用一个使用那个label的限定名引用。

41.6.6. 遍历查询结果 #

使用一种不同类型的FOR循环,你可以通过一个查询的结果进行迭代并且操纵相应的数据。语法是:

[ <<label>> ]
FOR target IN query LOOP
    statements
END LOOP [ label ];

target 可以是记录变量、行变量,或者由标量变量组成的逗号分隔列表。query 产生的每一行都会依次赋给 target,并为每一行执行一次循环体。下面是一个示例:

CREATE FUNCTION refresh_mviews() RETURNS integer AS $$
DECLARE
    mviews RECORD;
BEGIN
    RAISE NOTICE 'Refreshing all materialized views...';

    FOR mviews IN
       SELECT n.nspname AS mv_schema,
              c.relname AS mv_name,
              pg_catalog.pg_get_userbyid(c.relowner) AS owner
         FROM pg_catalog.pg_class c
    LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
        WHERE c.relkind = 'm'
     ORDER BY 1
    LOOP

        -- Now "mviews" has one record with information about the materialized view

        RAISE NOTICE 'Refreshing materialized view %.% (owner: %)...',
                     quote_ident(mviews.mv_schema),
                     quote_ident(mviews.mv_name),
                     quote_ident(mviews.owner);
        EXECUTE format('REFRESH MATERIALIZED VIEW %I.%I', mviews.mv_schema, mviews.mv_name);
    END LOOP;

    RAISE NOTICE 'Done refreshing materialized views.';
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

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

在这类 FOR 语句中使用的 query,可以是任何向调用者返回行的 SQL 命令:最常见的是 SELECT,但也可以是带 RETURNING 子句的 INSERTUPDATEDELETEMERGE。某些实用程序命令,如 EXPLAIN,也可以用于此处。

PL/pgSQL变量会被查询参数替换,并且如Section 41.11.1Section 41.11.2中详细讨论的,查询计划会被缓存以用于可能的重用。

FOR-IN-EXECUTE语句是在行上迭代的另一种方式:

[ <<label>> ]
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
    statements
END LOOP [ label ];

这个示例类似前面的形式,只不过源查询被指定为一个字符串表达式,在每次进入FOR循环时都会计算它并且重新计划。这允许程序员在一个预先计划好了的命令的速度和一个动态命令的灵活性之间进行选择,就像一个纯EXECUTE语句那样。在使用EXECUTE时,可以通过USING将参数值插入到动态命令中。

另一种指定要对其结果迭代的查询的方式是将它声明为一个游标。这会在Section 41.7.4中描述。

41.6.7. 遍历数组 #

FOREACH循环很像FOR循环,但它不是遍历 SQL 查询返回的行,而是遍历数组值的元素。(一般来说,FOREACH用于遍历组合值表达式的组成部分;未来还可能加入遍历数组以外其他组合值的变体。)用于遍历数组的FOREACH语句如下:

[ <<label>> ]
FOREACH target [ SLICE number ] IN ARRAY expression LOOP
    statements
END LOOP [ label ];

如果没有写SLICE,或者指定的是SLICE 0,循环就会遍历计算expression所得数组的各个独立元素。target变量会依次接收每个元素值,并对每个元素执行一次循环体。下面是一个遍历整数数组元素的示例:

CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
DECLARE
  s int8 := 0;
  x int;
BEGIN
  FOREACH x IN ARRAY $1
  LOOP
    s := s + x;
  END LOOP;
  RETURN s;
END;
$$ LANGUAGE plpgsql;

元素会按存储顺序访问,而不管数组有多少维。尽管target通常只是单个变量,但在遍历组合值(记录)数组时,它也可以是一个变量列表。在这种情况下,每个数组元素都会按组合值的连续列给这些变量赋值。

SLICE为正值时,FOREACH遍历的是数组切片,而不是单个元素。SLICE值必须是一个不大于数组维数的整数常量。target变量必须是数组,并且它会依次接收数组值的各个切片,其中每个切片都具有SLICE指定的维数。下面是一个遍历一维切片的示例:

CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
  x int[];
BEGIN
  FOREACH x SLICE 1 IN ARRAY $1
  LOOP
    RAISE NOTICE 'row = %', x;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);

NOTICE:  row = {1,2,3}
NOTICE:  row = {4,5,6}
NOTICE:  row = {7,8,9}
NOTICE:  row = {10,11,12}

41.6.8. 捕获错误 #

默认情况下,PL/pgSQL函数中发生的任何错误都会中止函数及其外围事务的执行。你可以使用带有EXCEPTION子句的BEGIN块来捕获错误并从中恢复。其语法是在普通BEGIN块语法上的扩展:

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
EXCEPTION
    WHEN condition [ OR condition ... ] THEN
        handler_statements
    [ WHEN condition [ OR condition ... ] THEN
          handler_statements
      ... ]
END;

如果没有发生错误,这种形式的块只是简单地执行所有statements, 并且接着控制转到END之后的下一个语句。但是如果在statements内发生了一个错误,则会放弃对statements的进一步处理,然后控制会转到EXCEPTION列表。系统会在列表中寻找匹配所发生错误的第一个condition。如果找到一个匹配,则执行对应的handler_statements,并且接着把控制转到END之后的下一个语句。如果没有找到匹配,该错误就会传播出去,就好像根本没有EXCEPTION一样:错误可以被一个带有EXCEPTION的闭合块捕捉,如果没有EXCEPTION则中止该函数的处理。

condition的名字可以是Appendix A中显示的任何名字。一个分类名匹配其中所有的错误。特殊的条件名OTHERS匹配除了QUERY_CANCELEDASSERT_FAILURE之外的所有错误类型(虽然通常并不明智,还是可以用名字捕获这两种错误类型)。条件名是大小写无关的。一个错误条件也可以通过SQLSTATE代码指定,例如以下是等价的:

WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...

如果在选中的handler_statements内发生了新的错误,那么它不能被这个EXCEPTION子句捕获,而是被传播出去。一个外层的EXCEPTION子句可以捕获它。

当一个错误被EXCEPTION捕获时,PL/pgSQL函数的局部变量会保持错误发生时的值,但是该块中所有对持久数据库状态的改变都会被回滚。例如,考虑这个片段:

INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
BEGIN
    UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
    x := x + 1;
    y := x / 0;
EXCEPTION
    WHEN division_by_zero THEN
        RAISE NOTICE 'caught division_by_zero';
        RETURN x;
END;

当控制到达对y赋值的地方时,它会带着一个division_by_zero错误失败。这个错误将被EXCEPTION子句捕获。而在RETURN语句中返回的值将是x增加过后的值。但是UPDATE命令的效果将已经被回滚。不过,在该块之前的INSERT将不会被回滚,因此最终的结果是数据库包含Tom Jones但不包含Joe Jones

Tip

进入和退出一个包含EXCEPTION子句的块要比不包含EXCEPTION的块开销大的多。因此,只在必要的时候使用EXCEPTION

Example 41.2. UPDATE/INSERT的异常

这个示例使用异常处理来酌情执行UPDATEINSERT。我们推荐应用使用带有 ON CONFLICT DO UPDATEINSERT 而不是真正使用这种模式。下面的示例主要是为了展示 PL/pgSQL如何控制流程:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- 先尝试更新该键
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- 不在这里,那么尝试插入该键
        -- 如果其他某人并发地插入同一个键,
        -- 我们可能得到一个唯一键失败
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- 什么也不做,并且循环再次尝试 UPDATE
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

这段代码假定unique_violation错误是INSERT造成,并且不是由该表上一个触发器函数中的INSERT导致。如果在该表上有多于一个唯一索引,也可能会发生不正确的行为,因为不管哪个索引导致该错误它都将重试该操作。通过接下来要讨论的特性来检查被捕获的错误是否为所预期的会更安全。


41.6.8.1. 获取错误信息 #

异常处理器经常需要识别所发生的具体错误。有两种方法可以获取PL/pgSQL中当前异常的信息:特殊变量和GET STACKED DIAGNOSTICS命令。

在一个异常处理器内,特殊变量SQLSTATE包含了对应于被抛出异常的错误代码(可能的错误代码列表见Table A.1)。特殊变量SQLERRM包含与该异常相关的错误消息。这些变量在异常处理器外是未定义的。

在一个异常处理器内,我们也可以用GET STACKED DIAGNOSTICS命令检索有关当前异常的信息,该命令的形式为:

GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

每个item是一个关键词,它标识一个被赋予给指定变量(应该具有接收该值的正确数据类型)的状态值。Table 41.2中显示了当前可用的状态项。

Table 41.2. 错误诊断项

名称 类型 描述
RETURNED_SQLSTATE text 该异常的 SQLSTATE 错误代码
COLUMN_NAME text 与异常相关的列名
CONSTRAINT_NAME text 与异常相关的约束名
PG_DATATYPE_NAME text 与异常相关的数据类型名
MESSAGE_TEXT text 该异常的主要消息的文本
TABLE_NAME text 与异常相关的表名
SCHEMA_NAME text 与异常相关的模式名
PG_EXCEPTION_DETAIL text 该异常的详细消息文本(如果有)
PG_EXCEPTION_HINT text 该异常的提示消息文本(如果有)
PG_EXCEPTION_CONTEXT text 描述产生异常时调用栈的文本行(见Section 41.6.9

如果异常没有为一个项设置值,将返回一个空字符串。

这里是一个示例:

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- 某些可能导致异常的处理
  ...
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                          text_var2 = PG_EXCEPTION_DETAIL,
                          text_var3 = PG_EXCEPTION_HINT;
END;

41.6.9. 获得执行位置信息 #

GET DIAGNOSTICS(之前在Section 41.5.5中描述)命令检索有关当前执行状态的信息(反之上文讨论的GET STACKED DIAGNOSTICS命令会把有关执行状态的信息报告成一个以前的错误)。它的PG_CONTEXT状态项可用于标识当前执行位置。状态项PG_CONTEXT将返回一个文本字符串,其中有描述该调用栈的多行文本。第一行会指向当前函数以及当前正在执行GET DIAGNOSTICS的命令。第二行及其后的行表示调用栈中更上层的调用函数。例如:

CREATE OR REPLACE FUNCTION outer_func() RETURNS integer AS $$
BEGIN
  RETURN inner_func();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION inner_func() RETURNS integer AS $$
DECLARE
  stack text;
BEGIN
  GET DIAGNOSTICS stack = PG_CONTEXT;
  RAISE NOTICE E'--- Call Stack ---\n%', stack;
  RETURN 1;
END;
$$ LANGUAGE plpgsql;

SELECT outer_func();

NOTICE:  --- Call Stack ---
PL/pgSQL function inner_func() line 5 at GET DIAGNOSTICS
PL/pgSQL function outer_func() line 3 at RETURN
CONTEXT:  PL/pgSQL function outer_func() line 3 at RETURN
 outer_func
 ------------
           1
(1 row)

GET STACKED DIAGNOSTICS ... PG_EXCEPTION_CONTEXT返回同类的栈跟踪,但是它描述检测到错误的位置而不是当前位置。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。