# 43.5.基本陈述

43.5.1. 分配

43.5.2. 执行SQL命令

43.5.3. 使用单行结果执行命令

43.5.4. 执行动态命令

43.5.5. 获取结果状态

43.5.6. 什么都不做

在本节和接下来的部分中,我们将描述PL/pgSQL明确理解的所有语句类型。任何未被识别为这些语句类型之一的语句都被假定为SQL命令,并被发送到主数据库引擎执行,如中所述第43.5.2节.

# 43.5.1.任务

PL/pgSQL变量的赋值写为:

variable { := | = } expression;

如前所述,这种语句中的表达式是通过SQL进行计算的选择命令发送到主数据库引擎。表达式必须产生一个值(如果变量是行或记录变量,则可能是行值)。目标变量可以是简单变量(可以选择使用块名限定)、行或记录目标的字段,或数组目标的元素或切片。同等的(=)可以用来代替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;

# 43.5.2.执行SQL命令

通常,任何不返回行的SQL命令都可以通过编写命令在PL/pgSQL函数中执行。例如,您可以通过编写

CREATE TABLE mytable (id int primary key, data text);
INSERT INTO mytable VALUES (1,'one'), (2,'two');

如果命令确实返回行(例如选择插入/使现代化/删去具有返回),有两种方法。当命令最多返回一行,或者您只关心第一行输出时,请像往常一样编写命令,但添加一个进入子句捕获输出,如中所述第43.5.3节。要处理所有输出行,请将命令作为对于循环,如中所述第43.6.6节.

通常,仅仅执行静态定义的SQL命令是不够的。通常,您希望命令使用不同的数据值,甚至以更基本的方式变化,例如在不同的时间使用不同的表名。同样,根据情况,有两种方法可以继续。

PL/pgSQL变量值可以自动插入到可优化的SQL命令中,这些命令是选择, 插入, 使现代化, 删去,以及包含其中一个的某些实用程序命令,例如解释创建表格。。。作为选择。在这些命令中,出现在命令文本中的任何PL/pgSQL变量名都将被一个查询参数替换,然后在运行时将该变量的当前值作为参数值提供。这与前面描述的表达式处理过程完全相同;有关详细信息,请参阅第43.11.1节.

以这种方式执行可优化的SQL命令时,PL/pgSQL可能会缓存并重新使用该命令的执行计划,如中所述第43.11.2节.

不可优化的SQL命令(也称为实用程序命令)不能接受查询参数。因此,PL/pgSQL变量的自动替换在此类命令中不起作用。要在从PL/pgSQL执行的实用程序命令中包含非常量文本,必须将实用程序命令构建为字符串,然后处决正如中所讨论的第43.5.4节.

处决如果要以其他方式修改命令,而不是提供数据值,例如更改表名,则必须使用。

有时,对表达式或表达式求值是有用的选择查询但放弃结果,例如,当调用具有副作用但没有有用结果值的函数时。要在PL/pgSQL中执行此操作,请使用表演声明:

PERFORM query;

执行*查询并丢弃结果。写下查询*与编写SQL语句的方式相同选择命令,但替换初始关键字选择具有表演对于具有查询,使用表演然后将查询放在括号中。(在这种情况下,查询只能返回一行。)PL/pgSQL变量将被替换到查询中,如上所述,并以相同的方式缓存计划。还有,特殊变量建立如果查询至少生成一行,则设置为true;如果查询不生成行,则设置为false(请参阅)第43.5.5节).

# 笔记

有人可能会认为选择直接将实现这一结果,但目前唯一被接受的方法是表演。可以返回行的SQL命令,例如选择,将被视为错误而拒绝,除非进入如下一节所述。

举个例子:

PERFORM create_mv('cs_session_page_requests_mv', my_query);

# 43.5.3.使用单行结果执行命令

生成单行(可能是多列)的SQL命令的结果可以分配给记录变量、行类型变量或标量变量列表。这是通过编写base SQL命令并添加进入条款例如

SELECT select_expressions INTO [STRICT] target FROM ...;
INSERT ... RETURNING expressions INTO [STRICT] target;
UPDATE ... RETURNING expressions INTO [STRICT] target;
DELETE ... RETURNING expressions INTO [STRICT] target;

哪里*目标*可以是记录变量、行变量,也可以是简单变量和记录/行字段的逗号分隔列表。PL/pgSQL变量将被替换到命令的其余部分(即除进入子句),并且该计划以相同的方式缓存。这适用于选择, 插入/使现代化/删去具有返回,以及返回行集的某些实用程序命令,例如解释.除了进入子句中,SQL命令与在PL/pgSQL外部编写的命令相同。

# 提示

请注意,对选择具有进入与PostgreSQL的常规选择进入命令,其中进入目标是一个新创建的表。如果要从选择结果在PL/pgSQL函数中,使用以下语法创建表格。。。作为选择.

如果将行变量或变量列表用作目标,则命令的结果列在数量和数据类型上必须与目标的结构完全匹配,否则会发生运行时错误。当记录变量是目标时,它会自动将自己配置为命令结果列的行类型。

