我在新数据库中启动并尝试了一些事情,并遇到了问题。我是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 。
任何人都可以解释错误吗?
答案 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;
将特殊行值OLD
和NEW
以及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_RELID
或oid
:
如果current_user
定义为唯一,则可以从子查询中删除session_user
。否则,你需要决定选择哪一行 - 如果出现平局 - LIMIT 1
。
触发器函数必需以dad_nome
语句终止。对于ORDER BY
触发器,也可能RETURN
。 The 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将表现为行。