↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

已结束支持的版本: 7.0 / 6.5 / 6.4
历史版本。 PostgreSQL 7.0 已结束支持。 请参阅 当前版本手册.

10.2. 描述

10.2.1. PL/pgSQL 的结构

PL/pgSQL 语言不区分大小写。所有关键字和标识符都可以用大小写混合的形式使用。

PL/pgSQL 是一种面向块的语言。块的定义是

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

块的语句部分中可以有任意数量的子块。子块可以用来把变量隐藏在语句块的外部世界之外。块前面的声明部分中声明的变量在每次进入该块时都会被初始化为它们的默认值,而不是每次函数调用只初始化一次。

重要的一点是不要误解 PL/pgSQL 中用于给语句分组的 BEGIN/END 与用于事务控制的数据库命令的含义。函数和触发器过程不能开始或提交事务,而且Postgres没有嵌套事务。

10.2.2. 注释

PL/pgSQL 中有两种注释。双横线'--'开始一个延伸到行尾的注释。'/*'开始一个块注释,它延伸到下一个'*/'出现的位置。块注释不能嵌套,但双横线注释可以被包进块注释中,而双横线也可以隐藏块注释的定界符'/*'和'*/'。

10.2.3. 声明

块或其子块中使用的所有变量、行和记录都必须在块的声明部分中声明,唯一的例外是遍历一段整数值范围的 FOR 循环的循环变量。传递给 PL/pgSQL 函数的参数会用通常的标识符 $n 自动声明。声明的语法如下:

name [ CONSTANT ] type> [ NOT NULL ] [ DEFAULT | := value ];

声明一个指定基本类型的变量。如果变量被声明为 CONSTANT,它的值不能被更改。如果指定了 NOT NULL,赋予 NULL 值会导致一个运行时错误。由于所有变量的默认值都是 SQL的 NULL 值,所有声明为 NOT NULL 的变量还必须指定一个默认值。

默认值在每次函数被调用时都会被求值。因此把 'now'赋给一个 datetime类型的变量会让该变量拥有实际这次函数调用的时间,而不是函数被预编译为字节码时的时间。

name class%ROWTYPE;

声明一个具有给定类结构的行。类必须是数据库中已存在的表或视图的名字。行的字段用点号记法访问。函数的参数可以是复合类型(完整的表行)。这种情况下,对应的标识符 $n 将是一个行类型,但它必须用下面描述的 ALIAS 命令声明别名。行中只能访问表行的用户属性,不能访问 Oid 或其他系统属性(因为该行可能来自一个视图,而视图的行没有有用的系统属性)。

行类型的字段继承表中该字段的尺寸或精度(对于 char() 等数据类型)。

name RECORD;

记录与行类型类似,但它们没有预定义的结构。它们被用在选取和 FOR 循环中,保存 SELECT 操作产生的一个实际数据库行。同一个记录可以用于不同的选取。当记录中没有实际行时访问记录或试图给记录字段赋值都会导致运行时错误。

触发器中的 NEW 和 OLD 行会作为记录交给过程。这是必要的,因为在Postgres中同一个触发器过程可以处理不同表的触发器事件。

name ALIAS FOR $n;

为了让代码有更好的可读性,可以为函数的位置参数定义一个别名。

当复合类型作为参数传给函数时,这种别名声明是必需的。SQL 函数中的点号记法 $1.salary 在 PL/pgSQL 中是不允许的。

RENAME oldname TO newname;

更改变量、记录或行的名字。这在需要在触发器过程内部用另一个名字引用 NEW 或 OLD 时有用。

10.2.4. 数据类型

变量的类型可以是数据库中任何已有的基本类型。上面声明部分中的type定义为:

  • Postgres基本类型

  • variable%TYPE

  • class.field%TYPE

variable是此前在同一函数中声明、在此处可见的变量的名字。

class是一个已存在的表或视图的名字,其中field是某个属性的名字。

使用class.field%TYPE 会让 PL/pgSQL 在后端生命期内第一次调用函数时查找属性的定义。假设有一张带 char(20) 属性的表和一些用局部变量处理其内容的 PL/pgSQL 函数。现在有人觉得 char(20) 不够了,转储表、删除它、把该属性重建为 char(40) 再恢复数据。哈——他忘了那些函数。函数内部的计算会把值截断到 20 个字符。但如果它们是用 class.field%TYPE 声明定义的,它们就会自动处理尺寸的变化,即使新表模式把该属性定义为 text 类型也没问题。

10.2.5. 表达式