这个进入子句几乎可以出现在SQL命令中的任何位置。通常,它写在列表的前面或后面*选择_表达式*在一个选择命令,或在其他命令类型的命令末尾。建议您遵循此约定,以防PL/pgSQL解析器在未来版本中变得更严格。

如果严格的中没有指定进入那么是条款吗*目标*将设置为命令返回的第一行,如果命令未返回任何行,则将设置为null。(请注意,“第一行”的定义并不明确,除非您使用了订购人)第一行之后的任何结果行都将被丢弃。你可以查一下特价商品建立变量(参见第43.5.5节)要确定是否返回了行,请执行以下操作:

SELECT * INTO myrec FROM emp WHERE empname = myname;
IF NOT FOUND THEN
    RAISE EXCEPTION 'employee % not found', myname;
END IF;

如果严格的选项时,该命令必须仅返回一行,否则将报告运行时错误找不到数据(无行)或太多行了(不止一排)。如果希望捕获错误,可以使用异常块,例如:

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;

命令的成功执行严格的总是设定建立这是真的。

对于插入/使现代化/删去具有返回,PL/pgSQL为多个返回行报告错误,即使在严格的未指定。这是因为没有这样的选择订购人用于确定应返回哪个受影响的行。

如果打印参数为函数启用,然后当由于严格的如果不满足,则细节错误消息的一部分将包括有关传递给命令的参数的信息。你可以改变主意打印参数通过设置来设置所有功能plpgsql。打印参数,但只会影响后续的函数编译。您还可以使用编译器选项在每个函数的基础上启用它,例如:

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: $1 = 'nosuchuser'
CONTEXT:  PL/pgSQL function get_userid(text) line 6 at SQL statement

# 笔记

这个严格的选项与Oracle PL/SQL的行为相匹配选择进入以及相关声明。

# 43.5.4.执行动态命令

通常,您会希望在PL/pgSQL函数中生成动态命令,即每次执行时涉及不同表或不同数据类型的命令。PL/pgSQL缓存命令计划的正常尝试(如中所述)第43.11.2节)在这种情况下不起作用。为了解决这类问题执行提供声明:

EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];

在哪里*命令字符串是一个产生字符串的表达式(类型文本) 包含要执行的命令。可选的目标*是记录变量、行变量或简单变量和记录/行字段的逗号分隔列表,命令的结果将存储在其中。可选的使用表达式提供要插入到命令中的值。

PL/pgSQL 变量不会在计算的命令字符串上进行替换。任何必需的变量值都必须在构造时插入到命令字符串中;或者您可以使用如下所述的参数。

此外,没有计划缓存通过执行的命令执行.相反,每次运行语句时总是计划命令。因此,可以在函数内动态创建命令字符串,以对不同的表和列执行操作。

进入子句指定应将返回行的 SQL 命令的结果分配到何处。如果提供了行变量或变量列表,它必须与命令结果的结构完全匹配;如果提供了记录变量,它将自动配置自己以匹配结果结构。如果返回多行,则仅将第一行分配给进入变量。如果未返回任何行,则将 NULL 分配给进入变量。如果不进入子句被指定,命令结果被丢弃。

如果严格的如果给出了选项,则会报告错误,除非该命令恰好生成一行。

命令字符串可以使用参数值,在命令中引用为1美元,$2等。这些符号指的是使用条款这种方法通常比将数据值作为文本插入命令字符串更可取:它避免了将值转换为文本并返回的运行时开销,而且由于不需要引用或转义,因此不太容易受到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;

更干净的方法是使用格式()是的%我插入带有自动引用的表名或列名的规范:

EXECUTE format('SELECT count(*) FROM %I '
   'WHERE inserted_by = $1 AND inserted <= $2', tabname)
   INTO c
   USING checked_user, checked_date;

(本例依赖于SQL规则,即由换行符分隔的字符串文本隐式连接。)

对参数符号的另一个限制是,它们只适用于可优化的SQL命令(选择,插入,使现代化,删去,以及包含其中一个的某些命令)。在其他语句类型(通常称为实用语句)中,必须以文本方式插入值,即使它们只是数据值。

处决使用一个简单的常量命令字符串和一些使用参数,就像上面的第一个例子一样,在功能上等同于直接在PL/pgSQL中编写命令,并允许自动替换PL/pgSQL变量。重要的区别在于处决将在每次执行时重新规划命令,生成特定于当前参数值的计划;而PL/pgSQL可能会创建一个通用计划,并将其缓存以供重用。在最佳方案强烈依赖于参数值的情况下,使用处决积极确保未选择通用计划。

选择进入当前在中不受支持处决; 相反,执行一个简单的选择命令并指定进入作为处决它本身

# 笔记

PL/pgSQL处决声明与处决PostgreSQL server支持的SQL语句。服务器的处决语句不能直接在PL/pgSQL函数中使用(不需要)。

例43.1.在动态查询中引用值

使用动态命令时,通常需要处理单引号的转义。在函数体中引用固定文本的推荐方法是美元引用。(如果您有不使用美元报价的旧代码,请参阅中的概述。)第43.12.1节,这可以在将所述代码转换为更合理的方案时节省一些精力。)

