37.6. 基本语句 #
在本节及随后几节中,我们描述 PL/pgSQL 显式理解的所有语句类型。凡是没有被识别为这些语句类型之一的内容,都被推定为
SQL 命令,并被发送到主数据库引擎执行(在此之后,语句中使用的任何
PL/pgSQL 变量都会被替换)。因此,例如 SQL
命令 INSERT、UPDATE 和 DELETE 可以被视为 PL/pgSQL 的语句,但这里不专门列出。
37.6.1. 赋值 #
向变量或行/记录字段赋值写作:
identifier:=expression;
如上文所述,这种语句中的表达式通过向主数据库引擎发送 SQL
SELECT 命令来求值。表达式必须产生单个值。
如果表达式的结果数据类型与变量的数据类型不匹配,或者变量有特定的大小/
精度(比如 char(20)),结果值将由
PL/pgSQL 解释器使用结果类型的输出函数和变量类型的输入函数隐式转换。注意,如果结果值的字符串形式不被输入函数接受,这可能潜在地导致输入函数产生运行时错误。
例子:
user_id := 20; tax := subtotal * 0.06;
37.6.2. SELECT INTO #
产生多列(但只有一行)的 SELECT 命令的结果可以赋给一个记录变量、行类型变量或标量变量列表。写法是:
SELECT INTOtargetselect_expressionsFROM ...;
其中 target 可以是记录变量、行变量,或由简单变量和记录/行字段组成的逗号分隔列表。select_expressions 和命令的其余部分与普通
SQL 中相同。
注意,这与 PostgreSQL 对 SELECT INTO
的正常解释大不相同——后者的 INTO 目标是一张新建的表。如果你想在 PL/pgSQL 函数内部从
SELECT 结果创建表,请使用语法
CREATE TABLE ... AS SELECT。
如果用一行或一个变量列表作为目标,选出的值必须与目标的结构完全匹配,否则会发生运行时错误。当目标是记录变量时,它会自动把自己配置为查询结果列的行类型。
除了 INTO 子句之外,SELECT 语句与普通 SQL
SELECT 命令相同,可以使用其全部能力。
如果查询返回零行,会向目标赋予空值。如果查询返回多行,第一行被赋给目标,其余的都被丢弃。(注意,除非你使用了 ORDER BY,否则“第一行”没有明确的定义。)
目前,INTO 子句几乎可以出现在 SELECT
语句的任何位置,但建议像上面描述的那样把它紧放在
SELECT 关键字之后。PL/pgSQL
的未来版本可能对 INTO 子句的位置不那么宽容。
你可以在 SELECT INTO 语句之后立即使用
FOUND 来判断赋值是否成功(即查询是否至少返回了一行)。例如:
SELECT INTO myrec * FROM emp WHERE empname = myname;
IF NOT FOUND THEN
RAISE EXCEPTION ''employee % not found'', myname;
END IF;
要测试记录/行结果是否为空,可以使用 IS NULL 条件。但没有办法知道是否有额外的行被丢弃。下面是一个处理未返回行情况的例子:
DECLARE
users_rec RECORD;
full_name varchar;
BEGIN
SELECT INTO users_rec * FROM users WHERE user_id=3;
IF users_rec.homepage IS NULL THEN
-- user entered no homepage, return "http://"
RETURN ''http://'';
END IF;
END;
37.6.3. 执行无结果的表达式或查询 #
有时人们希望求一个表达式或查询的值但丢弃其结果(通常是因为正在调用一个有有用副作用但没有有用结果值的函数)。在
PL/pgSQL 中要做到这一点,使用
PERFORM 语句:
PERFORM query;
这会执行 query(它必须是一条
SELECT 语句)并丢弃结果。
PL/pgSQL 变量照常在查询中被替换。此外,如果查询产生了至少一行,特殊变量 FOUND 会被设置为真;如果没有产生行,则为假。
注意
人们可能以为不带 INTO 子句的 SELECT
能达到这个效果,但目前唯一被接受的方式是 PERFORM。
一个例子:
PERFORM create_mv(''cs_session_page_requests_mv'', my_query);
37.6.4. 执行动态命令 #
很多时候你会想在 PL/pgSQL 函数内部生成动态命令,也就是每次执行时会涉及不同的表或不同数据类型的命令。
PL/pgSQL 为命令缓存计划的常规做法在这种场景下行不通。为处理这类问题,提供了 EXECUTE
语句:
EXECUTE command-string;
其中 command-string 是一个产生字符串(类型为 text)的表达式,该字符串包含要执行的命令。这个字符串被按字面送给 SQL 引擎。
特别注意,命令字符串不做 PL/pgSQL 变量替换。变量的值必须在构造命令字符串时插入其中。
处理动态命令时,你将不得不面对 PL/pgSQL 中单引号的转义问题。请参阅第 37.2.1 节中的概述,它可以为你省些力气。
与 PL/pgSQL 中的所有其他命令不同,由
EXECUTE 语句运行的命令不会在会话生命周期内只预备和保存一次。相反,该命令在语句每次运行时都会重新预备。命令字符串可以在函数内动态创建,以对可变的表和列执行操作。
SELECT 命令的结果会被 EXECUTE
丢弃,而且 SELECT INTO 目前不能在
EXECUTE 中使用。有两种方法可以从动态创建的
SELECT 中提取结果:一是使用第 37.7.4 节中描述的
FOR-IN-EXECUTE 循环形式,二是使用带
OPEN-FOR-EXECUTE 的游标,如第 37.8.2 节中所述。
一个例子:
EXECUTE ''UPDATE tbl SET ''
|| quote_ident(colname)
|| '' = ''
|| quote_literal(newvalue)
|| '' WHERE ...'';
这个例子展示了函数 quote_ident(
和 text)quote_literal( 的使用。为安全起见,包含列和表标识符的变量应传递给函数
text)quote_ident。包含在构造的命令中应作为字符串字面量出现的值的变量应传递给 quote_literal。两者都会采取适当的步骤,分别返回以双引号或单引号括起的输入文本,并正确转义任何嵌入的特殊字符。
下面是一个大得多的动态命令与 EXECUTE 的例子:
CREATE FUNCTION cs_update_referrer_type_proc() RETURNS integer AS '
DECLARE
referrer_keys RECORD; -- declare a generic record to be used in a FOR
a_output varchar(4000);
BEGIN
a_output := ''CREATE FUNCTION cs_find_referrer_type(varchar, varchar, varchar)
RETURNS varchar AS ''''
DECLARE
v_host ALIAS FOR $1;
v_domain ALIAS FOR $2;
v_url ALIAS FOR $3;
BEGIN '';
-- Notice how we scan through the results of a query in a FOR loop
-- using the FOR <record> construct.
FOR referrer_keys IN SELECT * FROM cs_referrer_keys ORDER BY try_order LOOP
a_output := a_output || '' IF v_'' || referrer_keys.kind || '' LIKE ''''''''''
|| referrer_keys.key_string || '''''''''' THEN RETURN ''''''
|| referrer_keys.referrer_type || ''''''; END IF;'';
END LOOP;
a_output := a_output || '' RETURN NULL; END; '''' LANGUAGE plpgsql;'';
EXECUTE a_output;
END;
' LANGUAGE plpgsql;
37.6.5. 获取结果状态 #
有几种方法可以确定命令的效果。第一种方法是使用
GET DIAGNOSTICS 命令,其形式为:
GET DIAGNOSTICSvariable=item[ , ... ] ;
这个命令允许检索系统状态指示器。每个 item
是一个关键字,标识要赋给指定变量的状态值(该变量应具有接收它的正确数据类型)。当前可用的状态项有 ROW_COUNT(最近发送到
SQL 引擎的 SQL 命令所处理的行数)和 RESULT_OID(最近一条 SQL
命令插入的最后一行的 OID)。注意 RESULT_OID 只在
INSERT 命令之后有用。
一个例子:
GET DIAGNOSTICS integer_var = ROW_COUNT;
确定命令效果的第二种方法是检查名为 FOUND 的特殊变量,其类型为 boolean。FOUND 在每个
PL/pgSQL 函数调用开始时为假。它被下列各类语句设置:
SELECT INTO语句在返回行时把FOUND设为真,未返回行时设为假。PERFORM语句在产生(并丢弃)行时把FOUND设为真,未产生行时设为假。UPDATE、INSERT和DELETE语句在至少影响一行时把FOUND设为真,未影响行时设为假。FETCH语句在返回行时把FOUND设为真,未返回行时设为假。FOR语句在迭代一次或多次时把FOUND设为真,否则设为假。这适用于FOR语句的所有三种变体(整数FOR循环、记录集FOR循环和动态记录集FOR循环)。FOUND只在FOR循环退出时设置:在循环体执行期间,FOUND不会被FOR语句修改,尽管循环体内其他语句的执行可能会改变它。
FOUND 是一个局部变量;对它的任何更改都只影响当前的
PL/pgSQL 函数。