在触发器函数中插入动态表名
Posted
技术标签:
【中文标题】在触发器函数中插入动态表名【英文标题】:INSERT with dynamic table name in trigger function 【发布时间】:2011-12-16 09:24:25 【问题描述】:我不确定如何实现以下目标:
CREATE OR REPLACE FUNCTION fnJobQueueBEFORE() RETURNS trigger AS $$
DECLARE
shadowname varchar := TG_TABLE_NAME || 'shadow';
BEGIN
INSERT INTO shadowname VALUES(OLD.*);
RETURN OLD;
END;
$$
LANGUAGE plpgsql;
即将值插入到具有动态生成名称的表中。 执行上面的代码产生:
ERROR: relation "shadowname" does not exist
LINE 1: INSERT INTO shadowname VALUES(OLD.*)
这似乎表明变量没有被扩展/允许作为表名。我在 Postgres 手册中没有找到对此的参考。
我已经像这样尝试过EXECUTE
:
EXECUTE 'INSERT INTO ' || quote_ident(shadowname) || ' VALUES ' || OLD.*;
但没有运气:
ERROR: syntax error at or near ","
LINE 1: INSERT INTO personenshadow VALUES (1,sven,,,)
RECORD
类型似乎丢失了:OLD.*
似乎被转换为字符串并被重新解析,导致各种类型问题(例如 NULL
值)。
有什么想法吗?
【问题讨论】:
【参考方案1】:PostgreSQL 9.1 或更高版本
format()
具有转义标识符的内置方法。比以前简单:
CREATE OR REPLACE FUNCTION foo_before()
RETURNS trigger AS
$func$
BEGIN
EXECUTE format('INSERT INTO %I.%I SELECT $1.*'
, TG_TABLE_SCHEMA, TG_TABLE_NAME || 'shadow')
USING OLD;
RETURN OLD;
END
$func$ LANGUAGE plpgsql;
也适用于 VALUES
表达式。
db小提琴here 老sqlfiddle.
要点
使用format()
或quote_ident()
引用标识符(仅在必要时自动引用),从而防止SQL injection 和简单的语法违规。
这是必要的,即使是您自己的表名!
模式限定表名。根据当前的search_path
setting,裸表名可能会解析为不同架构中的另一个同名表。
对动态 DDL 语句使用 EXECUTE
。
使用 USING
子句安全地传递 值。
请参阅Executing Dynamic Commands in plpgsql上的精美手册。
请注意,触发器函数中的RETURN OLD;
是触发器BEFORE DELETE
所必需的。 Details in the manual here.
您在几乎成功的版本中收到错误消息,因为OLD
在EXECUTE
中不可见。而且,如果您想像尝试的那样连接分解行的各个值,则必须使用 quote_literal()
准备每一列的文本表示,以保证语法有效。您还必须事先知道列名才能处理它们或查询系统目录 - 这与您拥有一个简单的动态触发函数的想法背道而驰...
我的解决方案避免了所有这些并发症。也简化了一点。
PostgreSQL 9.0 或更早版本
format()
尚不可用,所以:
CREATE OR REPLACE FUNCTION foo_before()
RETURNS trigger AS
$func$
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA)
|| '.' || quote_ident(TG_TABLE_NAME || 'shadow')
|| ' SELECT $1.*'
USING OLD;
RETURN OLD;
END
$func$ LANGUAGE plpgsql;
相关:
How to dynamically use TG_TABLE_NAME in PostgreSQL 8.2?【讨论】:
像魅力一样工作。我想我需要进一步阅读:) 不过,您的manual link 中有一个错字。无法更正,因为 *** 认为一个字符编辑太小了:/ @Johan:啊,不知道。所以,我得继续问这么好的问题g! @ErwinBrandstetter :我的模式和表名都是大写的。因此,在执行此查询时,由于双引号EXECUTE format ('INSERT INTO %I SELECT $1.*', 'SCHEMA_NAME' || '.' || TG_TABLE_NAME || '_SHADOW') USING (row)
放错位置而出现错误。错误是:ERROR: relation "SCHEMA_NAME.TABLE_NAME" does not exist
@Siddharth:架构包含在单独的变量 TG_TABLE_SCHEMA
中。似乎您在正确转义之前连接了两个 before。考虑:***.com/a/10711349/939860。如果问题仍然不清楚,请开始一个新问题。您可以随时参考此答案的上下文和/或添加评论,链接到相关问题。
@Jian: USING
是EXECUTE
语法的一部分。【参考方案2】:
我只是偶然发现了这个,因为我正在寻找一个动态的INSTEAD OF DELETE
触发器。为了感谢您的提问和回答,我将发布我的 Postgres 9.3 解决方案。
CREATE OR REPLACE FUNCTION set_deleted_instead_of_delete()
RETURNS TRIGGER AS $$
BEGIN
EXECUTE format('UPDATE %I set deleted = now() WHERE id = $1.id', TG_TABLE_NAME)
USING OLD;
RETURN NULL;
END;
$$ language plpgsql;
【讨论】:
您能否解释一下,您的解决方案带来了哪些新概念或想法? 再次阅读我的帖子,我发现我并不清楚它的上下文。我的帖子是针对正在搜索动态删除触发器功能的帖子。当 Google 将此问题显示为热门问题时,我正在搜索此问题。 本来想在答案下发表评论,但是代码高亮不起作用 重要的是要注意这仅适用于INSTEAD OF DELETE
触发器。我冒昧地作出相应的澄清。以上是关于在触发器函数中插入动态表名的主要内容,如果未能解决你的问题,请参考以下文章
如何在 SQL Server 触发器函数中获取表名? [关闭]
8.6 UPDATE触发器可以判断在修改某个执行列是才触发,用到了函数update(),该函数的参数是创建触发器的表名,如在employee表创建触发器,判断不允许修改employeeID字段