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, прежде чем вставлять ее в текстовый блок.

Есть ли возможность выполнить это, не используя GTTs?

Ответ 1

Вам нужно некоторое метапрограммирование, но с триггерами на системных таблицах, которые не проблема.

Это решение работает, даже если у вас много столбцов.

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 ; ^

Ответ 2

Этот инструмент является решением firebirds для вашей проблемы:

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

В противном случае вы не сможете получить доступ к новому. /old. переменные динамически.

Я исследовал решение, основанное на заявлении, но оно также является тупиковым.

Использование EXECUTE STATEMENT с переменной контекста (NEW или OLD) будет никогда не работают, потому что это доступно только внутри триггера, а не в новом (EXECUTE STATEMENT) не выполняется внутри триггера, хотя он использует то же соединение и транзакцию.