控制结构可能是 PL/pgSQL 最有用(也最重要)的部分。借助 PL/pgSQL 的控制结构,你可以以非常灵活而强大的方式操纵 PostgreSQL 数据。
有两个命令可用于从函数返回数据:RETURN
和 RETURN NEXT。
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
RETURN NEXTexpression; RETURN QUERYquery; RETURN QUERY EXECUTEcommand-string[ USINGexpression[, ... ] ];
当 PL/pgSQL 函数被声明为返回
SETOF 时,
要遵循的过程略有不同。在这种情况下,要返回的各个项
由一系列 sometypeRETURN NEXT 或
RETURN QUERY 命令指定,
然后用一条不带参数的最终 RETURN 命令
表明函数已执行完毕。RETURN NEXT
既可以用于标量数据类型,也可以用于复合数据类型;
对于复合结果类型,将返回一个完整的“表”的结果。
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
-- 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 NEXT 和
RETURN QUERY 的当前实现会在从函数返回之前
存储整个结果集。这意味着,如果 PL/pgSQL
函数产生一个非常大的结果集,性能可能会很差:
为避免内存耗尽,数据会被写到磁盘上,但在整个结果集生成完毕之前,
函数本身不会返回。PL/pgSQL
的未来版本可能允许用户定义没有这一限制的集合返回函数。
目前,数据开始写入磁盘的时机由
work_mem
配置变量控制。有足够内存把更大的结果集保存在内存中的管理员
可以考虑调大这个参数。
过程没有返回值。因此过程可以不以 RETURN
语句结束。如果你想用 RETURN 语句提前退出代码,
只写不带表达式的 RETURN。
如果过程有输出参数,输出参数变量的最终值将被返回给调用者。
PL/pgSQL 函数、过程或
DO 块可以使用 CALL
调用一个过程。输出参数的处理方式与 CALL
在普通 SQL 中的工作方式不同。过程的每个
OUT 或 INOUT 参数
都必须对应 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;
$$;
对应输出参数的变量可以是简单变量,也可以是复合类型变量的字段。 目前,它不能是数组的元素。
IF 和 CASE 语句让你可以
根据特定条件执行备选的命令。
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
IFboolean-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;
IFboolean-expressionTHENstatementsELSEstatementsEND 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;
IFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements[ ELSIFboolean-expressionTHENstatements... ] ] [ ELSEstatements] 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
繁琐得多。
CASEsearch-expressionWHENexpression[,expression[ ... ]] THENstatements[ WHENexpression[,expression[ ... ]] THENstatements... ] [ ELSEstatements] 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;
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 子句
会导致错误,而不是什么都不做。
使用 LOOP、EXIT、
CONTINUE、WHILE、FOR
和 FOREACH 语句,
你可以让你的 PL/pgSQL 函数
重复执行一系列命令。
[ <<label>> ] LOOPstatementsEND LOOP [label];
LOOP 定义一个无条件循环,它会无限重复,
直到被 EXIT 或 RETURN
语句终止。可选的 label
可以被嵌套循环中的 EXIT 和
CONTINUE 语句用来指明这些语句针对哪个循环。
EXIT [label] [ WHENboolean-expression];
如果没有给出 label,
则终止最内层循环,并接着执行 END
LOOP 之后的语句。如果给出了
label,它必须是当前层或某个外层的
嵌套循环或块的标签。此时被指名的循环或块会被终止,
控制流继续进行到该循环/块对应 END 之后的语句。
如果指定了 WHEN,则只有当
boolean-expression 为真时才会退出循环。
否则,控制流传递到 EXIT 之后的语句。
EXIT 可以用于所有类型的循环;
它并不仅限于无条件循环。
当与 BEGIN 块一起使用时,
EXIT 会把控制流传递到块结束之后的下一条语句。
注意,为此目的必须使用标签;不带标签的
EXIT 永远不会被认为匹配一个
BEGIN 块。(与 8.4 之前的
PostgreSQL 版本不同,
那些版本允许不带标签的 EXIT 匹配一个
BEGIN 块。)
示例:
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;
CONTINUE [label] [ WHENboolean-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;
[ <<label>> ] WHILEboolean-expressionLOOPstatementsEND LOOP [label];
WHILE 语句会在
boolean-expression
求值为真的情况下重复一系列语句。
该表达式会在每次进入循环体之前被检查。
例如:
WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
-- some computations here
END LOOP;
WHILE NOT done LOOP
-- some computations here
END LOOP;
[ <<label>> ] FORnameIN [ REVERSE ]expression..expression[ BYexpression] LOOPstatementsEND 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 的情况下
是小于),则循环体完全不会被执行。不会引发任何错误。
如果 FOR 循环上附加了
label,则整数循环变量
可以用该 label
以限定名的形式引用。
使用另一种类型的 FOR 循环,
你可以遍历查询的结果并对数据进行相应处理。其语法是:
[ <<label>> ] FORtargetINqueryLOOPstatementsEND 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 子句的
INSERT、UPDATE、
DELETE 或 MERGE。
某些实用命令(例如 EXPLAIN)也可以使用。
PL/pgSQL 变量会被替换为查询参数, 查询计划会被缓存以备重用,如 Section 41.11.1 和 Section 41.11.2 中详细讨论的那样。
FOR-IN-EXECUTE 语句是遍历行的另一种方式:
[ <<label>> ] FORtargetIN EXECUTEtext_expression[ USINGexpression[, ... ] ] LOOPstatementsEND LOOP [label];
它与前一种形式相似,区别在于源查询被指定为字符串表达式,
该表达式在每次进入 FOR 循环时被求值并重新规划。
这允许程序员像使用普通 EXECUTE 语句那样,
在预规划查询的速度与动态查询的灵活性之间做出选择。
与 EXECUTE 一样,
参数值可以通过 USING 插入动态命令中。
指定要遍历其结果的查询的另一种方式是把它声明为游标。 这在 Section 41.7.4 中描述。
FOREACH 循环与 FOR 循环非常相似,
但它遍历的不是 SQL 查询返回的行,而是数组值的元素。
(总体而言,FOREACH 旨在遍历复合值表达式的组成部分;
用于遍历数组之外的其他复合值的变体将来可能会加入。)
遍历数组的 FOREACH 语句是:
[ <<label>> ] FOREACHtarget[ SLICEnumber] IN ARRAYexpressionLOOPstatementsEND 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}
默认情况下,PL/pgSQL 函数中发生的任何错误
都会中止该函数以及外层事务的执行。你可以通过使用带
EXCEPTION 子句的 BEGIN 块
来捕获错误并从中恢复。其语法是对 BEGIN 块
常规语法的扩展:
[ <<label>> ] [ DECLAREdeclarations] BEGINstatementsEXCEPTION WHENcondition[ ORcondition... ] THENhandler_statements[ WHENcondition[ ORcondition... ] THENhandler_statements... ] END;
如果没有发生错误,这种形式的块只是执行所有
statements,然后控制流传递到
END 之后的下一条语句。但如果
statements 中发生了错误,
对它们的进一步处理会被放弃,控制流传递到
EXCEPTION 列表。该列表会被搜索,
找到第一个与所发生错误匹配的 condition。
如果找到匹配,则执行相应的
handler_statements,
然后控制流传递到 END 之后的下一条语句。
如果没有找到匹配,错误会向外传播,就好像
EXCEPTION 子句根本不存在一样:
该错误可以被带 EXCEPTION 的外层块捕获;
如果没有这样的块,它将中止函数的处理。
condition 的名称可以是
Appendix A 中列出的任何名称。
类别名匹配该类别中的任何错误。特殊的条件名
OTHERS 匹配除
QUERY_CANCELED 和 ASSERT_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。
Example 41.2. UPDATE 与 INSERT 中的异常处理
这个例子使用异常处理来酌情执行 UPDATE
或 INSERT。建议应用使用带
ON CONFLICT DO UPDATE 的
INSERT,而不是真正使用这种模式。
这个例子主要用于说明 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 之类的操作引起的。
如果表上有多个唯一索引,它也可能行为不正确,
因为无论哪个索引引发了错误,它都会重试该操作。
使用接下来讨论的特性来检查被捕获的错误是否正是预期的那个,
可以获得更高的安全性。
异常处理程序经常需要识别所发生的具体错误。
在 PL/pgSQL 中获取当前异常信息
有两种方式:特殊变量和 GET STACKED DIAGNOSTICS
命令。
在异常处理程序内部,特殊变量
SQLSTATE 包含与被引发异常对应的错误代码
(可能的错误代码列表见 Table A.1)。
特殊变量 SQLERRM 包含与该异常关联的错误消息。
这些变量在异常处理程序之外是未定义的。
在异常处理程序内部,还可以使用
GET STACKED DIAGNOSTICS 命令检索
当前异常的信息,其形式为:
GET STACKED DIAGNOSTICSvariable{ = | := }item[ , ... ];
每个 item 是一个关键字,
标识要赋给指定 variable
(其数据类型应适合接收该值)的状态值。
当前可用的状态项见
Table 41.2。
Table 41.2. 错误诊断项说明
| Name | Type | Description |
|---|---|---|
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
-- 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;
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 ---
%', 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
返回同类的栈追踪,但描述的是检测到错误的位置,
而不是当前位置。