9.1第三十九章

From PostgreSQL wiki
Jump to navigationJump to search

第三十九章. PL/pgSQL - SQL存储过程语言


39.1 概述

PL/pgSQL是 PostgreSQL 数据库系统的一个可装载的过程语言。PL/pgSQL的设计目标是创建一种可装载的过程语言,可以

可用于创建函数和触发器过程, 在SQL语言中添加控制结构功能, 能够进行复杂的计算, 继承所有用户自定义类型,函数和操作符, 能够定义被服务器信任(的语言), 容易使用。

用PL/pgSQL创建的函数可以用在内置函数用的任何地方,例如,可以创建复杂的计算函数,并之后用它们来定义操作符或者在索引表达式中使用它们。

在PostgreSQL 9.0和以后的版本中,PL/pgSQL将会缺省安装。然而,它仍旧是一个可装载的模块,所以,有特别安全意识的管理员可以选择移除它。

39.1.1.使用PL/pgSQL的优点

SQL是PostgreSQL和大多数其它关系型数据库用做命令的语言。它可移植并且容易学习。但是所有 SQL 语句都必须由数据库服务器独立地执行。

这意味着你的客户端的应用必须把每条命令发送给这个数据库服务器,等待它进行处理,接收和处理这个结果,做一些计算,然后给服务器发送另外一个命令。所有这些东西都会产生进程间通讯,并且如果你的客户端在另外一台机器上甚至还会导致网络开销。

在PL/pgSQL中你可以把一块运算和一系列命令在数据库服务器里面组成一个块,因此这样就拥有了过程语言的力量并且简化 SQL 的使用,并且能大量节约客户端/服务器通讯之间的开销。

   客户端和服务端之间额外的传输将被取消.
   客户端不需要也不用客户端和服务端之间进行封装或者传输的中间结果。
   可以避免命令的多轮解析

这样对于没有用存储功能的应用能有很大的性能提升。 并且,在PL/pgSQL中你可以用所有的SQL数据类型,操作符合函数。

39.1.2.所支持的参数和结果数据类型

在PL/pgSQL中编写的函数可以用服务器支持的任何标量参数或者数组类型,并且他们可以返回这些类型的任何结果。它们也能接受或者返回任何名字声明的复合类型(行类型)。我们还能够声明一个PL/pgSQL函数返回record函数,这意味这这个结果是一个行类型,这个行的字段是在调用它的查询中指定的,就像我们在Section 7.2.1.4讨论的一样。

PL/pgSQL函数能够用VARIADIC标志声明一个可变的数字参数。这个方法实际和SQL的函数是用的方法是一样的,如Section 35.4.5讨论的那样。

PL/pgSQL函数也可以声明并接受返回多态的类型anyelement, anyarray, anynonarray, 和anyenum。一个多态的函数实际操作的数据类型可以在不同的调用环境中变化。如Section 35.2.5讨论的那样,例子请见Section 39.3.1.

PL/pgSQL函数也能声明并返回单个实例的任意一个数据类型集(或者表)。这样的函数通过为结果集每个需要返回的元素执行一个RETURN NEXT生成它的输出,或者用RETURN QUERY的输出来评估一个查询的结果。

最后,PL/pgSQL 函数可以声明为返回void,如果它没什么有用的值可以返回的话。

PL/pgSQL函数也能够用输出的参数来进行声明并代替一个明确的返回类型。这不会为这个语言添加任何基本的功能,但是这样常常是很方便的,特别是返回多个值的时候。这个RETURNS TABLE表示法也能用RETURNS SETOF进行替换。

具体的例子请看Section 39.3.1和Section 39.6.1.


39.2 PL/pgSQL结构

PL/pgSQL是一个块结构语言。函数定义的所有文本都必须是一个块。一个块用下面的方法定义:

   [ <<label>> ]
   [ DECLARE
        declarations ]
   BEGIN
        statements
   END [ label ];