PL/pgSQL 语句中使用的所有表达式都由后端的执行器处理。表面上包含常量的表达式实际上可能需要运行时求值(例如 datetime 类型的 'now'),因此 PL/pgSQL 解析器不可能识别除 NULL 关键字之外的真正常量值。所有表达式都通过执行查询

      SELECT expression
     

使用 SPI 管理器来求值。在表达式中,变量标识符的出现会被参数替代,而变量的实际值则通过参数数组传递给执行器。PL/pgSQL 函数中使用的所有表达式都只准备和保存一次。

Postgres主解析器所做的类型检查对常量值的解释有一些副作用。详细说来,下面这两个函数的行为是有差别的:

CREATE FUNCTION logfunc1 (text) RETURNS datetime AS '
    DECLARE
        logtxt ALIAS FOR $1;
    BEGIN
        INSERT INTO logtable VALUES (logtxt, ''now'');
        RETURN ''now'';
    END;
' LANGUAGE 'plpgsql';
     

和

CREATE FUNCTION logfunc2 (text) RETURNS datetime AS '
    DECLARE
        logtxt ALIAS FOR $1;
        curtime datetime;
    BEGIN
        curtime := ''now'';
        INSERT INTO logtable VALUES (logtxt, curtime);
        RETURN curtime;
    END;
' LANGUAGE 'plpgsql';
     

的行为。对于 logfunc1(),Postgres主解析器在为 INSERT 准备计划时就知道字符串'now'应当被解释为 datetime,因为 logtable 的目标字段就是该类型。于是它会在此刻把它变成一个常量,而这个常量值随后在后端的整个生命期内 logfunc1() 的所有调用中都被使用。不用说,这并不是程序员想要的结果。

对于 logfunc2(),Postgres主解析器不知道'now'应当变成什么类型,因此它返回一个包含字符串'now' 的 text 数据类型。在向局部变量 curtime 赋值期间,PL/pgSQL 解释器通过调用 text_out() 和 datetime_in() 函数把这个字符串转换成 datetime 类型。

Postgres主解析器所做的这种类型检查是在 PL/pgSQL 基本完成之后才实现的。这是 6.3 与 6.4 之间的一个差异,影响所有使用 SPI 管理器预备计划特性的函数。以上述方式使用局部变量是目前 PL/pgSQL 中让这些值被正确解释的唯一办法。

如果在表达式或语句中使用了记录字段,这些字段的数据类型在同一表达式的各次调用之间不应改变。在编写为多张表处理事件的触发器过程时要牢记这一点。

10.2.6. 语句

凡是没有被 PL/pgSQL 解析器按下面说明理解的东西,都会被放进一个查询并发送到数据库引擎执行。这样的查询不应返回任何数据。

赋值

把一个值赋给变量或行/记录字段写作

	 identifier := expression;
	

如果表达式的结果数据类型与变量的数据类型不匹配,或者变量具有已知的尺寸/精度(如 char(20)),结果值会被 PL/pgSQL 字节码解释器使用结果类型的输出函数和变量类型的输入函数隐式转换。注意这可能导致类型的输入函数产生运行时错误。

把一个完整的选取赋给记录或行可以这样完成

SELECT  INTO target expressions FROM ...;
	

target可以是记录、行变量,或者由变量和记录/行字段组成的逗号分隔列表。

如果用一行或一个变量列表作为目标,所选中的值必须与目标的结构完全匹配,否则会发生运行时错误。FROM 关键字后面可以跟 SELECT 语句允许的任何有效的限定条件、分组、排序等。

有一个名为 FOUND 的 bool 类型特殊变量,可以在 SELECT INTO 之后立即使用它来检查赋值是否成功。

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

如果选取返回多行,只有第一行会被移入目标字段。其余的行都会被无声地丢弃。

调用另一个函数

Prostgres数据库中定义的所有函数都返回一个值。因此,调用函数的通常方式是执行一个 SELECT 查询或做一次赋值(产生一个 PL/pgSQL 内部的 SELECT)。但有时人们并不关心函数的结果。

PERFORM query
	

通过 SPI 管理器执行一个'SELECT query' 并丢弃结果。像局部变量这样的标识符仍然会被替换为参数。

从函数返回

RETURN expression
	

函数终止,expression的值会被返回给上层执行器。函数的返回值不能没有定义。如果控制到达函数顶层块的末尾而没有碰到 RETURN 语句,就会发生运行时错误。

表达式的结果会被自动转换成函数的返回类型,转换方式与赋值中描述的相同。

中止与消息

正如上面的例子所示,有一条 RAISE 语句可以把消息抛进 Postgres的 elog 机制。

RAISE level format'' [, identifier [...]];
	

