如何在触发器函数中将 OLD、NEW 和标识符传递给 EXECUTE?
How to pass OLD, NEW and identifiers to EXECUTE in a trigger function?
我开始在新数据库中尝试一些操作,但遇到了问题。我是 PostgreSQL 新手。
我正在尝试为用户 table 的列中的值更改创建历史记录。这个想法很简单。每当有更新时,都会在另一个table(代表历史)中插入一条新记录。
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.
谁能解释一下哪里出了问题?
是您在此处使用 EXECUTE 导致了问题。当您以您完成的方式准备语句时,OLD 变成了文字,而不是像在准备之外那样成为一个包含一行数据的变量。
正如我所见,您需要执行,因为您正在这样做:
OLD.' || TG_ARGV[0] || ' ,
NEW.' || TG_ARGV[0] || ' ,
真的需要吗?你不能只使用
OLD.USU_NASCIMENTO ,
NEW.USU_NASCIMENTO ,
并完成它?这样您就无需使用 execute 并且 OLD 和 NEW 将表现为行。
这就是您的触发器功能正常工作的方式:
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$I , .%1$I , , )
)', 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;
要点
将特殊行值 OLD
和 NEW
以及 TG_RELID
作为 值 到 EXECUTE
与 USING
子句。您可能必须将 TG_RELID
转换为合适的数据类型。 tb_modificacoes
的 table 定义未公开。或者你真的想要别的东西。见下文。
传递给 EXECUTE
的 SQL 字符串中的 </code>、<code>
和 </code> 指的是 <code>USING
子句中的表达式, 不是 为函数参数,可以在函数体中用相同的位置语法引用 outside EXECUTE
.
使用 format()
. Much cleaner and safer. Quote and escape identifiers, code and values properly! %1$I
and %1$L
are format specifiers for format()
. Read the manual for details.
连接您的动态 SQL 命令
要求大小写正确!您使用大写字母拼写标识符的约定在 Oracle 中是有意义的,其中不带引号的标识符被转换为大写字母。它在 Postgres 中没有用,因为 Postgres 中的所有内容都折叠成小写:
- Are PostgreSQL column names case-sensitive?
不要在 DAD_NOME ILIKE 'USU_NASCIMENTO'
中使用 ILIKE
。 Postgres 标识符区分大小写。您 可以 在 dad_nome
中有多个匹配值。请改用 =
并传递拼写正确的标识符。并确保 dad_nome
被定义为唯一的。见下文。
您的评论是:MOD_USUARIO , -- Translated to: User (ID)
。但这不是你通过的。手册:
TG_RELID
Data type oid
; the object ID of the table that caused the trigger invocation.
您可能想改用 current_user
或 session_user
:
- What is the difference between USER() and SYS_CONTEXT('USERENV','CURRENT_USER')?
如果定义了 dad_nome
UNIQUE
,您可以从子查询中删除 LIMIT 1
。否则你需要决定在平局的情况下选择哪一行 - 使用 ORDER BY
.
触发函数需要以RETURN
语句终止。对于 AFTER
触发器,也可能是 RETURN NULL
。 The manual:
The return value is ignored for row-level triggers fired after an
operation, and so they can return NULL
.
相关:
旁白: 如果您是 Postgres 的新手,您可能需要谨慎使用这种高级动态 SQL。你需要明白你在做什么。
我开始在新数据库中尝试一些操作,但遇到了问题。我是 PostgreSQL 新手。
我正在尝试为用户 table 的列中的值更改创建历史记录。这个想法很简单。每当有更新时,都会在另一个table(代表历史)中插入一条新记录。
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.
谁能解释一下哪里出了问题?
是您在此处使用 EXECUTE 导致了问题。当您以您完成的方式准备语句时,OLD 变成了文字,而不是像在准备之外那样成为一个包含一行数据的变量。
正如我所见,您需要执行,因为您正在这样做:
OLD.' || TG_ARGV[0] || ' ,
NEW.' || TG_ARGV[0] || ' ,
真的需要吗?你不能只使用
OLD.USU_NASCIMENTO ,
NEW.USU_NASCIMENTO ,
并完成它?这样您就无需使用 execute 并且 OLD 和 NEW 将表现为行。
这就是您的触发器功能正常工作的方式:
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$I , .%1$I , , )
)', 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;
要点
将特殊行值
OLD
和NEW
以及TG_RELID
作为 值 到EXECUTE
与USING
子句。您可能必须将TG_RELID
转换为合适的数据类型。tb_modificacoes
的 table 定义未公开。或者你真的想要别的东西。见下文。
传递给EXECUTE
的 SQL 字符串中的</code>、<code>
和</code> 指的是 <code>USING
子句中的表达式, 不是 为函数参数,可以在函数体中用相同的位置语法引用 outsideEXECUTE
.使用
format()
. Much cleaner and safer. Quote and escape identifiers, code and values properly!%1$I
and%1$L
are format specifiers forformat()
. Read the manual for details. 连接您的动态 SQL 命令
要求大小写正确!您使用大写字母拼写标识符的约定在 Oracle 中是有意义的,其中不带引号的标识符被转换为大写字母。它在 Postgres 中没有用,因为 Postgres 中的所有内容都折叠成小写:
- Are PostgreSQL column names case-sensitive?
不要在
DAD_NOME ILIKE 'USU_NASCIMENTO'
中使用ILIKE
。 Postgres 标识符区分大小写。您 可以 在dad_nome
中有多个匹配值。请改用=
并传递拼写正确的标识符。并确保dad_nome
被定义为唯一的。见下文。您的评论是:
MOD_USUARIO , -- Translated to: User (ID)
。但这不是你通过的。手册:TG_RELID
Data type
oid
; the object ID of the table that caused the trigger invocation.您可能想改用
current_user
或session_user
:- What is the difference between USER() and SYS_CONTEXT('USERENV','CURRENT_USER')?
如果定义了
dad_nome
UNIQUE
,您可以从子查询中删除LIMIT 1
。否则你需要决定在平局的情况下选择哪一行 - 使用ORDER BY
.触发函数需要以
RETURN
语句终止。对于AFTER
触发器,也可能是RETURN NULL
。 The manual:The return value is ignored for row-level triggers fired after an operation, and so they can return
NULL
.
相关:
旁白: 如果您是 Postgres 的新手,您可能需要谨慎使用这种高级动态 SQL。你需要明白你在做什么。