如何在触发函数中将OLD,NEW和标识符传递给EXECUTE?

时间:2016-05-21 01:32:22

标签: database postgresql triggers plpgsql dynamic-sql

我在新数据库中启动并尝试了一些事情,并遇到了问题。我是PostgreSQL的新手。

我尝试在用户表的列中创建值更改的历史记录。这个想法很简单。只要有更新,就会在另一个表中插入一条新记录(代表历史记录)。

DROP FUNCTION IF EXISTS LOCA_APP.FUNC_HISTORICO_MOD_USUARIOS() CASCADE;
CREATE OR REPLACE FUNCTION LOCA_APP.FUNC_HISTORICO_MOD_USUARIOS() RETURNS TRIGGER
AS $$ 
BEGIN
    EXECUTE 'INSERT INTO LOCA_APP.TB_MODIFICACOES (
        MOD_MOMENTO ,             -- Translated to: Moment
        MOD_VALOR_ANTERIOR ,      -- Translated to: Old value
        MOD_VALOR_ATUAL ,         -- Translated to: New value
        MOD_USUARIO ,             -- Translated to: User (ID)
        MOD_DADO)                 -- Translated to: Data (Column Name - ID)
    VALUES(
        now() , 
        OLD.' || TG_ARGV[0] || ' , 
        NEW.' || TG_ARGV[0] || ' , 
        '|| TG_RELID || ' ,
        (SELECT DAD_ID FROM LOCA_APP.TB_DADOS WHERE DAD_NOME ILIKE ''' || TG_ARGV[0] || ''') );';
END $$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS TRIG_HISTORICO_USU_ID ON LOCA_APP.TB_USUARIOS CASCADE;
CREATE TRIGGER TRIG_HISTORICO_USU_ID AFTER UPDATE OF USU_ID ON LOCA_APP.TB_USUARIOS
FOR EACH ROW EXECUTE PROCEDURE LOCA_APP.FUNC_HISTORICO_MOD_USUARIOS('USU_ID');

注意: EXECUTE评论是为了更好地理解这里。原始代码没有这些评论。

pgAdmin告诉我:

ERROR:  missing FROM-clause entry for table "old"
LINE 9:   OLD.USU_NASCIMENTO , 
          ^
QUERY:  INSERT INTO LOCA_APP.TB_MODIFICACOES (
      MOD_MOMENTO ,
      MOD_VALOR_ANTERIOR ,
      MOD_VALOR_ATUAL ,
      MOD_USUARIO , 
      MOD_DADO)
  VALUES(
      now() , 
      OLD.USU_NASCIMENTO , 
      NEW.USU_NASCIMENTO , 
      22664 ,
      (SELECT DAD_ID FROM LOCA_APP.TB_DADOS WHERE DAD_NOME ILIKE 'USU_NASCIMENTO') );
CONTEXT:  PL/pgSQL function loca_app.func_historico_mod_usuarios() line 3 at EXECUTE

********** Error **********

ERROR: missing FROM-clause entry for table "old"
SQL state: 42P01
Context: PL/pgSQL function loca_app.func_historico_mod_usuarios() line 3 at EXECUTE

我的数据库中有一个名为LOCA_APP的模式,而我的PostgreSQL版本 9.5

任何人都可以解释错误吗?

2 个答案:

答案 0 :(得分:5)

这是你的触发功能正常工作的方式:

CREATE OR REPLACE FUNCTION loca_app.func_historico_mod_usuarios()
  RETURNS trigger AS
$func$
BEGIN
   EXECUTE format(
      'INSERT INTO loca_app.tb_modificacoes
              (mod_momento, mod_valor_anterior, mod_valor_atual, mod_usuario, mod_dado)
       VALUES (now()      , $1.%1$I           , $2.%1$I        , $3         ,
                                                             (SELECT dad_id
                                                              FROM   loca_app.tb_dados
                                                              WHERE  dad_nome = %1$L
                                                              LIMIT  1) 
              )', TG_ARGV[0])
   USING OLD, NEW, TG_RELID;

   RETURN NULL;  -- only good for AFTER trigger
END
$func$ LANGUAGE plpgsql;

重点

  • 将特殊行值OLDNEW以及TG_RELID作为 传递给{{1使用EXECUTE子句。您可能必须将USING强制转换为拟合数据类型。 TG_RELID的表定义未公开。或者你真的想要别的东西。见下文。
    传递给tb_modificacoes的SQL字符串中的$1$2$3引用EXECUTE子句中的表达式,来运行参数,可以在函数体 outside USING中使用相同的位置语法引用。

  • 使用format()连接动态SQL命令。更干净,更安全。正确引用并转义标识符代码EXECUTE%1$I%1$L的格式说明符。 Read the manual for details.

  • 需要正确的情况!在Oracle中,使用大写字母拼写标识符的约定是有意义的,其中不带引号的标识符将转换为大写字母。它在Postgres中没有用,它将所有内容折叠成小写字母:

  • 请勿在{{1​​}}中使用format()。 Postgres标识符区分大小写。您可以ILIKE中匹配多个匹配值。请改用DAD_NOME ILIKE 'USU_NASCIMENTO'并正确传递拼写错误的标识符。并确保dad_nome被定义为唯一。见下文。

  • 您的评论说:=。但这不是你通过的。手册:

      

    dad_nome

         

    数据类型MOD_USUARIO , -- Translated to: User (ID);导致触发器调用的表的对象ID。

    您可能希望改为使用TG_RELIDoid

  • 如果current_user定义为唯一,则可以从子查询中删除session_user。否则,你需要决定选择哪一行 - 如果出现平局 - LIMIT 1

  • 触发器函数必需dad_nome语句终止。对于ORDER BY触发器,也可能RETURNThe manual:

      

    对于在a之后触发的行级触发器,将忽略返回值   操作,因此他们可以返回RETURN NULL

相关:

旁白:虽然您不熟悉Postgres,但您可能需要仔细使用这种高级动态SQL。你需要了解自己在做什么。

答案 1 :(得分:2)

这是你在EXECUTE中使用导致问题的原因。当您以完成OLD的方式准备语句时,OLD变为文字,而不是保存一行数据的变量,因为它将在准备之外。

我认为你需要执行,因为你这样做:

    OLD.' || TG_ARGV[0] || ' , 
    NEW.' || TG_ARGV[0] || ' , 

真的需要吗?你不能只使用

    OLD.USU_NASCIMENTO , 
    NEW.USU_NASCIMENTO , 

并完成它?这样就可以避免使用execute和OLD,NEW将表现为行。