块中的每个声明和每条语句都是用一个分号终止的。如果一个子块在另外一个块里,那么 END 后面必须有个分号,如上所述,不过结束函数体的最后的 END 可以不要这个分号。

   提示:一个常见的错误是在BEGIN后面写上分号。这是错误的并且会导致一个语法错误。

如果你想要一个EXIT语句验证这个块只需要用一个label即可,或者在这个块里声明一个变量的名字并使之有效。如果标签在END后面,那么它必须和这个块开始的标签 相匹配。

所有关键字都不区分大小写。标识符被隐含地转换成小写字符,除非被双引号包围,正如在普通的SQL命令中一样。

在PL/pgSQL中注释的方法和普通SQL中一样。一个双破折号--)引出一个扩展到该行结尾的注释。一个/* 引出一个块注释,一直扩展到下一次*/的出现。块注释嵌套。

在一个块的语句段里的任何语句都可以是一个子块。子块可用于逻辑分组或者把变量局部化为作用于一个比较小的语句组。在子块中声明的变量在其范围之内,将屏蔽跟这个子块外部有着同样的名字的变量,如果你用他们的块的标签引用他们的名字,那么你可以访问任意的外部变量。例如:

      CREATE FUNCTION somefunc() RETURNS integer AS $$
      << outerblock >>
      DECLARE
          quantity integer := 30;
      BEGIN
          RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 30
          quantity := 50;
          --
          -- Create a subblock
          --
          DECLARE
              quantity integer := 80;
          BEGIN
              RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 80
              RAISE NOTICE 'Outer quantity here is %', outerblock.quantity;  -- Prints 50
          END;
      
          RAISE NOTICE 'Quantity here is %', quantity;  -- Prints 50
      
          RETURN quantity;
      END;
      $$ LANGUAGE plpgsql;

注意:在任意的PL/pgSQL函数体周围实际隐藏了这个“外部块(outer block)”。这个块提供了这个函数的参数的声明(如果有),和一些特殊的变量如FOUND(见Section 39.5.5)一样。这个外部块是这个函数的名称的标签,意思是这个参数和特殊的变量可以当作这个函数的名字。

我们一定不要把PL/pgSQL里用于语句分组的 BEGIN/END 和用于事务控制的数据库命令搞混了,他们不是开始或者结束事务。函数和触发器过程总是在一个由外层命令建立起来的事务里执行--他们不能开始或者提交这个事务,因为不会为他们的执行来联系上下文。不过,一个包含EXCEPTION字句能够有效的形成一个子事务,并且在不影响外部事务的情况下能进行回滚。更多关于这些写的信息见Section 39.6.6.

39.3. 声明

所有在块里使用的变量都必须在一个块的声明段里声明。(唯一的例外是FOR循环的循环变量在一个整数值的范围进行遍历,并且被自动声明为整型变量,同样的,for循环的循环变量也将遍历一个游标的结果,并自动声明为一个记录变量。)

PL/pgSQL可以用任意的SQL数据类型,如integer, varchar,和char.

这里是一些关于声明变量的例子:

   user_id integer;
   quantity numeric(5);
   url varchar;
   myrow tablename%ROWTYPE;
   myfield tablename.columnname%TYPE;
   arow RECORD;

通常声明一个变量的语法是:

   name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ];

如果有DEFAULT字句,当进入这个块的时候将会为这个变量指定一个初始的值。如果没有DEFAULT字句,那这个变量的值将为SQL中的空值。CONSTANT选项避免了该变量被赋值,这样其数值在该块的范围内保持常量。如果指定了NOT NULL选项,如果分配一个空值将会导致运行时错误。所有用NOT NULL声明的变量还必须声明一个非空的缺省值。

每次进入该块的时候,这个变量的默认值将会评估和分配给改变量(不是每一次函数调用)。因此,例如,如果把 now() 赋予一个类型为 timestamp 的变量会令变量拥有函数实际调用的时间,而不是函数预编译的时间。

