【问题标题】:Set a temporary global variable during a Postgres query在 Postgres 查询期间设置临时全局变量
【发布时间】:2019-04-15 07:23:08
【问题描述】:

是否可以在查询期间设置可由 TRIGGER 过程捕获的变量(仅对相关查询有效)?

比如我想记录一个查询的执行者的ID(current_user总是一样的)。 所以我会做这样的事情:

tbl_executor (
  id   PRIMARY KEY,
  name VARCHAR
);
tbl_log (
  executor REFERENCE tbl_executor(id),
  op VARCHAR
);
tbl_other ...

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( ID_VAR_OF_THIS_QUERY ,TG_OP))

现在,如果我运行如下查询:

INSERT INTO tbl_other 
VALUES(.......) - and set ID_VAR_OF_THIS_QUERY='id of executor' -

我得到以下结果:

           tbl_log
-----------------------------
id                | op      |
-----------------------------
'id of executor'  | 'INSERT'|

我希望我已经提出了这个想法......而且我认为这几乎不可行......但是有人可以帮助我吗?

【问题讨论】:

  • 你可以用create if not exists/on commit drop创建一个临时表,然后把数据放在那里。
  • 或者,您可以拥有一个共享表,其中 PK 是当前的pg_backend_pid(),并将数据存储在其中。

标签: database postgresql


【解决方案1】:

回答问题

你可以像这样SET a (customized option):

SET myvar.role_id = '123';

但这需要一个 literal 值。还有函数set_config()Quoting the manual:

set_config(setting_name, new_value, is_local) ... 设置参数并返回新值

set_config 将参数setting_name 设置为new_value。如果is_localtrue,则新值将仅适用于当前事务。

相应地,使用SHOWcurrent_setting() 读取选项值。相关:

但是您的触发器位于错误的表 (tbl_executor) 上,语法错误。看起来像 Oracle 代码,您可以在其中直接向 CREATE TRIGGER 提供代码。在 Postgres 中,您首先需要一个触发函数

所以:

CREATE OR REPLACE FUNCTION trg_log_who()
  RETURNS trigger AS
$func$
BEGIN
   INSERT INTO tbl_log(executor, op)
   VALUES(current_setting('myvar.role_id')::int, TG_OP);  -- !

   RETURN NULL;  -- irrelevant for AFTER trigger     
END
$func$  LANGUAGE plpgsql;

您的示例设置需要类型转换 ::int
那么:

CREATE TRIGGER trg_log_who
AFTER INSERT OR UPDATE OR DELETE ON tbl_other  -- !
FOR EACH ROW EXECUTE PROCEDURE trg_log_who();  -- !

最后,从表tbl_executor中获取id来设置变量:

BEGIN;
SELECT set_config('myvar.role_id', id::text, true)   -- !
FROM   tbl_executor
WHERE  name = current_user;

INSERT INTO tbl_other VALUES( ... );
INSERT INTO tbl_other VALUES( ... );
--  more?
COMMIT;

set_config() 的第三个参数(is_local) 设置为true,使其按要求session-local。 (相当于SET LOCAL。)

但为什么要按?按声明来写似乎更合理?

...
FOR EACH STATEMENT EXECUTE PROCEDURE trg_foo();

不同的方法

除此之外,我会考虑另一种方法:一个返回 id 列默认值的简单函数:

CREATE OR REPLACE FUNCTION f_current_role_id()
  RETURNS int LANGUAGE sql STABLE AS
'SELECT id FROM tbl_executor WHERE name = current_user';

CREATE TABLE tbl_log (
  executor int DEFAULT f_current_role_id() REFERENCES tbl_executor(id)
, op VARCHAR
);

然后,在触发函数中,忽略executor列;将自动填充:

...
   INSERT INTO tbl_log(op) VALUES(TG_OP);
...

注意current_usersession_user 之间的区别。见:

【讨论】:

  • @Phocs:你已经接受了。但请考虑改进后的答案。
【解决方案2】:

一种选择是创建一个共享表来保存这些信息。由于它是每个连接的,所以主键应该是pg_backend_pid()


    create table connection_global_vars(
       backend_pid bigint primary key,
       id_of_executor varchar(50)
    );
    insert into connection_global_vars(backend_pid) select pg_backend_pid() on conflict do nothing;
    update connection_global_vars set id_of_executor ='id goes here' where backend_pid = pg_backend_pid();


    -- in the trigger: 

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( (select id_of_executor from connection_global_vars where backend_pid = pg_backend_pid()) ,TG_OP))

另一种选择是创建一个临时表(每个连接都存在)。

 create temporary table if not exists connection_global_vars(
       id_of_executor varchar(50)
    ) on commit delete rows;
    insert into connection_global_vars(id_of_executor) select null where not exists (select 1 from connection_global_vars);
    update connection_global_vars set id_of_executor ='id goes here';



    -- in the trigger: 

CREATE TRIGGER t AFTER INSERT OR UPDATE OR DELETE ON tbl_executor 
FOR EACH ROW 
EXECUTE PROCEDURE (INSERT INTO tbl_log VALUES( (select id_of_executor from connection_global_vars where backend_pid = pg_backend_pid()) ,TG_OP))

特别是对于 PostgreSQL,它可能不会对性能产生太大影响,只是未记录的临时表可能会稍微快一些。

如果您在无法识别它是单行表时遇到性能问题,您可以运行分析。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    • 2016-09-03
    • 2019-07-18
    • 2020-07-23
    • 1970-01-01
    相关资源
    最近更新 更多