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

问题描述 投票:0回答:2

我正在开始并在新数据库中尝试一些事情,但遇到了问题。我是 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

谁能解释一下出了什么问题吗?

database postgresql triggers plpgsql dynamic-sql
2个回答
7
投票

这就是您的触发功能如何正常工作的方式:

CREATE OR REPLACE FUNCTION loca_app.func_historico_mod_usuarios()
  RETURNS trigger
  LANGUAGE plpgsql 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$;

要点

  • 使用

    OLD
    子句将特殊行值
    NEW
    TG_RELID
    以及 EXECUTE 作为
     传递到 
    USING
    。您可能必须将
    TG_RELID
    转换为合适的数据类型。
    tb_modificacoes
    的表定义未公开。或者你真的想要这里别的东西。见下文。
    传递给
    $1
    的 SQL 字符串中的
    $2
    $3
    EXECUTE
    引用
    USING
    子句中的表达式,not 引用函数参数,可以在函数体中使用相同的位置语法引用它们外面
    EXECUTE

  • 使用

    format()
    连接动态 SQL 命令。更清洁、更安全。正确引用和转义identifierscodevalues
    %1$I
    %1$L
    format()
    的格式说明符。 详细阅读说明书。

  • 大小写必须正确!使用大写字母拼写标识符的约定在 Oracle 中是有意义的,其中未加引号的标识符将转换为大写字母。它在 Postgres 中没有用,所有内容都折叠为小写:

  • PostgreSQL 列名区分大小写吗?

  • 请勿在

    ILIKE
    中使用
    DAD_NOME ILIKE 'USU_NASCIMENTO'
    。 Postgres 标识符区分大小写。您可以
    dad_nome
    中有多个匹配值。请改用
    =
    并传递拼写正确的标识符。并确保
    dad_nome
    的定义是唯一的。见下文。

  • 您的评论说:

     MOD_USUARIO ,  -- Translated to: User (ID)
    。但这不是你通过的。说明书:

    TG_RELID

    数据类型

    oid
    ;引发触发器调用的表的对象 ID。

    您可能想使用

    current_user
    session_user
    来代替。参见:

  • 如果定义了

    LIMIT 1
    ,则可以从子查询中删除
    dad_nome
    。否则,您需要决定在平局时选择哪一行 - 使用
    UNIQUE
    
    

  • 触发器函数是
  • 必需的

    ,以用ORDER BY语句终止。也可能是

    RETURN
    作为
    RETURN NULL
    触发器。
    说明书:

    对于在某个事件之后触发的行级触发器,返回值将被忽略。 操作,因此他们可以返回
    AFTER

    
    

  • 相关:

    如何在触发函数中将NEW.*传递给EXECUTE
  • 在 Postgres (plpgsql) 中用单引号替换双引号
旁白:

虽然您是 Postgres 新手,但您可能需要小心使用这种高级动态 SQL。你需要明白你在做什么。


2
投票

据我所知,您需要执行,因为您正在这样做:

NULL

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

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

就这样结束了吗?这样您就无需使用执行,并且 OLD 和 NEW 将表现为行。

© www.soinside.com 2019 - 2024. All rights reserved.