例如:

   quantity integer DEFAULT 32;
   url varchar := 'http://mysite.com';
   user_id CONSTANT integer := 10;


39.3.1.声明函数参数

传递给函数的参数都是用 $1,$2,等等这样的标识符。有时候为了增强可读性,我们可以为 $n 参数名声明别名。然后通过这个别名或者数字标识符可以指向这个参数值。

有两种方法创建一个别名。最好的方法是用CREATE FUNCTION命令给予这个参数一个名字,例如:

   CREATE FUNCTION sales_tax(subtotal real) RETURNS real AS $$
   BEGIN
      RETURN subtotal * 0.06;
   END;
   $$ LANGUAGE plpgsql;

另一个方法是,在PostgreSQL 8.0之前唯一的方法,明确的用别名进行声明,用以下的语法进行声明:

   name ALIAS FOR $n;

这个风格的同一个例子看起来像下面这样 :

   CREATE FUNCTION sales_tax(real) RETURNS real AS $$
   DECLARE
       subtotal ALIAS FOR $1;
   BEGIN
       RETURN subtotal * 0.06;
   END;
   $$ LANGUAGE plpgsql;

注意:这两个例子不是完全一样的。在第一种情况,subtotal可以用sales_tax.subtotal进行引用,但是在第二种情况下不能这么做。(如果我们给这个内部块附加了一个标签,subtotal能够替代这个标签)

一些更多的例子:

   CREATE FUNCTION instr(varchar, integer) RETURNS integer AS $$
   DECLARE
        v_string ALIAS FOR $1;
        index ALIAS FOR $2;
   BEGIN
         -- some computations using v_string and index here
   END;
   $$ LANGUAGE plpgsql;


   CREATE FUNCTION concat_selected_fields(in_t sometablename) RETURNS text AS $$
   BEGIN
        RETURN in_t.f1 || in_t.f3 || in_t.f5 || in_t.f7;
   END;
   $$ LANGUAGE plpgsql

当一个PL/pgSQL函数用输出参数来进行声明时,给予这个输出参数$n名和一个任意的别名跟正常输入参数是同样的方法。即使这个输出参数以NULL开始时也是一个有效的变量,它应该在函数的执行过程中被分配。这个参数最好的值将被返回。例如,这个sales-tax例子也可以用这种方法完成:

   CREATE FUNCTION sales_tax(subtotal real, OUT tax real) AS $$
   BEGIN
        tax := subtotal * 0.06;
   END;
   $$ LANGUAGE plpgsql;

注意:我们省略了RETURNS real---我们可以将它包括在内,但它是多余的。

当返回多个值的时候输出参数将非常有用,一个简单的例子是:

   CREATE FUNCTION sum_n_product(x int, y int, OUT sum int, OUT prod int) AS $$
   BEGIN
       sum := x + y;
       prod := x * y;
   END;
   $$ LANGUAGE plpgsql;

如在Section 35.4.4中的讨论,这将为这个函数的结果创建一个匿名的记录类型。如果使用了RETURNS字句,那么必须给它指明RETURNS记录。

另外一种方法声明PL/pgSQL函数是用RETURNS TABLE,例如:

   CREATE FUNCTION extended_sales(p_itemno int)
   RETURNS TABLE(quantity int, total numeric) AS $$
   BEGIN
        RETURN QUERY SELECT quantity, quantity * price FROM sales
                WHERE itemno = p_itemno;
   END;
   $$ LANGUAGE plpgsql;

这跟声明一个或者多个OUT参数和制定RETURNS SETOF这些类型是同样的方法。