动态值需要小心处理,因为它们可能包含引号字符。使用格式()(这假设您对函数体进行美元报价,因此引号不需要加倍):

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);

这个例子演示了引述引用字面意思功能(参见第9.4节).为安全起见,应传递包含列或表标识符的表达式引述在插入动态查询之前。表达式中包含的值应该是构造的命令中的文字字符串,应该通过引用字面意思。这些函数采取适当的步骤返回分别用双引号或单引号括起来的输入文本,并正确转义任何嵌入的特殊字符。

因为引用字面意思被贴上标签严格的,当使用null参数调用时,它将始终返回null。在上面的例子中,如果新价值键值如果为null,则整个动态查询字符串将变为null,从而导致处决.您可以通过使用quote_nullable函数,其工作原理与引用字面意思除了当使用null参数调用时,它返回字符串无效的例如

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = '
        || quote_nullable(newvalue)
        || ' WHERE key = '
        || quote_nullable(keyvalue);

如果处理的值可能为空,通常应该使用quote_nullable代替引用字面意思.

一如既往,必须注意确保查询中的空值不会产生意外结果。例如哪里条款

'WHERE key = ' || quote_nullable(keyvalue)

如果键值为null,因为使用相等运算符的结果=如果为空,则操作数始终为空。如果希望null像普通键值一样工作,则需要将上述内容重写为

'WHERE key IS NOT DISTINCT FROM ' || quote_nullable(keyvalue)

(目前,与…没有区别处理效率远远低于=,所以除非你必须这样做,否则不要这样做。看见第9.2节有关null和很明显.)

请注意,美元报价仅适用于引用固定文本。试着把这个例子写为:

EXECUTE 'UPDATE tbl SET '
        || quote_ident(colname)
        || ' = $$'
        || newvalue
        || '$$ WHERE key = '
        || quote_literal(keyvalue);

因为如果新价值碰巧控制住了$$.同样的反对意见也适用于您可能选择的任何其他美元报价分隔符。所以,为了安全地引用事先不知道的文本,您必须使用引用字面意思, quote_nullable引述,视情况而定。

还可以使用总体安排功能(参见第9.4.1节).例如:

EXECUTE format('UPDATE tbl SET %I = %L '
   'WHERE key = %L', colname, newvalue, keyvalue);

%我相当于引述%L相当于quote_nullable这个总体安排函数可以与使用条款:

EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
   USING newvalue, keyvalue;

这种形式更好,因为变量是以其本机数据类型格式处理的,而不是无条件地将其转换为文本并通过%L.而且效率更高。

动态命令和处决从中可以看出例43.10,它构建并执行创建函数命令定义一个新函数。

# 43.5.5.获取结果状态

有几种方法可以确定命令的效果。第一种方法是使用获取诊断信息命令,其形式如下:

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

此命令允许检索系统状态指示器。现在的是一个噪音词(但也请参见获取堆叠诊断在里面第43.6.8.1节).每人*项目是一个关键字,用于标识要分配给指定对象的状态值变量*(应该是正确的数据类型来接收它)。中显示了当前可用的状态项表43.1.冒号相等(:=)可以用来代替SQL标准=代币举个例子:

GET DIAGNOSTICS integer_var = ROW_COUNT;

表43.1.可用的诊断项目

名称 类型 描述
行数 比基特 最近的SQL命令处理的行数
PG_语境 文本 描述当前调用堆栈的文本行(请参见第43.6.9节)

确定命令效果的第二种方法是检查名为建立,属于布尔值. 建立在每个PL/pgSQL函数调用中都以false开头。它由以下每种类型的语句设置:

  • A.选择进入语句集建立如果指定了一行,则为true;如果未返回任何行,则为false。

  • A.表演语句集建立如果生成(并丢弃)一行或多行,则为true;如果未生成行,则为false。

  • 使现代化, 插入删去语句集建立如果至少有一行受到影响,则为true;如果没有行受到影响,则为false。

  • A.取来语句集建立如果返回一行,则为true;如果未返回任何行,则为false。

  • A.移动语句集建立如果成功重新定位光标,则为true,否则为false。

  • A.对于弗雷奇语句集建立如果迭代一次或多次,则为true,否则为false。建立在循环退出时以这种方式设置;在循环的执行过程中,建立不会被循环语句修改,尽管它可能会被循环体中其他语句的执行所更改。

  • 返回查询返回查询执行语句集建立如果查询返回至少一行,则为true;如果未返回任何行,则为false。

    其他PL/pgSQL语句不会更改建立.请特别注意处决更改的输出获取诊断信息,但并没有改变建立.

建立是每个PL/pgSQL函数中的局部变量;对其所做的任何更改只会影响当前功能。

# 43.5.6.什么都不做

有时,不做任何事情的占位符语句是有用的。例如,它可以指示if/then/else链的一个臂故意为空。为此,请使用无效的声明:

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中,不允许使用空语句列表,等等无效的声明是必修的在这种情况下。PL/pgSQL允许您什么都不写。