问题 Firebird - 获取触发器内的所有修改字段


我需要获取连续更改的所有值,并在其他“审计”表上发布修改。我是否可以完成此操作,而无需为行中的每个元素编写条件?我知道SQL来自 http://www.firebirdfaq.org/faq133/ 它为您提供验证的所有条件:

select 'if (new.' || rdb$field_name || ' is null and old.' ||
rdb$field_name || ' is not null or new.' || rdb$field_name ||
'is not null and old.' || rdb$field_name || ' is null or new.' ||
rdb$field_name || ' <> old.' || rdb$field_name || ') then'
from rdb$relation_fields
where rdb$relation_name = 'EMPLOYEE';

但这应该写在触发器中。所以,如果我改变一个表,那么我需要修改触发器。

由于FireBird不允许动态增加varchar变量的大小,我考虑将所有值转换为大varchar变量,然后将其插入文本blob中。

没有使用,是否有可能实现这一目标 这些GTT


8185
2017-10-03 10:56


起源



答案:


您需要一些元编程,但在系统表上使用触发器没有问题。

即使您有很多列,此解决方案似乎也可以使用。

set term ^ ;

create or alter procedure create_audit_update_trigger (tablename char(31)) as
    declare sql blob sub_type 1;
    declare fn char(31);
    declare skip decimal(1);
begin
    -- TODO add/remove fields to/from audit table

    sql = 'create or alter trigger ' || trim(tablename) || '_audit_upd for ' || trim(tablename) || ' after update as begin if (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ' or ';
        sql = sql || '(old.' || trim(:fn) || ' is distinct from new.' || trim(:fn) || ')';
        skip = 0;
    end
    sql = sql || ') then insert into ' || trim(tablename) || '_audit (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ',';
        sql = sql || trim(:fn);
        skip = 0;
    end
    sql = sql || ') values (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ',';
        sql = sql || 'new.' || trim(:fn);
        skip = 0;
    end
    sql = sql || '); end';

    execute statement :sql;
end ^

create or alter trigger field_audit for rdb$relation_fields after insert or update or delete as
begin
    -- TODO filter table name, don't include system or audit tables
    -- TODO add insert trigger
    execute procedure create_audit_update_trigger(new.rdb$relation_name);
end ^

set term ; ^

5
2017-10-14 14:14



+1创造力......但它有效吗? 这个邮件列表线程 建议①当一些表被DROPped时,系统表触发消失,而②这种方法至少曾经是崩溃的。 - pilcrow


答案:


您需要一些元编程,但在系统表上使用触发器没有问题。

即使您有很多列,此解决方案似乎也可以使用。

set term ^ ;

create or alter procedure create_audit_update_trigger (tablename char(31)) as
    declare sql blob sub_type 1;
    declare fn char(31);
    declare skip decimal(1);
begin
    -- TODO add/remove fields to/from audit table

    sql = 'create or alter trigger ' || trim(tablename) || '_audit_upd for ' || trim(tablename) || ' after update as begin if (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ' or ';
        sql = sql || '(old.' || trim(:fn) || ' is distinct from new.' || trim(:fn) || ')';
        skip = 0;
    end
    sql = sql || ') then insert into ' || trim(tablename) || '_audit (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ',';
        sql = sql || trim(:fn);
        skip = 0;
    end
    sql = sql || ') values (';

    skip = 1;
    for select rdb$field_name from rdb$relation_fields where rdb$relation_name = :tablename into :fn do
    begin
        if (skip = 0) then sql = sql || ',';
        sql = sql || 'new.' || trim(:fn);
        skip = 0;
    end
    sql = sql || '); end';

    execute statement :sql;
end ^

create or alter trigger field_audit for rdb$relation_fields after insert or update or delete as
begin
    -- TODO filter table name, don't include system or audit tables
    -- TODO add insert trigger
    execute procedure create_audit_update_trigger(new.rdb$relation_name);
end ^

set term ; ^

5
2017-10-14 14:14



+1创造力......但它有效吗? 这个邮件列表线程 建议①当一些表被DROPped时,系统表触发消失,而②这种方法至少曾经是崩溃的。 - pilcrow


此工具是针对您的问题的firebirds解决方案:

http://www.upscene.com/products.audit.iblm_main.php

否则您无法访问new./old。变量动态。

我调查了基于执行语句的解决方案,但它也是一个死胡同。

将EXECUTE STATEMENT与上下文变量(NEW或OLD)一起使用即可   永远不会工作,因为它只能在触发器中使用,而不是在新的触发器中   语句(EXECUTE STATEMENT)不在触发器内执行,   虽然它使用相同的连接和事务。


4
2017-10-10 14:31



我不能使用其他工具。它需要在“内部”制作。谢谢。 - RBA
在这种情况下,我认为最好的方法是创建一些存储过程来重新生成触发器,并安排它们在每日基础上运行(或者在数据库上运行DDL-s时)。 - Lajos Veres