当返回的PL/pgSQL函数的类型被声明为一个多态类型(anyelement, anyarray, anynonarray, 或者anyenum),特殊参数$0将被创建。它的数据类型将实际的返回函数的类型,从实际的输入类型返回(见Section 35.2.5)。这运行这个函数访问这个实际的返回类型如Section 39.3.3显示的那样。$0初始值为空并且能够被函数修改,如果需要,它可以用于保留返回值,虽然这不是必须的。$0也可以被给予一个别名。例如,这个函数能在任意一个有+操作符的数据类型上工作:

   CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
   RETURNS anyelement AS $$
   DECLARE
       result ALIAS FOR $0;
   BEGIN
       result := v1 + v2 + v3;
       RETURN result;
   END;
   $$ LANGUAGE plpgsql

声明一个或者多个多态类型的输出参数也是同样的效果。这种情况下这个特殊的$0参数将不会被用到,这个输出参数本身也是同样的作用,例如:

   CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement,
                                OUT sum anyelement)
   AS $$
   BEGIN
       sum := v1 + v2 + v3;
   END;
   $$ LANGUAGE plpgsql;

39.3.2. 别名

   newname ALIAS FOR oldname;

这个ALIAS语法比以前的章节中介绍的更加普通:你可以为任意一个变量声明一个别名,不只是函数的参数。这实际的用途是用预定义的名字为变量定义不同的名字,如触发器过程中的NEW或者OLD。 例子:

   DECLARE
      prior ALIAS FOR old;
      updated ALIAS FOR new;

因此,ALIAS使同样的对象有两种不同的方式命名,如果不限制的使用,将会变得混乱。这种方法最好只用于覆盖预定义的名字。


39.3.3.拷贝类型

   variable%TYPE

%TYPE 提供一个变量或者表字段的数据类型。你可以用这个声明将要保存数据库数值的变量。例如,比方说你在users表中有一个字段的名字是user_id。声明一个和users.user_id同样的变量类型你可以这样写:

   user_id users.user_id%TYPE;

通过用%TYPE,你不需要知道你引用的这个数据类型的结构,并且,最重要的是,如果引用的这个数据类型在将来被改变(例如:你将user_id的类型从integer变成real类型),你也不需要改变这个函数的定义。

在多态函数中%TYPE是一个特殊的变量,因为内部变量的数据类型可能在不同调用中是不一样的,我们可以通过给函数的参数或者结果占位符附加 %TYPE 的方法来创建合适的变量。

39.3.4. 行类型

   name table_name%ROWTYPE;
   name composite_type_name;

一个复合类型的变量叫做一个行变量(或者行类型变量)。这样的变量能保留一个SELECT或者FOR查询结果的完整一行,只要命令的字段集匹配该变量声明的类型。改行各个字段的值能用常用的点号进行访问,例如rowvar.field。

行变量能够被声明为和现有的表或者视图的行的类型相同,方法是用table_name% ROWTYPE表示法。或者你也可以声明它的类型是一个复合类型的名字。(因为每个表都有一个相关联的同名数据类型,在 PostgreSQL 里你写不写 %ROWTYPE其实并不重要。但是有 %ROWTYPE 的形式移植性更好)

函数的参数可以是一个复合变量(表完整的行)。在这种情况下,对应的标识符 $n 将是一个行变量,并且可以从中选取字段,比如 $1.user_id。

只有用户自定义的表的行才能够用行类型的变量访问,并发OID或者其他系统行(因为这行可能来自视图)。该行类型的数据域继承表中象 char(n) 这种类型字段的尺寸和精度。 例如char(n). 这是一个用复合类型的例子。table1和table2是一个已存在的表并至少有一个已经提到的字段:

   CREATE FUNCTION merge_fields(t_row table1) RETURNS text AS $$
   DECLARE
      t2_row table2%ROWTYPE;
   BEGIN
      SELECT * INTO t2_row FROM table2 WHERE ... ;
      RETURN t_row.f1 || t2_row.f3 || t_row.f5 || t2_row.f7;
   END;
   $$ LANGUAGE plpgsql;
   SELECT merge_fields(t.*) FROM table1 t WHERE ... ;

39.3.5.记录类型

   name RECORD;