在格式中,"%"用作后续逗号分隔标识符的占位符。可用的级别有 DEBUG(在生产运行的数据库中被无声地抑制)、NOTICE(写进数据库日志并转发给客户端应用)和 EXCEPTION(写进数据库日志并中止事务)。

条件语句

IF expression THEN
    statements
[ELSE
    statements]
END IF;
	

expression必须返回一个至少可以转换为布尔类型的值。

循环

有多种类型的循环。

[<<label>>]
LOOP
    statements
END LOOP;
	

一个必须由 EXIT 语句显式终止的无条件循环。可选的标签可以被嵌套循环的 EXIT 语句用来指定应该终止哪一层嵌套。

[<<label>>]
WHILE expression LOOP
    statements
END LOOP;
	

一个条件循环,只要expression的求值为真它就一直执行。

[<<label>>]
FOR name IN [ REVERSE ] expression .. expression LOOP
    statements
END LOOP;
	

一个遍历一段整数值范围的循环。变量 name被自动创建为 integer 类型并且只存在于循环内部。给出范围下界和上界的两个表达式只在进入循环时被求值。迭代步长总是 1。

[<<label>>]
FOR record | row IN select_clause LOOP
    statements
END LOOP;
	

记录或行会被赋以 select 子句产生的所有行,并且语句对每一行都执行。如果循环被 EXIT 语句终止,最后赋值的行在循环之后仍然可以访问。

EXIT [ label ] [ WHEN expression ];
	

如果没有给出label,最内层的循环会被终止,接下来执行 END LOOP 之后的语句。如果给出了 label,它必须是当前或嵌套循环块某个上层的标签。此时被命名的循环或块被终止,控制转移到该循环/块对应的 END 之后的语句。

10.2.7. 触发器过程

PL/pgSQL 可以用来定义触发器过程。它们用通常的 CREATE FUNCTION 命令创建,是一个没有参数、返回类型为 OPAQUE 的函数。

用作触发器过程的函数有一些Postgres 特有的细节。

首先它们的顶层块声明部分中会自动创建一些特殊变量。它们是

NEW

数据类型 RECORD;在行级触发器中保存 INSERT/UPDATE 操作的新数据库行的变量。

OLD

数据类型 RECORD;在行级触发器中保存 UPDATE/DELETE 操作的旧数据库行的变量。

TG_NAME

数据类型 name;包含实际被触发的触发器名字的变量。

TG_WHEN

数据类型 text;依据触发器定义而是'BEFORE'或'AFTER'之一的字符串。

TG_LEVEL

数据类型 text;依据触发器定义而是'ROW'或'STATEMENT'之一的字符串。

TG_OP

数据类型 text;'INSERT'、'UPDATE'或'DELETE'之一的字符串,指出触发器实际是为哪个操作而触发。

TG_RELID

数据类型 oid;导致触发器调用的表的对象 ID。

TG_RELNAME

数据类型 name;导致触发器调用的表的名字。

TG_NARGS

数据类型 integer;CREATE TRIGGER 语句中给触发器过程的参数个数。

TG_ARGV[]

text 类型的数组;来自 CREATE TRIGGER 语句的参数。索引从 0 开始计数并且可以用表达式给出。无效的索引(< 0 或 >= tg_nargs)得到 NULL 值。

其次它们必须返回 NULL,或者返回一个恰好包含触发该触发器的表的结构的记录/行。AFTER 触发器总是返回 NULL 值也没有影响。BEFORE 触发器返回 NULL 时会通知触发器管理器跳过对这一实际行的操作。否则,返回的记录/行会在操作中替换被插入/更新的行。可以直接在 NEW 中替换单个值然后返回它,也可以构造一个完整的新记录/行来返回。

10.2.8. 异常

Postgres没有一个非常聪明的异常处理模型。每当解析器、规划器/优化器或执行器判定一条语句无法再继续处理时,整个事务都会被中止,系统跳回主循环去从客户端应用获取下一条查询。

可以钩进错误机制来注意到这种情况的发生。但目前无法判断究竟是什么导致了中止(输入/输出转换错误、浮点错误、解析错误)。而且此时数据库后端可能处于不一致状态,因此返回上层执行器或发出更多命令可能会损坏整个数据库。即便可以,此时事务已中止的信息已经发送给了客户端应用,恢复操作没有任何意义。

因此,当 PL/pgSQL 在函数或触发器过程执行期间遇到中止时,它目前唯一会做的事情就是写一些额外的 DEBUG 级别日志消息,指出这件事发生在哪个函数中以及在哪里(行号和语句类型)。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.