触发器中拼接表名必须用EXECUTE配合format('%I', tg_table_name)实现安全转义,不可直接变量拼接;示例函数用archive_+TG_TABLE_NAME动态归档,通过USING NEW绑定行数据。

触发器里不能直接用变量拼接表名,必须用 EXECUTE
PostgreSQL 触发器函数中,所有 SQL 语句在函数定义时就被解析和计划,不支持把表名写成变量(比如 INSERT INTO tab_name ... 中的 tab_name 是变量),否则会报错 relation "tab_name" does not exist。唯一可行路径是用 EXECUTE 执行动态拼接的字符串,且必须配合 format() 做安全转义。
EXECUTE + format() 是唯一安全拼表名的方式
手动字符串拼接(如 'INSERT INTO ' || tg_table_name || ' ...')极危险:一旦表名含双引号、点号或特殊字符,就会语法错误甚至 SQL 注入。必须用 format('%I', tg_table_name) —— %I 专用于标识符(表名、列名),自动加双引号并转义。
-
%I处理表名、模式名、列名等标识符(推荐始终使用) -
%L处理字面值(如字符串、数字),加单引号并转义 - 不要混用:
format('INSERT INTO %I VALUES (%L)', 'my_table', 'hello') - 触发器中常用变量:
TG_TABLE_NAME(当前表名)、TG_TABLE_SCHEMA(当前 schema)
完整可运行的触发器函数示例
以下函数在插入时,将新行复制到另一张同结构的归档表(表名由 tg_table_name 动态推导为 archive_原表名):
CREATE OR REPLACE FUNCTION log_to_archive()
RETURNS TRIGGER AS $$
DECLARE
archive_table TEXT := 'archive_' || TG_TABLE_NAME;
BEGIN
EXECUTE format(
'INSERT INTO %I SELECT $1.*',
archive_table
) USING NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;关键点:
-
USING NEW把NEW记录作为参数传入,避免在字符串里拼 JSON 或字段列表 - 不用
SELECT * FROM %I,因为NEW不是表,要用$1.*+USING绑定 - 如果归档表不存在,
EXECUTE会直接报错,需提前建好或加IF EXISTS判断(但IF NOT EXISTS对INSERT无效,得靠异常捕获)
容易踩的坑:权限、事务与性能
动态 SQL 在触发器中执行,不是“透明”的,它受额外约束:
- 执行用户必须对目标表有对应权限(如
INSERT),即使触发器属SECURITY DEFINER,也不能绕过目标表权限检查 -
EXECUTE语句参与当前事务,失败会回滚整个触发操作(包括原 INSERT/UPDATE) - 每次调用都重新解析执行计划(无缓存),高频写入场景下比静态 SQL 开销大;若目标表固定,优先考虑静态 SQL + 多个触发器
-
NEW和OLD在AFTER触发器中仍可用,但在BEFORE中修改NEW后再EXECUTE INSERT,要注意字段一致性
最常被忽略的是标识符转义——漏掉 format('%I', ...) 而直接拼字符串,上线后遇到带下划线、大小写混合或含点的表名就崩,而且错误信息不直观。

