记录变量和行类型变量类似,但是它们没有预定义的结构。它们在SELECT或者FOR命令中获取实际的行结构。 一个行变量的子结构可以在每次赋值的时候改变。这样做的一个结果是:在一个记录变量被赋予数值之前,它没有子结构, 并且任何对其中的数据域进行访问的企图都将产生一个运行时错误。

请注意RECORD不是一个真正的数据类型,只是一个占位符。我们还应该意识到当PL/pgSQL声明一个返回类型为record时,它和一个记录变量的概念并不完全相同,即使这个函数可能使用一个记录变量保存它的结果也如此。在两种情况下,在书写函数的时候,实际的行结构都是不知道的,但是对于返回 record 的函数来说, 实际的结构是在调用它的查询被分析的时候决定的,而行变量可以在运行中改变其行结构。


39.3.6. PL/pgSQL变量排序

当一个PL/pgSQL函数有一个或者多个collatable数据类型参数时,每一个函数调用时的排序规则取决于指定的实际参数的顺序。见Section 22.2描述。如果成功指定一个排序规则(例如,他们和隐式的排序规则没有冲突),那么所有的collatable参数将被视为有隐式的排序规则。这将影响在函数里面一些敏感排序的操作的行为。例如,认为

   CREATE FUNCTION less_than(a text, b text) RETURNS boolean AS $$
   BEGIN
       RETURN a < b;
   END;
   $$ LANGUAGE plpgsql;
   SELECT less_than(text_field_1, text_field_2) FROM table1;
   SELECT less_than(text_field_1, text_field_2 COLLATE "C") FROM table1;

如果collatable数据类型没有指定参数,或者没有为他们定义公共的排序规则,那么这些参数和本地变量将会用他们默认的数据类型(通常用数据库默认的排序规则,但是对于domain类型的变量可能会不同)。

如果无论什么函数调用都想强制用一个特定的排序规则,那么可以在函数内部使用显式的COLLATE字句。例如,

   CREATE FUNCTION less_than_c(a text, b text) RETURNS boolean AS $$
   BEGIN
       RETURN a < b COLLATE "C";
   END;
   $$ LANGUAGE plpgsql;

39.4.表达式

所有在PL/pgSQL 语句里使用的表达式都是用服务器的普通SQL执行器进行处理的。例如,当你写一个PL/pgSQL表达式类似与:

   IF expression THEN ...

PL/pgSQL将会认为这个表达式类似于下面这个查询

   SELECT expression

用主SQL引擎形成SELECT命令,任何一个出现PL/pgSQL变量标识符的地方将被参数代替,关于这些细节的讨论见Section 39.10.1.这样就允许SELECT的执行计划只需要准备一次,并且可以为随后的不同的变量的值复用。因此,一个表达式真正在第一次使用实际上是一个PREPARE命令。例如,如果我们声明两个整形变量X和Y,并且我们写成

   IF x < y THEN ...

在后台发生的事件跟这个是一样的:

   PREPARE statement_name(integer, integer) AS SELECT $1 < $2;

并且,每一个IF表达式执行时,这个预备的表达式都会被执行,并作为参数值提供给当前的PL/pgSQL变量。这种方法中预备的执行计划将在数据库连接的生命周期内被保存,见Section 39.10.2中的描述。通常,这些描述对于一个PL/pgSQL用户来说并不重要,但是这对于他们来诊断问题来说非常有用。


39.5. 基本语句

在本节和随后的一节里,我们将描述所有PL/pgSQL能够明确理解的语句类型。任何不能识别的语句类型都被视为一个SQL命令来对待,并且发送给主数据库的引擎去执行,见Section 39.5.2 和 Section 39.5.3. 的描述。


39.5.1.赋值

一个PL/pgSQL变量赋值这样来书写:

   variable := expression;

