在触发器函数中插入动态表名

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.

您在几乎成功的版本中收到错误消息,因为OLDEXECUTE不可见。而且,如果您想像尝试的那样连接分解行的各个值,则必须使用 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: USINGEXECUTE 语法的一部分。【参考方案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 触发器函数中获取表名? [关闭]

如何在 PostgreSQL 触发器函数中获取表名?

8.6 UPDATE触发器可以判断在修改某个执行列是才触发,用到了函数update(),该函数的参数是创建触发器的表名,如在employee表创建触发器,判断不允许修改employeeID字段

触发器创建删除等操作

mysql--触发器