企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 40.6\. 控制结构 控制结构可能是PL/pgSQL中最有用的(以及最重要)的部分了。 利用PL/pgSQL的控制结构, 你可以以非常灵活而且强大的方法操纵PostgreSQL的数据。 ## 40.6.1\. 从函数返回 有两个命令可以用来从函数中返回数据:`RETURN`和 `RETURN NEXT`。 ### 40.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); -- must cast columns to correct types ``` ### 40.6.1.2\. `RETURN NEXT`和`RETURN QUERY` ``` RETURN NEXT _expression_; RETURN QUERY _query_; RETURN QUERY EXECUTE _command-string_ [ USING `_expression_` [, ... ] ]; ``` 如果一个PL/pgSQL函数声明为返回`SETOF` `_sometype_`, 那么遵循的过程则略有不同。 在这种情况下,要返回的独立项是在`RETURN NEXT`或者 `RETURN QUERY`命令里声明的, 然后最后有一个不带参数的`RETURN`命令用于告诉这个函数已经完成执行了。 `RETURN NEXT`可以用于标量和复合数据类型;对于复合类型, 将返回一个完整的结果"table"。 `RETURN QUERY`命令将一条查询的结果追加到一个函数的结果集中。 `RETURN NEXT`和`RETURN QUERY`在单一集合返回 函数中自由混合,在这种情况下,结果将被级联。 `RETURN NEXT`和`RETURN QUERY`实际上不会从函数中返回, 它们是将零或者多个行追加到函数的结果集中。 然后继续执行PL/pgSQL函数里的下一条语句。 随着后继的`RETURN NEXT`或者`RETURN 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; -- 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); -- 由于没有完成执行,我们可以检查行是否返回并且如果没有则抛出异常。 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 NEXT`和`RETURN QUERY` 实现在从函数返回之前把整个结果集都保存起来, 就像上面描述的那样。这意味着如果一个PL/pgSQL函数生成一个非常大的结果集, 性能可能会很差:数据将被写到磁盘上以避免内存耗尽, 但是函数在完成整个结果集的生成之前不会退出。 将来的PL/pgSQL版本可能会允许用户定义没有这样限制的返回集合的函数。 目前,数据开始向磁盘里写的时刻是由配置变量[work_mem](#calibre_link-1374)控制的。 拥有足够内存的管理员如果想在内存里存储更大的结果集, 则可以考虑把这个参数增大一些。 ## 40.6.2\. 条件 `IF`和`CASE`语句让你可以根据某种条件执行命令。 PL/pgSQL有三种形式的`IF`: * `IF ... THEN` * `IF ... THEN ... ELSE` * `IF ... THEN ... ELSIF ... THEN ... ELSE` 以及两种形式的`CASE`: * `CASE ... WHEN ... THEN ... ELSE ... END CASE` * `CASE WHEN ... THEN ... ELSE ... END CASE` ### 40.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; ``` ### 40.6.2.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; ``` ### 40.6.2.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`繁琐。 ### 40.6.2.4\. 简单`CASE` ``` CASE _search-expression_ WHEN _expression_ [, `_expression_` [ ... ]] THEN _statements_ [ WHEN `_expression_` [, `_expression_` [ ... ]] THEN `_statements_` ... ] [ ELSE `_statements_` ] END CASE; ``` `CASE`简单的形式提供基于操作数平等的条件执行。`_search-expression_`被评价并且 先后比较`WHEN`子句中的每个`_表达式_`。 如果找到匹配,那么相应的`_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; ``` ### 40.6.2.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; ``` `CASE`这种形式完全等价于 `IF-THEN-ELSIF`,除了达到忽略错误中的`ELSE`子句结果而不是什么都不做的规则。 ## 40.6.3\. 简单循环 使用`LOOP`, `EXIT`,`CONTINUE`, `WHILE`, `FOR`和`FOREACH`语句, 可以控制PL/pgSQL函数重复一系列命令。 ### 40.6.3.1\. `循环` ``` [ <<`_label_`>> ] LOOP _statements_ END LOOP [ `_label_` ]; ``` `LOOP`定义一个无条件的循环,无限循环, 直到由`EXIT`或者 `RETURN`语句终止。 可选的`_label_`可以由`EXIT` 和`CONTINUE`语句使用, 用于在嵌套循环中声明应该应用于哪一层循环。 ### 40.6.3.2\. `退出` ``` 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; -- exit loop 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; ``` ### 40.6.3.3\. `CONTINUE` ``` CONTINUE [ `_label_` ] [ WHEN `_boolean-expression_` ]; ``` 如果没有给出`_label_`,那么就开始最内层循环的下一次执行。 也就是说,控制传递回给循环控制表达式(如果有),然后重新计算循环体。 如果出现了`_label_`,它声明即将继续执行的循环的标签。 如果声明了`WHEN`,那么循环的下一次执行只有 在`_boolean-expression_`为真的情况下才进行。 否则,控制传递给`CONTINUE`后面的语句。 `CONTINUE`可以用于所有类型的循环;它并不仅仅限于无条件循环。 例如: ``` LOOP -- 一些计算 EXIT WHEN count > 100; CONTINUE WHEN count < 50; -- 在[50 .. 100]内的计算 END LOOP; ``` ### 40.6.3.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; ``` ### 40.6.3.5\. `FOR` (Integer 变量) ``` [ <<`_label_`>> ] FOR _name_ IN [ REVERSE ] _expression_ .. _expression_ [ BY `_expression_` ] LOOP _statements_ END LOOP [ `_label_` ]; ``` 这种形式的`FOR`对一定范围的整数进行迭代的循环。 变量`_name_`会自动定义为`BY`类型并且只在循环里存在 (任何该变量名的现存定义在此循环内都将被忽略)。 给出范围上下界的两个表达式在进入循环的时候计算一次。 `BY`子句指定迭代步长(缺省为 1), 但如果声明了`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_`引用适当名称。 ## 40.6.4\. 遍历命令结果 使用不同类型的`FOR`循环, 你可以遍历一个命令的结果并且对其进行相应的操作。语法是: ``` [ <<`_label_`>> ] FOR _target_ IN _query_ LOOP _statements_ END LOOP [ `_label_` ]; ``` `_target_`是一个记录变量、 行变量、逗号分隔的标量变量列表`_target_` 被连续不断赋予所有来自`_query_`的行, 并且循环体将为每行执行一次。 下面是一个例子: ``` CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$ DECLARE mviews RECORD; BEGIN RAISE NOTICE 'Refreshing materialized views...'; FOR mviews IN SELECT * FROM cs_materialized_views ORDER BY sort_key LOOP -- 现在"mviews"里有了一条来自 cs_materialized_views 的记录 RAISE NOTICE 'Refreshing materialized view %s ...', 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; RAISE NOTICE 'Done refreshing materialized views.'; RETURN 1; END; $$ LANGUAGE plpgsql; ``` 如果循环是用一个`EXIT`语句终止的, 那么在循环之后你仍然可以访问最后赋值的行。 `FOR`语句中使用的这种`_query_`可以是任何返回行的SQL命令, 通常是`SELECT`,不过带有`RETURNING`子句的`INSERT`, `UPDATE` 或`DELETE`也是可以的, 一些诸如`EXPLAIN`之类的命令也可以。 PL/pgSQL变量代替查询文本,并且查询计划为了重新使用被缓存,正如 [Section 40.10.1](#calibre_link-1045)和[Section 40.10.2](#calibre_link-1573)。 `FOR-IN-EXECUTE`语句是遍历所有行的另外一种方法: ``` [ <<`_label_`>> ] FOR _target_ IN EXECUTE _text_expression_ [ USING `_expression_` [, ... ] ] LOOP _statements_ END LOOP [ `_label_` ]; ``` 这个例子类似前面的形式,只不过源查询语句声明为了一个字符串表达式, 这样它在每次进入`FOR`循环的时候都会重新计算和生成执行计划。 这样就允许程序员在一个预先规划好了的命令所获得的速度和一个动态命令所获得的灵活性 (就像一个简单的`EXECUTE`语句那样)之间进行选择。 当使用`EXECUTE`时, 可以通过`USING`将参数值插入到动态命令中。 对于一个需要将结果迭代的查询, 另外一个声明的方法是将它定义为游标(cursor), 可参阅[Section 40.7.4](#calibre_link-1580)。 ## 40.6.5\. 遍历数组 `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} ``` ## 40.6.6\. 捕获错误 缺省时,一个在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_`的名字可以是[Appendix A](#calibre_link-120)里显示的任何名字。 一个范畴名匹配任意该范畴里的错误。 特殊的条件名`OTHERS`匹配除了`QUERY_CANCELED`之外的所有错误类型。 可以用名字捕获`QUERY_CANCELED`,不过通常是不明智的。 条件名是大小写无关的。同时也可以通过`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`。 **Example 40-2\. `UPDATE`/`INSERT`异常** 这个例子根据使用异常处理器执行恰当的`UPDATE`或者`INSERT`。 ``` CREATE TABLE db (a INT PRIMARY KEY, b TEXT); CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS $$ BEGIN LOOP -- 第一次尝试更新key UPDATE db SET b = data WHERE a = key; IF found THEN RETURN; END IF; -- 不存在,所以尝试插入key,如果其他人同时插入相同的key,我们可能得到唯一key失败。 BEGIN INSERT INTO db(a,b) VALUES (key, data); RETURN; EXCEPTION WHEN unique_violation THEN -- 什么也不做,并且循环尝试再次更新。 END; END LOOP; END; $$ LANGUAGE plpgsql; SELECT merge_db(1, 'david'); SELECT merge_db(1, 'dennis'); ``` 这个代码假设通过`INSERT`不是说表上触发器函数中的`INSERT` 产生`unique_violation`错误,如果表上有超过一个以上的唯一索引,它可能行为不端, 因为将重试操作不论哪个索引产生错误。 可以通过特性讨论下一步检查捕获的错误是预期的来获取更高的安全性。 ### 40.6.6.1\. 获得有关错误的信息 异常处理程序经常需要确定发生的具体错误。有两种方法来获得 当前PL/pgSQL异常: 特殊变量和`GET STACKED DIAGNOSTICS`命令 的有关信息。 在一个异常处理程序中,特殊变量 `SQLSTATE`包含相当于发生异常的错误代码 (参考[Table A-1](#calibre_link-1092)获得可能错误代码列)。 特殊变量`SQLERRM`包含与异常有关的错误消息。 这些变量是在异常处理外未被定义的。 在一个异常处理程序中,也可以检索关于使用`GET STACKED DIAGNOSTICS`命令的当前异常信息,形成了: ``` GET STACKED DIAGNOSTICS _variable_ = _item_ [ , ... ]; ``` 每个`_item_`是识别被分配到指定变量的状态值(应该是接收它的正确数据类型)的一个关键字。 目前可用的状态显示在[Table 40-1](#calibre_link-1581)中。 **Table 40-1\. 错误诊断值** | 名字 | 类型 | 描述 | | --- | --- | --- | | `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 | 描述调用堆栈的文本线程 | 如果异常没有设置项值,则返回空字符串。 例子: ``` 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; ```