我正在开始并在新数据库中尝试一些事情,但遇到了问题。我是 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。
谁能解释一下出了什么问题吗?
这就是您的触发功能如何正常工作的方式:
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 命令。更清洁、更安全。正确引用和转义identifiers、code和values! %1$I
和 %1$L
是 format()
的格式说明符。 详细阅读说明书。
大小写必须正确!使用大写字母拼写标识符的约定在 Oracle 中是有意义的,其中未加引号的标识符将转换为大写字母。它在 Postgres 中没有用,所有内容都折叠为小写:
请勿在
ILIKE
中使用 DAD_NOME ILIKE 'USU_NASCIMENTO'
。 Postgres 标识符区分大小写。您可以在dad_nome
中有多个匹配值。请改用 =
并传递拼写正确的标识符。并确保 dad_nome
的定义是唯一的。见下文。
您的评论说:
MOD_USUARIO , -- Translated to: User (ID)
。但这不是你通过的。说明书:
TG_RELID
数据类型
;引发触发器调用的表的对象 ID。oid
您可能想使用
current_user
或 session_user
来代替。参见:
如果定义了
LIMIT 1
,则可以从子查询中删除 dad_nome
。否则,您需要决定在平局时选择哪一行 - 使用 UNIQUE
。
,以用ORDER BY
语句终止。也可能是
RETURN
作为 RETURN NULL
触发器。 说明书:对于在某个事件之后触发的行级触发器,返回值将被忽略。 操作,因此他们可以返回
AFTER
。
旁白:
虽然您是 Postgres 新手,但您可能需要小心使用这种高级动态 SQL。你需要明白你在做什么。
据我所知,您需要执行,因为您正在这样做:
NULL
真的需要吗?你不能只使用
OLD.' || TG_ARGV[0] || ' ,
NEW.' || TG_ARGV[0] || ' ,
就这样结束了吗?这样您就无需使用执行,并且 OLD 和 NEW 将表现为行。