19.5. 基本语句 #
在本节及随后几节中,我们描述 PL/pgSQL 显式理解的所有语句类型。凡是没有被识别为这些语句类型之一的内容,都被推定为
SQL 查询,并被发送到主数据库引擎执行(在此之后,语句中使用的任何
PL/pgSQL 变量都会被替换)。因此,例如 SQL
INSERT、UPDATE 和 DELETE 命令可以被视为 PL/pgSQL 的语句,但这里不专门列出。
19.5.1. 赋值 #
向变量或行/记录字段赋值写作:
identifier:=expression;
如上文所述,这种语句中的表达式通过向主数据库引擎发送 SQL
SELECT 命令来求值。表达式必须产生单个值。
如果表达式的结果数据类型与变量的数据类型不匹配,或者变量有特定的大小/
精度(比如 char(20)),结果值将由
PL/pgSQL 解释器使用结果类型的输出函数和变量类型的输入函数隐式转换。注意,如果结果值的字符串形式不被输入函数接受,这可能潜在地导致输入函数产生运行时错误。
例子:
user_id := 20; tax := subtotal * 0.06;
19.5.2. SELECT INTO #
产生多列(但只有一行)的 SELECT 命令的结果可以赋给一个记录变量、行类型变量或标量变量列表。写法是:
SELECT INTOtargetexpressionsFROM ...;
其中 target 可以是记录变量、行变量,或由简单变量和记录/行字段组成的逗号分隔列表。注意,这与
PostgreSQL 对 SELECT INTO 的正常解释大不相同——后者的
INTO 目标是一张新建的表。(如果你想在
PL/pgSQL 函数内部从 SELECT 结果创建表,请使用语法 CREATE TABLE ... AS SELECT。)
如果用一行或一个变量列表作为目标,选出的值必须与目标的结构完全匹配,否则会发生运行时错误。当目标是记录变量时,它会自动把自己配置为查询结果列的行类型。
除了 INTO 子句之外,SELECT 语句与普通 SQL SELECT 查询相同,可以使用其全部能力。
如果 SELECT 查询返回零行,会向目标赋予空值。如果 SELECT 查询返回多行,第一行被赋给目标,其余的都被丢弃。(注意,除非你使用了 ORDER BY,否则“第一行”没有明确的定义。)
目前,INTO 子句几乎可以出现在 SELECT 查询的任何位置,但建议像上面描述的那样把它紧放在 SELECT 关键字之后。PL/pgSQL 的未来版本可能对 INTO 子句的位置不那么宽容。
你可以在 SELECT INTO 语句之后立即使用
FOUND 来判断赋值是否成功(即 SELECT
语句是否至少返回了一行)。例如:
SELECT INTO myrec * FROM EMP WHERE empname = myname;
IF NOT FOUND THEN
RAISE EXCEPTION ''employee % not found'', myname;
END IF;
要测试记录/行结果是否为空,也可以使用 IS NULL(或 ISNULL)条件。但没有办法知道是否有额外的行被丢弃。
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;
19.5.3. 执行无结果的表达式或查询 #
有时人们希望求一个表达式或查询的值但丢弃其结果(通常是因为正在调用一个有有用副作用但没有有用结果值的函数)。在 PL/pgSQL 中要做到这一点,使用 PERFORM 语句:
PERFORM query;
这会执行一条 SELECT
query 并丢弃结果。
PL/pgSQL 变量照常在查询中被替换。此外,如果查询产生了至少一行,特殊变量 FOUND 会被设置为真;如果没有产生行,则为假。
注意
人们可能以为不带 INTO 子句的 SELECT 能达到这个效果,但目前唯一被接受的方式是 PERFORM。
一个例子:
PERFORM create_mv(''cs_session_page_requests_mv'', my_query);
19.5.4. 执行动态查询 #
很多时候你会想在 PL/pgSQL 函数内部生成动态查询,也就是每次执行时会涉及不同的表或不同数据类型的查询。 PL/pgSQL 为查询缓存计划的常规做法在这种场景下行不通。为处理这类问题,提供了 EXECUTE 语句:
EXECUTE query-string;
其中 query-string 是一个产生字符串(类型为 text)的表达式,该字符串包含要执行的
query。这个字符串被按字面送给 SQL 引擎。
特别注意,查询字符串不做 PL/pgSQL 变量替换。变量的值必须在构造查询字符串时插入其中。
处理动态查询时,你将不得不面对 PL/pgSQL 中单引号的转义问题。请参阅第 19.11 节中的表,那里有详细的解释,可以为你省些力气。
与 PL/pgSQL 中的所有其他查询不同,由
EXECUTE 语句运行的 query
不会在服务器生命周期内只预备和保存一次。相反,该
query 在语句每次运行时都会重新预备。query-string 可以在函数内动态创建,以对可变的表和字段执行操作。
SELECT 查询的结果会被 EXECUTE 丢弃,而且 SELECT INTO 目前不能在 EXECUTE 中使用。因此,从动态创建的 SELECT 中提取结果的唯一方法是使用稍后描述的 FOR-IN-EXECUTE 形式。
一个例子:
EXECUTE ''UPDATE tbl SET ''
|| quote_ident(fieldname)
|| '' = ''
|| 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'''';'';
-- This works because we are not substituting any variables
-- Otherwise it would fail. Look at PERFORM for another way to run functions
EXECUTE a_output;
END;
' LANGUAGE 'plpgsql';
19.5.5. 获取结果状态 #
有几种方法可以确定命令的效果。第一种方法是使用
GET DIAGNOSTICS,其形式为:
GET DIAGNOSTICSvariable=item[ , ... ] ;
这个命令允许检索系统状态指示器。每个 item
是一个关键字,标识要赋给指定变量的状态值(该变量应具有接收它的正确数据类型)。当前可用的状态项有 ROW_COUNT(最近发送到
SQL 引擎的 SQL 查询所处理的行数)和 RESULT_OID(最近一条 SQL
查询插入的最后一行的 OID)。注意 RESULT_OID 只在
INSERT 查询之后有用。
GET DIAGNOSTICS var_integer = 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 函数。