2013-10-03 2 views
10

Мне нужно получить все значения, которые были изменены в строке и опубликовать изменения в другой таблице аудита. Могу ли я выполнить это, не записывая условия для каждого элемента из строки? Я знаю SQL из http://www.firebirdfaq.org/faq133/, который дает вам все условия для проверки:Firebird - получить все измененные поля внутри триггера

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?

ответ

4

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

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

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

+1 для изобретательности ... но это работает? [Этот список рассылки] (http://groups.yahoo.com/neo/groups/firebird-support/conversations/topics/100294) предполагает, что ① системная таблица запускается, когда какая-либо таблица DROPped, и ② этот подход в наименее испорченный. – pilcrow

4

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

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

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

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

Использование ВЫПОЛНИТЬ ЗАЯВЛЕНИЕ с переменной контекста (новый или старый) будет никогда не работать, причиной того, что доступно только внутри триггера, а не в новом заявлении (Выполнить заявление) не выполняется внутри триггера, хотя он использует то же соединение и транзакцию.

+0

Я не могу использовать другой инструмент. Его нужно сделать «внутри». Спасибо. – RBA

+1

В этом случае я считаю, что лучший способ создать несколько хранимых процедур для восстановления триггеров и запланировать их запуск на ежедневной основе (или при запуске DDL-файлов в вашей базе данных). –

Смежные вопросы