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

Loa*_*Loa 2 database postgresql triggers dynamic-sql plpgsql

我正在开始尝试新数据库中的一些东西,并遇到了问题.我是PostgreSQL的新手.

我正在尝试在users表的列中创建值的更改历史记录.这个想法很简单.每当有更新时,都会在另一个表(代表历史记录)中插入新记录.

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');
Run Code Online (Sandbox Code Playgroud)

注意: 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
Run Code Online (Sandbox Code Playgroud)

我有一个LOCA_APP在我的数据库中命名的单一模式,我的PostgreSQL版本是9.5.

任何人都可以解释什么是错的吗?

Erw*_*ter 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         , $4)

              )', TG_ARGV[0])
   USING OLD, NEW, TG_RELID
      , (SELECT dad_id FROM loca_app.tb_dados
         WHERE  dad_nome = TG_ARGV[0]  -- cast? see blow
         LIMIT  1);

   RETURN NULL;  -- only good for AFTER trigger
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

主要观点

  • 通过特殊的行值OLDNEW以及TG_RELID价值观,以EXECUTEUSING条款.您可能必须转换TG_RELID为适合的数据类型.表定义tb_modificacoes未公开.或者你真的想要别的东西.见下文.
    $1,$2$3在传递的SQL字符串中EXECUTE引用USING子句中的表达式,而不是函数参数,可以在函数体外部 使用相同的位置语法引用EXECUTE.

  • 使用连接动态SQL命令format().更干净,更安全.正确引用和转义标识符,代码!%1$I并且%1$L是格式说明符format().阅读手册了解详情.

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

  • 不要使用ILIKEDAD_NOME ILIKE 'USU_NASCIMENTO'.Postgres标识符区分大小写.您可以dad_nome该匹配中拥有多个匹配值.=改为使用并传递正确拼写的标识符.并确保dad_nome定义唯一.见下文.

  • 你的评论说:MOD_USUARIO , -- Translated to: User (ID).但这不是你通过的.手册:

    TG_RELID

    数据类型oid; 导致触发器调用的表的对象ID.

    您可能想要使用current_usersession_user代替:

  • LIMIT 1如果dad_nome定义为唯一,则可以从子查询中删除.否则,你需要决定选择哪一行,以防万一UNIQUE.

  • 触发函数需要ORDER BY语句终止.也可能是RETURN一个RETURN NULL触发器.手册:

    对于在操作之后触发的行级触发器,将忽略返回值,因此它们可以返回AFTER.

有关:

旁白:虽然你是新来的Postgres你可能要小心使用这种先进的动态SQL.你需要了解自己在做什么.