触发器似乎在事务之后触发,而不是在命令/语句之后触发

时间:2014-12-02 23:21:45

标签: sql postgresql triggers transactions common-table-expression

我有两个表格foobar以及after trigger foo,它会更改bar表格上的内容。

create table foo(id serial primary key, _key char(128) not null);
create table bar(id bigserial primary key, _key_p char(256) not null);

因此,当我有一个事务,例如调用下面的函数时,触发器在事务之后触发,而不是语句或相关的dml操作。
foo表的触发器:

create or replace function foo_trg_func()returns trigger as $$
declare k_p char(256);begin
k_p:=(select res from prepare_pa(new.key));
insert into bar(_key_p) values(k_p);--insert it to the bar
end $$ language plpgsql;
--
create trigger foo_trg
after insert on foo
for each row execute procedure foo_trg_func();

样本函数/事务

create or replace function `bas`(int,character(128))returns int as $$
-- some commands
with res as (select res as "d" from c_key($1,$2)),
-- attemp to insert into foo and expect the insertion to bar too
ins as (insert into foo(_key) select d from res returning 1)  --line[5]
-- check the effect of the foo_trg
select _key_p from bar,res where _key_p=res.d;    --line[7]

$$ language sql

调用触发器并通过bar触发器将数据插入foo表,但在函数调用之后,我无法在第7行获得触发器插入的结果。 我现在该怎么办?

我还必须提到可以将触发器标记为相反或之前,但它会引起许多变化,所以我想知道它是否可以用{{1触发器。

1 个答案:

答案 0 :(得分:0)

为什么?

首先,您必须知道您正在运行单个语句(包含多个CTE。)

单个语句alls的公用表表达式与数据库的相同的基本快照相同。

每个CTE(或最终命令)都可以重用先前CTE(内部临时表)的输出,但对其他CTE的基础表的影响不可见。您的最终SELECT无法看到您的CTE ins对基础表格产生任何影响。

如果您之后在单独的语句中运行SELECT(但仍在同一个事务中 - 可以在同一个函数中),您将看到效果。

此相关答案的详细信息:

您案件的解决方案

现在您有两个相互矛盾的要求:

  1. 您希望将更改的行用于基础表(不能从CTE的RETURNING子句中获得,因为这些是来自另一个表的触发器的副作用)。但是这些变化同一声明中不可见。

  2. 您希望将其与CTE返回的值与RETURNING子句相结合,但 在CTE语句之外不可见。

  3. 通过从TEMP TABLE中的CTE实现派生表来解决此问题:

    CREATE OR REPLACE FUNCTION bas (int, text)
      RETURNS SETOF int AS
    $func$
    BEGIN
    
    CREATE TEMP TABLE foo_tmp (LIKE foo) ON COMMIT DROP;
    
    WITH ins AS (
       INSERT INTO foo(_key)
       SELECT res FROM c_key($1,$2) f
       RETURNING foo.*
       )
    INSERT INTO foo_tmp
    SELECT * FROM ins;
    
    RETURN QUERY
    SELECT b._key_p
    FROM   bar b
    JOIN   foo_tmp f ON b._key_p = f._key;
    
    END
    $func$ LANGUAGE plpgsql;
    

    或者,你可以删除触发器并手动执行INSERT(如果这是一个单一的入口点)。

    旁白

    • 你几乎肯定不想要character(128)。使用varchar(128),或varchartext

    • 标识符(`res`)没有反引号。这看起来像MySQL的溢出,在Postgres中是无稽之谈。 Use double-quotes instead(仅在必要时)。

    • 您的触发功能可以更简单/更便宜:

      CREATE OR REPLACE FUNCTION foo_trg_func()
        RETURNS trigger AS
      $func$
      BEGIN
      INSERT INTO bar(_key_p)
      SELECT res FROM prepare_pa(NEW.key);
      END
      $func$ LANGUAGE plpgsql;
      

      如果prepare_pa()返回单个值,则更简单。