如上面所述,这样的语句中的表达式被认为的意思是:发送到主数据库引擎的一个SQL SELECT命令。这个表达式必须要生成单一的数值(如果这个变量是一行或者是记录类型,那么这可能是一个行值),这个目标变量可以是一个简单的变量(选择一个合格的块名),一个行的字段或者记录变量,或者是一个数组的一个元素,这是一个简单的变量或者字段。

如果表达式的结果的数据类型和变量的数据类型不匹配,或者这个变量指定了一个大小/精度(如char(20)),这个结果值将会隐式的被PL/pgSQL的解释器用这个结果类型的输出函数和这个变量类型的输入函数进行转换。要注意的是,如果结果数值的字串形式不是输入函数可以接受的形式, 那么这样做可能导致类型输入函数产生的运行时错误。 例如:

   tax := subtotal * 0.06;
   my_record.user_id := 20;


39.5.2.执行一个没有结果的表达式或者命令

任何SQL命令不返回任何行,例如没有RETURNING的INSERT字句,你可以在PL/pgSQL函数里执行这个命令或者只是写这个命令。

任何一个PL/pgSQL变量名出现在命令中都会被视为是一个参数,并且,变量当前的值将作为运行时的参数的值。这跟前面表达式的处理是完全一样的,详细信息见Section 39.10.1.

当用这种方法执行一个SQL命令,在数据库连接的生命周期里,PL/pgSQL计划只会将这个命令执行一次,这个计划会被后面的执行再利用。这种情况的影响的详细讨论见Section 39.10.2.

有时候对于评估一个表达式或者一个SELECT语句但不丢弃它的结果将非常有用,例如,调用一个有副作用但是没有结果的函数。在PL/pgSQL这样做,用PERFORM语句;

   PERFORM query;

这条语句执行一个query并丢弃它的结果。query 的写法和你平常写 SQL SELECT 命令是一样的,但是初始关键字SELECT将会被PERFORM替代。PL/pgSQL变量在这个查询中将会被取代,同样的命令但不返回任何结果,这个计划将用同样的方法进行缓存。并且,如果查询产生至少一行,特殊变量FOUND应该设置为true,如果不产生行,那么应该设置成false。(见Section 39.5.5)

注意:我们可能希望直接写SELECT就能达到这结构,但是目前唯一可以接受的方法就是用PERFORM。一个SQL命令将能够放回行,例如SELECT,这将会被拒绝并返回一个错误,除非使用了INTO字句,见下一节的讨论。 例子:

   PERFORM create_mv('cs_session_page_requests_mv', my_query);


39.5.3.执行一个单行结果查询

一个SQL命令的结果产生一个单个的行可以分配给一个记录变量(可能是多行),行类型变量,或者变量列表。要完成这些可以书写一个基本的SQL命令并添加INTO字句。例如,

   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;

target可以是一个记录变量,行变量或者一个用逗号分隔的单个变量或者记录/行域列表。PL/pgSQL变量将被其他的查询替代,执行计划将被缓存,就像上面描述的不返回任何一行的的命令中描述的那样。它试用与有RETURNING 的SELCT,INSERT/UPDATE/DELETE语句,还有用公共的工具命令返回行集的结果(如EXPLAIN)。除了这个INTO字句,这个SQL命令和在PL/pgSQL之外的SQL的写法是一样的。

提示:上面提到的SELECT和INTO的解释和PostgreSQL的SELECT INTO命令是完全不同 ,其中INTO的目标是创建表。如果你想在PL/pgSQL函数内部通过SELECT的结果来创建一个表,用语法CREATE TABLE ... AS SELECT。

如果一行或者一个变量列表被用于这个目标,这个查询的结果行必须和这个目标 的数量和数据类型的结构完全匹配。否则在运行时将会出现错误。当一个目标是记录变量时,它将自动的将自己设置成这个查询结果的行类型。

