支持的版本:当前 (17) / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本: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

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 语句。

一些示例

-- functions returning a scalar type
RETURN 1 + 2;
RETURN scalar_var;

-- functions returning a composite type
RETURN composite_type_var;
RETURN (1, 2, 'three'::text);  -- must cast columns to correct types

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 (当有多个输出参数时),或 SETOF sometype (当只有一个类型为 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
        -- can do some processing here
        RETURN NEXT r; -- return current row of 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);

    -- Since execution is not finished, we can check whether rows were returned
    -- and raise exception if not.
    IF NOT FOUND THEN
        RAISE EXCEPTION 'No flight at %.', $1;
    END IF;

    RETURN;
 END;
$BODY$
LANGUAGE plpgsql;

-- Returns available flights or raises exception if there are no
-- available flights.
SELECT * FROM get_available_flightid(CURRENT_DATE);

注意

如上所述,当前 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
    -- hmm, the only other possibility is that number is null
    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;

除了到达省略的 ELSE 子句会导致错误而不是不执行任何操作的规则外,这种形式的 CASEIF-THEN-ELSIF 完全等效。

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 之前的版本的一个更改,它允许无标签的 EXITBEGIN 块匹配。)

示例

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;

<<ablock>>
BEGIN
    -- some computations
    IF stocks > 100000 THEN
        EXIT ablock;  -- causes exit from the BEGIN block
    END IF;
    -- computations here will be skipped when stocks > 100000
END;

41.6.5.3. CONTINUE #

CONTINUE [ label ] [ WHEN boolean-expression ];

如果未给定 label,则开始最内层循环的下一次迭代。也就是说,跳过循环体中剩余的所有语句,并将控制权返回给循环控制表达式(如果有),以确定是否需要另一次循环迭代。如果存在 label,则它指定将继续执行的循环的标签。

如果指定了 WHEN,则仅当 boolean-expression 为真时,才开始循环的下一次迭代。否则,控制权传递给 CONTINUE 之后的语句。

CONTINUE 可以与所有类型的循环一起使用;它不限于与无条件循环一起使用。

示例

LOOP
    -- some computations
    EXIT WHEN count > 100;
    CONTINUE WHEN count < 50;
    -- some computations for count IN [50 .. 100]
END LOOP;

41.6.5.4. WHILE #

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

只要 boolean-expression 的计算结果为 true,WHILE 语句就会重复执行一系列语句。在每次进入循环体之前都会检查该表达式。

例如

WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- some computations here
END LOOP;

WHILE NOT done LOOP
    -- some computations here
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
    -- i will take on the values 1,2,3,4,5,6,7,8,9,10 within the loop
END LOOP;

FOR i IN REVERSE 10..1 LOOP
    -- i will take on the values 10,9,8,7,6,5,4,3,2,1 within the loop
END LOOP;

FOR i IN REVERSE 10..1 BY 2 LOOP
    -- i will take on the values 10,8,6,4,2 within the loop
END LOOP;

如果下限大于上限(或在 REVERSE 情况下小于上限),则根本不执行循环体。不会引发错误。

如果 label 附加到 FOR 循环,则可以使用限定名称(使用该 label)来引用整数循环变量。

41.6.6. 循环访问查询结果 #

使用不同类型的 FOR 循环,您可以迭代查询的结果并相应地操作该数据。语法如下

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

target 是一个记录变量、行变量或以逗号分隔的标量变量列表。会依次为 target 分配 query 产生的每一行,并为每一行执行循环体。这是一个示例

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)也可以使用。

按照 第 41.11.1 节第 41.11.2 节中详细讨论的那样,PL/pgSQL 变量将替换为查询参数,并且查询计划将缓存以供可能重复使用。

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

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

这与前面的形式类似,不同之处在于,源查询被指定为字符串表达式,该表达式在每次进入 FOR 循环时都会被计算和重新计划。这允许程序员选择预先计划的查询的速度或动态查询的灵活性,就像使用普通的 EXECUTE 语句一样。与 EXECUTE 一样,可以使用 USING 将参数值插入到动态命令中。

指定应该迭代结果的查询的另一种方法是将其声明为游标。这在 第 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 的封闭块捕获,如果不存在这样的块,则会中止函数的处理。

condition 名称可以是 附录 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

提示

包含 EXCEPTION 子句的块的进入和退出成本比没有该子句的块要高得多。因此,请不要在没有必要时使用 EXCEPTION

示例 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
        -- first try to update the key
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- Do nothing, and loop to try the UPDATE again.
        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 包含与引发的异常对应的错误代码(有关可能的错误代码列表,请参阅 表 A.1)。特殊变量 SQLERRM 包含与异常关联的错误消息。这些变量在异常处理程序之外是未定义的。

在异常处理程序中,还可以使用 GET STACKED DIAGNOSTICS 命令检索有关当前异常的信息,该命令的格式如下:

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

每个 item 都是一个关键字,用于标识要分配给指定 variable 的状态值(该变量应具有接收该值的正确数据类型)。当前可用的状态项在 表 41.2 中显示。

表 41.2. 错误诊断项

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

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

这是一个示例

DECLARE
  text_var1 text;
  text_var2 text;
  text_var3 text;
BEGIN
  -- some processing which might cause an exception
  ...
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. 获取执行位置信息 #

先前在 第 41.5.5 节中描述的 GET DIAGNOSTICS 命令,检索有关当前执行状态的信息(而上面讨论的 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 返回相同类型的堆栈跟踪,但描述了检测到错误的位置,而不是当前位置。

提交更正

如果您在文档中发现任何不正确的内容、与您使用特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。