19.5. 基本语句

本节以及随后的一节里,我们描述所有 PL/pgSQL 明确可以理解的语句类型.任何无法识别为这样类型的语句将被做为 SQL 查询看待,并且被发送到主数据库引擎执行(在将语句中用到的任何 PL/pgSQL 变量进行替换之后).因此, 举例来说,SQL INSERTUPDATE,和 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 INTO target expressions FROM ...;

这里的 target 可以是一个记录变量, 行变量,或者一个用逗号分隔的简单变量和记录/行字段的列表. 请注意这个构造和 PostgreSQL 普通的 SELECT INTO 构造 的解释是不一样的,后者的 INTO 目标是一个新创建的表. (如果你想在 PL/pgSQL 函数里从一个 SELECT 的结果中创建一个表,那么使用 CREATE TABLE ... AS SELECT 语法.)

如果将一行或者一个变量列表用做目标,那么选出的数值必需精确匹配 目标的结构,否则就会产生运行时错误.如果目标是一个记录变量, 那么它自动将自己配置成查询结果列的行类型.

除了 INTO 子句,剩下的 SELECT 语句和普通的 SQL SELECT 查询完全一样, 并且你可以使用 SELECT 的全部能力.

如果 SELECT 查询返回零行,则给目标赋予 NULL. 如果 SELECT 查询返回多行,那么将第一行赋予目标并抛弃其它的行. (请注意∶除非你用了 ORDER BY,否则"第一行"是不明确的.)

目前,INTO 子句可以出现在 SELECT 查询里的几乎任何地方, 但我们建议把它放在 SELECT 紧后面,正如我们上面描写的那样. 将来的 PL/pgSQL 版本可能不会对 INTO 子句的位置再那么宽松了.

PL/pgSQL 里有一个特殊的变量,叫 FOUND,类型为 boolean. 它可以用在 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)条件测试一个 RECORD/ROW 结果 是否为 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
        -- 用户没有输入主页,返回"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的单引号 逃逸的问题.请参考Section 19.11里的表 获取详细的解释,这些说明可以帮你省不少功夫.

和所有其它在PL/pgSQL里的查询不同,一个由EXECUTE语句 运行的 query 在服务器生命期内 并不只准备和保存一次.相反,在该语句每次运行的时候, query 都准备一次. query-string 可以在过程里动态地 生成以便于对各种不同的表和字段进行操作.

来自SELECT查询的结果被EXECUTE抛弃,并且目前EXECUTE 里面还不支持SELECT INTO.因此,从一个动态创建的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;  -- 声明一个用于 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 ''; 

    -- 
    -- 请注意这里是如何在一个 FOR 循环中扫描所有的结果的
    -- 这里用的是 FOR <record> 构造
    --

    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'''';''; 
 
    -- 我们可以象下面这样写是因为我们没有替换任何变量
    -- 否则它会出错.看看 PERFORM 获取另外一种运行函数的方法
    
    EXECUTE a_output; 
END; 
' LANGUAGE 'plpgsql';

19.5.5. 获取结果状态

有好几种方法可以判断一条命令的效果。第一个方法是使用 GET DIAGNOSTICS,它的形式如下:

GET DIAGNOSTICS variable = item [ , ... ] ;

这条命令允许我们检索系统状态标识符.每个 item 是一个关键字,表示一个将要赋予该特定变量的状态值(该变量应该 和要接收的数值类型相同).当前可用的状态项有 ROW_COUNT, 最后一个发送给 SQL 引擎的 SQL 查询处理的行的数量;和 RESULT_OID,最后一条 SQL 查询插入的最后一行的 OID.请注意 RESULT_OID 只有在一个 INSERT 查询之后才有用.

      GET DIAGNOSTICS var_integer = ROW_COUNT;

有一个类型为 boolean 的特殊变量 FOUNDFOUND在每个 PL/pgSQL 函数 里开始都为假。它被下列语句设置:

FOUND 是一个局部变量;它的任何修改只影响当前的 PL/pgSQL 函数。