INTO字句几乎能出现在SQL命令中的任何地方。习惯上它被写在一个SELECT命令中select_expressions列表的之前或者之后,或者在其他命令类型的末尾。建议按照此条件下的惯例,PL/pgSQL的解析器成为在未来的版本更严格。

如果在INTO字句中没有声明STRICT,那么target将被设置成这个查询返回的第一行,如果这个查询返回空它将设置成空。(注意这个第一行“the first row”是不明确的,除非你用了ORDER BY语句。)其他非第一行的结果将会被丢弃。也可以坚持这个特殊变量FOUND来确定是否返回了一行:

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

如果声明了STRICT选项,这个查询必须要返回一个明确的行,否则在运行时将会出现错误,出现NO_DATA_FOUND(no rows)或者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。

对于有和ETURNING的INSERT/UPDATE/DELETE语句,PL/pgSQL对于返回值多余一行的情况将会报告一个错误,即使这个STRICT没有声明。这是因为这里没有这个选项如ORDER BY来确定哪行将被返回。

注意:STRICT选项和PL/SQL的SELECT INTO和相关的语句匹配。

在操作过程中你需要从SQL查询中处理多行的结果,见Section 39.6.4.

39.5.4执行动态命令

通常在PL/pgSQL函数中你想要生成动态命令。这个命令将包括他们每次执行时不同的表或者不同的数据类型。PL/plSQL尝试缓存这个命令的执行计划将在这种条件下将不再适合。(见Section 39.10.2中的论述)为了处理这样的问题,我们提供了 EXECUTE 语句:

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

这里的command-string 是一个生成字串(类型为 text)的表达式,该字串包含要执行的命令。 选项target是一个记录变量,一个行变量,或者用逗号分隔的单个变量和记录/行字段的列表,在其中的命令的结果将被保存。可选的USING表达式提供值到这个插入命令。

PL/pgSQL的变量在计算命令字符串完成中不会被替换。任何一个请求的变量的值必须在构造字符串的时候插在这个命令字符串中,或者你可以使用如下所述的参数。

此外,也没有通过执行EXECUTE命令来进行执行计划缓存。相反,在该语句每次运行的时候,命令都准备一次。因此,这个命令字符串能够动态的创建以便于对各种不同的表和字段进行操作。

该INTO字句声明返回行的SQL命令的结果应该被分配。如果提供一行或者一个变量列表,它必须完全匹配这个查询结果的结构(当一个记录变量被用的时候,它将自动的设置自己来匹配这个结果的结构)。如果返回多个行,只有第一行记录才会分配给INTO变量。如果没有返回任何一行,空值将会会分配给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 '
      || tabname::regclass
      || ' WHERE inserted_by = $1 AND inserted <= $2'
  INTO c
  USING checked_user, checked_date;

这个符号参数另外的限制是他们只能在SELECT,INSERT,UPDATE和DELETE命令中使用。在其他类型的语句中(通常是公用的语句),你必须插入原值,即使他们只是数据值。

一个带有单一常数的字符串和用USING参数的EXECUTE命令,如上面第一个例子,它在功能上和在PL/pgSQL中直接写这个命令是一样的,并且允许PL/pgSQL变量自动替换。EXECUTE重要的不同点是在每一次执行中这个命令的执行计划将会重新产生,这个计划是由当前指定的参数的值决定的,反之,PL/pgSQL将正常创建一个普通的执行计划并缓存起来重复使用。在这种情况下,最好的执行计划取决于这个参数的值,可以明显加快EXECUTE的执行速度,然而当执行计划不受参数值影响时,重新生成执行计划将是多余的。

SELECT INTO目前不支持EXECUTE语句,然而,可以执行一个简单的SELECT命令并且指定INTO为EXECUTE语句本身的一部分。

注意:PL/pgSQL EXECUTE表达式跟PostgreSQL中支持的EXECUTE SQL表达式没有关系。服务器的EXECUTE语句不能直接的在PL/pgSQL函数中使用(并且不需要)。

   Example 39-1. 在动态查询中引用值