在本节及后续章节中,我们将描述 PL/pgSQL 明确支持的所有语句类型。任何未被识别为这些语句类型的语句都被假定为 SQL 命令,并按照 第 41.5.2 节 中的描述发送到主数据库引擎执行。
将值赋给 PL/pgSQL 变量的写法如下:
variable
{ := | = }expression
;
如前所述,此语句中的表达式通过 SQL SELECT
命令计算,该命令发送到主数据库引擎。表达式必须产生单个值(如果变量是行或记录变量,则可能是一个行值)。目标变量可以是简单变量(可选择性地用块名限定)、行或记录目标的字段,或者数组目标的元素或切片。等号(=
)可以替代 PL/SQL 兼容的 :=
。
如果表达式的结果数据类型与变量的数据类型不匹配,该值将通过赋值转换(参见 第 10.4 节)进行强制转换。如果涉及的数据类型对之间没有已知的赋值转换,PL/pgSQL 解释器将尝试以文本形式转换结果值,即通过应用结果类型的输出函数,然后是变量类型的输入函数。请注意,如果结果值的字符串形式对输入函数不可接受,这可能会导致由输入函数生成的运行时错误。
示例
tax := subtotal * 0.06; my_record.user_id := 20; my_array[j] := 20; my_array[1:3] := array[1,2,3]; complex_array[n].realpart = 12.3;
通常,任何不返回行的 SQL 命令都可以通过编写该命令在 PL/pgSQL 函数中执行。例如,您可以编写以下内容来创建和填充表:
CREATE TABLE mytable (id int primary key, data text); INSERT INTO mytable VALUES (1,'one'), (2,'two');
如果命令返回行(例如 SELECT
,或带 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
),则有两种方法可以继续。当命令最多返回一行,或者您只关心输出的第一行时,像往常一样编写命令,但添加 INTO
子句以捕获输出,如 第 41.5.3 节中所述。要处理所有输出行,请将该命令写为 FOR
循环的数据源,如 第 41.6.6 节中所述。
通常,仅执行静态定义的 SQL 命令是不够的。通常您会希望一个命令使用不同的数据值,甚至在根本上有所不同,例如在不同时间使用不同的表名。同样,取决于具体情况,有两种方法可以继续。
PL/pgSQL 变量值可以自动插入到可优化 SQL 命令中,这些命令包括 SELECT
、INSERT
、UPDATE
、DELETE
、MERGE
以及包含其中之一的某些实用命令,例如 EXPLAIN
和 CREATE TABLE ... AS SELECT
。在这些命令中,命令文本中出现的任何 PL/pgSQL 变量名都将替换为查询参数,然后在运行时提供变量的当前值作为参数值。这与之前为表达式描述的处理方式完全相同;有关详细信息,请参见 第 41.11.1 节。
以这种方式执行可优化 SQL 命令时,PL/pgSQL 可能会缓存并重新使用该命令的执行计划,如 第 41.11.2 节中所述。
不可优化 SQL 命令(也称为实用命令)无法接受查询参数。因此,PL/pgSQL 变量的自动替换在这种命令中不起作用。要在从 PL/pgSQL 执行的实用命令中包含非常量文本,您必须将实用命令构建为一个字符串,然后 EXECUTE
它,如 第 41.5.4 节中所述。
EXECUTE
也必须用于您想以数据值提供以外的其他方式修改命令的情况,例如更改表名。
有时评估表达式或 SELECT
查询但丢弃结果很有用,例如在调用具有副作用但没有有用结果值的函数时。要在 PL/pgSQL 中执行此操作,请使用 PERFORM
语句:
PERFORM query
;
这将执行 query
并丢弃结果。以编写 SQL SELECT
命令相同的方式编写 query
,但将关键字 SELECT
替换为 PERFORM
。对于 WITH
查询,请使用 PERFORM
,然后将查询括在括号中。(在这种情况下,查询只能返回一行。)PL/pgSQL 变量将像上面描述的那样替换到查询中,并且计划以相同的方式缓存。此外,特殊变量 FOUND
将设置为 true(如果查询生成至少一行)或 false(如果未生成行)(参见 第 41.5.5 节)。
人们可能期望直接编写 SELECT
会完成此结果,但目前唯一接受的方式是 PERFORM
。可能返回行的 SQL 命令(例如 SELECT
)将被拒绝为错误,除非它具有 INTO
子句,如下一节所述。
一个例子
PERFORM create_mv('cs_session_page_requests_mv', my_query);
产生单行(可能包含多列)的 SQL 命令的结果可以分配给记录变量、行类型变量或标量变量列表。这可以通过编写基本 SQL 命令并添加 INTO
子句来完成。例如:
SELECTselect_expressions
INTO [STRICT]target
FROM ...; INSERT ... RETURNINGexpressions
INTO [STRICT]target
; UPDATE ... RETURNINGexpressions
INTO [STRICT]target
; DELETE ... RETURNINGexpressions
INTO [STRICT]target
; MERGE ... RETURNINGexpressions
INTO [STRICT]target
;
其中 target
可以是记录变量、行变量,或者逗号分隔的简单变量和记录/行字段列表。PL/pgSQL 变量将像上面描述的那样替换到命令的其余部分(即 INTO
子句之外的所有内容),并且计划以相同的方式缓存。这适用于 SELECT
、带 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
以及返回行集的某些实用命令,例如 EXPLAIN
。除了 INTO
子句之外,SQL 命令与在 PL/pgSQL 外部编写的命令相同。
请注意,这种对带 INTO
的 SELECT
的解释与 PostgreSQL 的常规 SELECT INTO
命令完全不同,在后者中,INTO
目标是新创建的表。如果您想在 PL/pgSQL 函数中从 SELECT
结果创建表,请使用 CREATE TABLE ... AS SELECT
语法。
如果将行变量或变量列表用作目标,则命令的结果列必须在数量和数据类型上与目标的结构完全匹配,否则将发生运行时错误。当记录变量是目标时,它会自动配置为匹配命令结果列的行类型。
INTO
子句几乎可以出现在 SQL 命令的任何位置。习惯上,它要么紧挨着 SELECT
命令中的 select_expressions
列表之前,要么紧挨着之后,要么对于其他命令类型放在命令的末尾。建议您遵循此约定,以防 PL/pgSQL 解析器在未来版本中变得更严格。
如果未在 INTO
子句中指定 STRICT
,则 target
将设置为命令返回的第一行,或者如果命令未返回行,则设置为 NULL。(请注意,除非您使用了 ORDER BY
,否则“第一行”并不明确定义。)第一行之后的任何结果行都将被丢弃。您可以检查特殊变量 FOUND
(参见 第 41.5.5 节)以确定是否返回了行。
SELECT * INTO myrec FROM emp WHERE empname = myname; IF NOT FOUND THEN RAISE EXCEPTION 'employee % not found', myname; END IF;
如果指定了 STRICT
选项,则命令必须返回恰好一行,否则将报告运行时错误,分别是 NO_DATA_FOUND
(未返回行)或 TOO_MANY_ROWS
(返回多于一行)。如果您愿意,可以使用异常块来捕获错误,例如:
BEGIN SELECT * INTO STRICT myrec FROM emp WHERE empname = myname; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'employee % not found', myname; WHEN TOO_MANY_ROWS THEN RAISE EXCEPTION 'employee % not unique', myname; END;
成功执行带 STRICT
的命令总是将 FOUND
设置为 true。
对于带 RETURNING
的 INSERT
/UPDATE
/DELETE
/MERGE
,即使未指定 STRICT
,PL/pgSQL 也会报告多于一行返回的错误。这是因为没有像 ORDER BY
这样的选项来确定应该返回哪个受影响的行。
如果为函数启用了 print_strict_params
,那么当由于 STRICT
的要求未得到满足而引发错误时,错误消息的 DETAIL
部分将包含有关传递给命令的参数的信息。您可以通过设置 plpgsql.print_strict_params
来为所有函数更改 print_strict_params
设置,但这只会影响后续的函数编译。您还可以通过使用编译器选项(例如)来为每个函数启用它:
CREATE FUNCTION get_userid(username text) RETURNS int AS $$ #print_strict_params on DECLARE userid int; BEGIN SELECT users.userid INTO STRICT userid FROM users WHERE users.username = get_userid.username; RETURN userid; END; $$ LANGUAGE plpgsql;
失败时,此函数可能会产生类似以下的错误消息:
ERROR: query returned no rows DETAIL: parameters: username = 'nosuchuser' CONTEXT: PL/pgSQL function get_userid(text) line 6 at SQL statement
STRICT
选项与 Oracle PL/SQL 的 SELECT INTO
和相关语句的行为相匹配。
通常,您会在 PL/pgSQL 函数中生成动态命令,即那些每次执行时可能涉及不同表或不同数据类型的命令。在这种情况下,PL/pgSQL 对命令缓存计划的正常尝试(如 第 41.11.2 节中所述)将不起作用。为了处理这类问题,提供了 EXECUTE
语句:
EXECUTEcommand-string
[ INTO [STRICT]target
] [ USINGexpression
[, ... ] ];
其中 command-string
是一个产生包含要执行的命令的字符串(类型为 text
)的表达式。可选的 target
是一个记录变量、一个行变量,或者一个逗号分隔的简单变量和记录/行字段列表,命令的结果将存储在其中。可选的 USING
表达式提供要插入到命令中的值。
不会对计算出的命令字符串进行 PL/pgSQL 变量替换。任何必需的变量值都必须在构建命令字符串时插入;或者您可以使用如下所述的参数。
此外,通过 EXECUTE
执行的命令没有计划缓存。相反,该命令总是在每次运行语句时进行计划。因此,命令字符串可以在函数内部动态创建,以在不同的表和列上执行操作。
INTO
子句指定返回行的 SQL 命令的结果应分配到何处。如果提供了行变量或变量列表,它必须与命令结果的结构完全匹配;如果提供了记录变量,它将自动配置为匹配结果结构。如果返回多行,只有第一行会被分配到 INTO
变量。如果没有返回行,则会将 NULL 分配到 INTO
变量。如果未指定 INTO
子句,则命令结果将被丢弃。
如果指定了 STRICT
选项,则除非命令生成恰好一行,否则将报告错误。
命令字符串可以使用参数值,这些值在命令中通过 $1
、$2
等引用。这些符号指的是 USING
子句中提供的值。此方法通常优于将数据值插入到命令字符串作为文本:它避免了将值转换为文本再转换回来的运行时开销,并且由于无需引用或转义,因此更不容易受到 SQL 注入攻击。例如:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;
请注意,参数符号只能用于数据值——如果您想使用动态确定的表名或列名,您必须将它们插入到命令字符串文本中。例如,如果前面的查询需要在动态选择的表中完成,您可以这样做:
EXECUTE 'SELECT count(*) FROM ' || quote_ident(tabname) || ' WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;
一种更清晰的方法是使用 format()
的 %I
规范来插入带自动引用的表名或列名:
EXECUTE format('SELECT count(*) FROM %I ' 'WHERE inserted_by = $1 AND inserted <= $2', tabname) INTO c USING checked_user, checked_date;
(此示例依赖于 SQL 规则,即用换行符分隔的字符串文字会被隐式连接。)
参数符号的另一个限制是它们只能用于可优化 SQL 命令(SELECT
、INSERT
、UPDATE
、DELETE
、MERGE
以及包含其中之一的某些命令)。在其他语句类型(统称为实用语句)中,即使它们只是数据值,您也必须以文本形式插入值。
具有简单常量命令字符串和一些 USING
参数的 EXECUTE
,如上面的第一个示例所示,在功能上等同于直接在 PL/pgSQL 中编写命令并允许 PL/pgSQL 变量替换自动发生。重要的区别在于 EXECUTE
会在每次执行时重新计划命令,生成一个针对当前参数值特定的计划;而 PL/pgSQL 可能会创建通用计划并缓存它以供重用。在最佳计划强烈依赖于参数值的情况下,使用 EXECUTE
以积极确保不选择通用计划可能很有帮助。
目前 EXECUTE
不支持 SELECT INTO
;相反,请执行一个普通的 SELECT
命令,并将 INTO
指定为 EXECUTE
本身的一部分。
PL/pgSQL 的 EXECUTE
语句与 PostgreSQL 服务器支持的 EXECUTE
SQL 语句无关。服务器的 EXECUTE
语句不能直接在 PL/pgSQL 函数中使用(且不需要)。
示例 41.1. 动态查询中的引用值
处理动态命令时,您通常需要处理单引号的转义。在函数体中引用固定文本的推荐方法是美元引用。(如果您有不使用美元引用的旧代码,请参阅 第 41.12.1 节中的概述,这可以为您翻译 said 代码以采用更合理的方案节省一些工作。)
动态值需要仔细处理,因为它们可能包含引号字符。使用 format()
的示例(假设您正在对函数体进行美元引用,因此不需要双倍引用):
EXECUTE format('UPDATE tbl SET %I = $1 ' 'WHERE key = $2', colname) USING newvalue, keyvalue;
也可以直接调用引用函数:
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = ' || quote_literal(newvalue) || ' WHERE key = ' || quote_literal(keyvalue);
此示例演示了 quote_ident
和 quote_literal
函数(参见 第 9.4 节)的用法。为了安全起见,包含列或表标识符的表达式在插入动态查询之前应通过 quote_ident
。包含应成为构造命令中文字符串的值的表达式应通过 quote_literal
。这些函数会执行适当的步骤,将输入文本分别用双引号或单引号括起来,并正确转义嵌入的特殊字符。
由于 quote_literal
被标记为 STRICT
,因此在调用 null 参数时它将始终返回 null。在上面的示例中,如果 newvalue
或 keyvalue
为 null,则整个动态查询字符串将变为 null,从而导致 EXECUTE
出错。您可以通过使用 quote_nullable
函数来避免此问题,该函数的作用与 quote_literal
相同,除了在调用 null 参数时它会返回字符串 NULL
。例如:
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = ' || quote_nullable(newvalue) || ' WHERE key = ' || quote_nullable(keyvalue);
如果您处理可能为 null 的值,通常应使用 quote_nullable
而不是 quote_literal
。
一如既往,必须小心确保查询中的 null 值不会产生意外结果。例如,WHERE
子句:
'WHERE key = ' || quote_nullable(keyvalue)
如果 keyvalue
为 null,将永远不会成功,因为使用等号运算符 =
与 null 操作数的结果始终为 null。如果您希望 null 像普通键值一样工作,则需要将上述内容重写为:
'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)
(目前,IS NOT DISTINCT FROM
的处理效率远不如 =
,所以除非必须,否则不要这样做。有关 nulls 和 IS DISTINCT
的更多信息,请参见 第 9.2 节。)
请注意,美元引用仅适用于引用固定文本。尝试这样编写示例将是一个非常糟糕的主意:
EXECUTE 'UPDATE tbl SET ' || quote_ident(colname) || ' = $$' || newvalue || '$$ WHERE key = ' || quote_literal(keyvalue);
因为它会在 newvalue
的内容碰巧包含 $$
时中断。同样的问题也会影响您选择的任何其他美元引用分隔符。因此,为了安全地引用未知文本,您必须根据需要使用 quote_literal
、quote_nullable
或 quote_ident
。
也可以使用 format
函数(参见 第 9.4.1 节)安全地构造动态 SQL 语句。例如:
EXECUTE format('UPDATE tbl SET %I = %L ' 'WHERE key = %L', colname, newvalue, keyvalue);
%I
等同于 quote_ident
,%L
等同于 quote_nullable
。 format
函数可以与 USING
子句结合使用:
EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname) USING newvalue, keyvalue;
这种形式更好,因为变量以其本机数据类型格式处理,而不是无条件地将它们转换为文本并通过 %L
引用。它也更有效。
一个更大规模的动态命令和 EXECUTE
的示例可以在 示例 41.10 中看到,它构建并执行一个 CREATE FUNCTION
命令来定义一个新函数。
有几种方法可以确定命令的效果。第一种方法是使用 GET DIAGNOSTICS
命令,其形式如下:
GET [ CURRENT ] DIAGNOSTICSvariable
{ = | := }item
[ , ... ];
此命令允许检索系统状态指示符。CURRENT
是一个无关词(但请参阅 第 41.6.8.1 节中的 GET STACKED DIAGNOSTICS
)。每个 item
都是一个关键字,用于标识要分配给指定 variable
的状态值(该变量应具有接收它的正确数据类型)。当前可用的状态项显示在 表 41.1 中。冒号等号(:=
)可以替代 SQL 标准 =
标记。一个例子:
GET DIAGNOSTICS integer_var = ROW_COUNT;
表 41.1. 可用的诊断项
名称 | 类型 | 描述 |
---|---|---|
ROW_COUNT |
bigint |
最近处理的行数SQLcommand |
PG_CONTEXT |
text |
描述当前调用堆栈的文本行(参见 第 41.6.9 节) |
PG_ROUTINE_OID |
oid |
当前函数的 OID |
确定命令效果的第二种方法是检查名为 FOUND
的特殊变量,该变量的类型为 boolean
。FOUND
在每次 PL/pgSQL 函数调用开始时为 false。它由以下类型的语句设置:
如果分配了一行,SELECT INTO
语句将 FOUND
设置为 true,如果未返回行,则设置为 false。
如果 PERFORM
语句生成(并丢弃)一个或多个行,则将其设置为 true,否则设置为 false。
如果受影响的行至少有一行,UPDATE
、INSERT
、DELETE
和 MERGE
语句会将 FOUND
设置为 true,否则设置为 false。
如果 FETCH
语句返回一行,则将其设置为 true,否则设置为 false。
如果 MOVE
语句成功重新定位游标,则设置为 true,否则为 false。
如果 FOR
或 FOREACH
语句迭代一次或多次,则设置为 true,否则为 false。循环退出时以此方式设置 FOUND
;在循环执行内部,FOUND
不会被循环语句修改,尽管它可能会被循环体内的其他语句执行所更改。
如果查询返回至少一行,RETURN QUERY
和 RETURN QUERY EXECUTE
语句将 FOUND
设置为 true,否则设置为 false。
其他 PL/pgSQL 语句不改变 FOUND
的状态。特别要注意的是,EXECUTE
会更改 GET DIAGNOSTICS
的输出,但不会改变 FOUND
。
FOUND
是每个 PL/pgSQL 函数中的局部变量;任何对它的更改只会影响当前函数。
有时,一个什么都不做的占位符语句很有用。例如,它可以指示 if/then/else 链中的一个分支是故意留空的。为此,请使用 NULL
语句:
NULL;
例如,以下两个代码片段是等效的:
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN NULL; -- ignore the error END;
BEGIN y := x / 0; EXCEPTION WHEN division_by_zero THEN -- ignore the error END;
哪个更好取决于个人喜好。
在 Oracle 的 PL/SQL 中,不允许空语句列表,因此 NULL
语句对于这种情况是必需的。 PL/pgSQL 允许您直接写空,而不是。
如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。