一、表达式
PL/pgSQL 语句所使用的所有表达式,均由服务器的主 SQL 执行器统一处理。
1. 表达式执行原理
- 转换为 SELECT 命令
当执行包含表达式的 PL/pgSQL 语句(如 IF expression THEN ...)时,解释器会将该表达式转换为 SELECT expression 语句,提交至主 SQL 引擎执行计算。
例如:
IF x < y THEN ...
底层等价于执行:
PREPARE statement_name(integer, integer) AS SELECT $1 < $2;
EXECUTE statement_name(x, y);
变量参数化与计划缓存
表达式中的 PL/pgSQL 变量会被替换为查询参数(
$1、$2...),而非直接拼接变量值,从源头规避语法冲突风险;该
SELECT语句的执行计划仅需预编译一次,后续调用可复用此计划并传入不同变量值,有效提升执行性能。
语法限制
表达式对应的 SELECT 命令支持普通 SELECT 的大部分子句,但不 得 包含顶层 UNION 、 INTERSECT 及 EXCEPT 子句,否则将触发语法错误。
- 结果要求
表达式对应的 SELECT 语句必须返回单个列,且返回行数不超过 1 行,具体规则如下:
2. 典型示例
判断表是否非空:
IF count(*) > 0 FROM my_table THEN ...
该表达式等价于执行 SELECT count(*) > 0 FROM my_table,系统将根据查询返回的布尔值执行对应分支逻辑。
二、基础语句
2.1 赋值操作
1. 语法格式
variable { := | = } expression;
赋值符:兼容 PL/SQL 风格的
:=与 SQL 风格的=,两种格式均可正常使用;目标变量范围:可包括普通变量、行/记录类型的字段、数组元素及数组切片。
2. 表达式求值规则
赋值语句中的表达式,同样会转换为
SELECT命令执行;表达式必须返回单个值(若变量是行/记录类型,可返回行值)。
3. 类型转换规则
若表达式结果类型与变量类型不匹配,系统将按以下优先级执行类型转换:
赋值类型转换:使用预设的类型转换规则;
文本转换:若无预设规则,先将结果转为字符串(调用类型的输出函数),再转为目标类型(调用目标类型的输入函数),可能触发运行时错误。
4. 示例
tax := subtotal * 0.06; -- 普通变量赋值
my_record.user_id := 20; -- 记录字段赋值
my_array[1:3] := array[1,2,3]; -- 数组切片赋值
complex_array[n].realpart = 12.3; -- 嵌套数组字段赋值
2.2 执行 SQL 命令
PL/pgSQL 中执行 SQL 命令分为静态命令与动态命令两类,二者核心差异在于是否支持变量参数化及执行计划缓存机制。
1. 执行无返回行的 SQL 命令
对于无返回行的 SQL 命令,可直接编写执行,例如 CREATE TABLE、无 RETURNING 子句的INSERT、UPDATE 等语句,示例如下:
CREATE TABLE mytable (id int primary key, data text);
INSERT INTO mytable VALUES (1,'one'), (2,'two');
2. 执行有返回行的 SQL 命令
针对有返回行的 SQL 命令,需根据返回行数采用对应的处理方式,具体如下表所示:
| 场景 | 处理方式 |
|---|---|
| 最多返回 1 行 | 添加 INTO 子句,将结果赋值给变量 |
| 返回多行 | 作为 FOR 循环的数据源,遍历结果 |
3. 静态 SQL 命令(可优化命令)
适用场景:SELECT 语句、带 RETURNING子句的 INSERT/UPDATE/DELETE/MERGE 语句及 EXPLAIN 语句等。
变量参数化:命令中的 PL/pgSQL 变量会自动替换为查询参数,有效防范 SQL 注入攻击;
计划缓存:执行计划会被系统缓存并复用,显著提升语句重复执行时的效率。
4. 丢弃结果的执行:PERFORM 语句
当需执行查询但无需使用返回结果时(如调用具有副作用的函数),需使用 PERFORM 语句替代 SELECT 语句,语法格式如下:
PERFORM query;
语法规范:将常规
SELECT替换为PERFORM;若查询包含WITH子句,需用括号包裹整个查询语句;状态反馈:执行完成后,系统会更新特殊变量
FOUND,若查询返回至少 1 行结果则设为true,无返回结果则设为false。
注意事项:直接使用 SELECT 语句且不搭配 INTO 子句时,将触发语法错误,需优先使用 PERFORM 语句。
5. 非优化命令(工具类命令)
适用 场景:CREATE INDEX、ALTER TABLE 等工具类命令,此类命令不支持变量参数化。
- 无法自动替换变量,必须通过动态命令拼接字符串执行。
2.3 执行返回单行结果的命令
通过 INTO 子句,可将单行查询结果赋值给记录变量、行变量或标量变量列表,实现结果的精准接收与后续处理。
1. 语法格式
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;
MERGE ... RETURNING expressions INTO [STRICT] target;
2. 关键参数说明
| 参数 | 说明 |
|---|---|
target |
接收结果的变量:单个记录/行变量,或逗号分隔的标量变量列表 |
STRICT |
严格模式:要求命令必须返回且仅返回 1 行结果,若不符合条件则抛出对应异常。 |
3. 执行规则
非严格模式(默认)
返回 1 行时,将结果赋值给
target指定的变量;返回 0 行时,
target变量值被设为NULL;返回多行时,仅提取第一行结果赋值,其余行结果将被丢弃;
可通过
FOUND变量判断是否成功返回数据。
严格模式(STRICT)
返回 0 行时,抛出
NO_DATA_FOUND异常;返回多行时,抛出
TOO_MANY_ROWS异常;执行成功时,
FOUND变量自动设为true。
特殊限制:对于带 RETURNING 子句的 INSERT/UPDATE 等命令,即使未指定 STRICT 模式,返回多行结果也会抛出异常。
4. 语法注意事项
INTO子句位置:SELECT命令中建议置于查询字段前后,其他类型命令建议置于语句末尾,保证语法规范性;与原生
SELECT INTO的区别:PL/pgSQL 中INTO用于结果赋值,原生命令中INTO用于创建表;若需创建表,应使用CREATE TABLE ... AS SELECT语句。
5. 调试优化:print_strict_params
启用该选项后,严格模式下触发异常时,会在 DETAIL 信息中显示具体参数值,为问题排查提供便利:
CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE userid int;
BEGIN
SELECT users.userid INTO STRICT userid FROM users WHERE users.username = username;
RETURN userid;
END;
$$ LANGUAGE plpgsql;
2.4 执行动态命令
当 SQL 命令需动态指定表名、字段名等标识符时,需使用 EXECUTE 语句执行动态拼接的 SQL 字符串,实现灵活适配。
1. 语法格式
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ];
| 参数 | 说明 |
|---|---|
command-string |
动态拼接的 SQL 命令字符串(text 类型) |
target |
接收结果的变量 |
USING |
传入命令的参数,对应字符串中的 $1、$2... |
2. 核心特性
无自动变量替换:需手动将变量拼接至字符串,或通过
USING子句传入参数,不可依赖系统自动替换;无执行计划缓存:每次执行都会重新生成执行计划,适用于动态表/字段等非固定结构的操作场景;
参数化传值(推荐):通过
USING子句传入参数,既能避免 SQL 注入风险,又无需手动转义特殊字符,提升安全性与效率。
3. 关键用法示例
| 场景 | 示例代码 |
|---|---|
| 带参数的动态查询 | EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1' INTO c USING checked_user; |
| 动态表名(安全转义) | EXECUTE format('SELECT count(*) FROM %I', tabname) INTO c; |
| 动态字段赋值 | EXECUTE format('UPDATE tbl SET %I = $1', colname) USING newvalue; |
4. 安全转义函数
拼接动态表名、字段名或变量值时,必须使用指定转义函数,以规避语法错误及 SQL 注入风险,保障命令安全执行:
| 函数 | 用途 | 示例 |
|---|---|---|
quote_ident |
转义标识符(表名、字段名) | quote_ident('my table') → "my table" |
quote_literal |
转义字符串值(NULL 会返回 NULL) |
quote_literal('a''b') → 'a''b' |
quote_nullable |
转义值(NULL 会返回字符串 NULL) |
quote_nullable(NULL) → NULL |
推荐方案:使用 format 函数的 %I(标识符占位符)和 %L(值占位符),简化转义操作,提升代码可读性。
2.5 获取执行状态
PL/pgSQL 提供两种方式获取命令执行后的状态信息,分别适用于详细指标查询与快速结果判断场景。
1. GET DIAGNOSTICS 命令
用于获取详细的系统状态指标,语法格式如下:
GET [ CURRENT ] DIAGNOSTICS variable { = | := } item [ , ... ];
支持的状态项:
| 状态项 | 类型 | 描述 |
|---|---|---|
ROW_COUNT |
bigint |
最近一次 SQL 命令处理的行数 |
PG_CONTEXT |
text |
当前调用栈信息 |
PG_ROUTINE_OID |
oid |
当前函数的 OID |
示例:
GET DIAGNOSTICS cnt = ROW_COUNT; -- 获取受影响行数
2. FOUND 变量
FOUND 为特殊布尔变量,用于快速判断命令是否产生有效结果。该变量在每次函数调用时初始化为 false,并由以下语句更新状态:
| 语句类型 | FOUND 设为 true 的条件 |
|---|---|
SELECT INTO |
成功返回 1 行 |
PERFORM |
返回至少 1 行 |
INSERT/UPDATE/DELETE/MERGE |
至少影响 1 行 |
FETCH/MOVE |
成功获取/移动游标 |
FOR/FOREACH |
循环至少执行 1 次 |
RETURN QUERY |
查询返回至少 1 行 |
注意 事项:EXECUTE 语句会更新 ROW_COUNT 变量值,但不会修改 FOUND 变量的状态,需单独通过其他方式判断结果。
2.6 空操作语句:NULL
空操作语句(NULL;)用于逻辑占位,无任何实际执行效果,语法格式如下:
NULL;
典型用途:标记 IF/EXCEPTION 等分支的空逻辑,明确代码意图,提升代码可读性与可维护性。
示例:
BEGIN
y := x / 0;
EXCEPTION
WHEN division_by_zero THEN
NULL; -- 明确表示忽略错误
END;