2012-06-29 4 views
2

Мне нужно создать trigger в oracle 11g для аудита таблицы.Аудит 50 столбцов с использованием оракульного триггера

У меня есть стол с 50 columns, который должен быть audited.

  • Для every new insert в таблицу, мне нужно поставить запись в audit table (1 row).
  • Для every update предположим, что я обновляю 1st 2nd column, затем он создаст две записи в аудите с ее old value and new value.

структура таблицы аудита будет

id  NOT NULL 
attribute NOT NULL 
OLD VALUE NOT NULL 
NEW VALUE NOT NULL 
cre_date NOT NULL 
upd_date NULL 
cre_time NOT NULL 
upd_time NULL 

В случае insert только первичный ключ (основная таблица) т.е. id и cre_date and cre_time должны быть заполнены и attribute равно *, в случай обновления, предположим, что colA и colB обновлены, тогда все необходимо заполнить. В этом случае будут созданы две записи с атрибутом первой записи colA и соответствующим значению old and new, а также для colB

Теперь мое решение аудита not very optimized, я создал row level trigger, который будет проверять для каждого и каждые 50 столбцов этой таблицы ли она была changed или не основано на его new and old value (если -else), и он будет заполнить таблицу аудита. Я не доволен своим soltuion, поэтому я публикую здесь. Другое решение, которое я видел в ссылке ниже:

http://stackoverflow.com/questions/1421645/oracle-excluding-updates-of-one-column-for-firing-a-trigger

Это не работает в моем случае, я сделал POC для этого, как показано ниже:

create table temp12(id number); 

create or replace trigger my_trigger 
after update or insert on temp12 
for each row 
declare 
    TYPE tab_col_nt IS table of varchar2(30); 

    v_tab_col_nt tab_col_nt; 

begin 
v_tab_col_nt := tab_col_nt('id','name'); 

    for r in v_tab_col_nt.first..v_tab_col_nt.last 
    loop 
     if updating(r) then 
     insert into data_table values(1,'i am updating'||r); 
     else 
     insert into data_table values(2,'i am inserting'||r); 
     end if; 
    end loop; 

end; 

В случае обновление - это вызов другой части, я не знаю почему. Может ли это быть возможным благодаря compound trigger

ответ

5

Ваша непосредственная проблема с else всегда будут вызван потому, что вы используете переменный индекс r напрямую, а не отрываясь соответствующим имя столбца:

for r in v_tab_col_nt.first..v_tab_col_nt.last 
loop 
    if updating(v_tab_col_nt(r)) then 
     insert into data_table values(1,'i am updating '||v_tab_col_nt(r)); 
    else 
     insert into data_table values(2,'i am inserting '||v_tab_col_nt(r)); 
    end if; 
end loop; 

Вы также только показывающим id столбца в вашем создании таблицы, поэтому, когда r составляет 2, он всегда будет говорить, что он вставляет name, никогда не обновляясь. Что еще более важно, если у вас есть столбец name и были только обновления, что для данного id, этот код будет показывать id как вставку, если он не изменился. Вам нужно разделить вставки/обновления на отдельные блоки:

if updating then 
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop 
     if updating(v_tab_col_nt(r)) then 
      insert into data_table values(1,'i am updating '||v_tab_col_nt(r)); 
     end if; 
    end loop; 
else /* inserting */ 
    for r in v_tab_col_nt.first..v_tab_col_nt.last loop 
     insert into data_table values(2,'i am inserting '||v_tab_col_nt(r)); 
    end loop; 
end if; 

Это еще говорят, что вставив name даже если столбец не существует, но я полагаю, что это ошибка, и я предполагаю, что вы пытаетесь чтобы заполнить список имен от user_tab_columns в любом случае, если вы действительно хотите попробовать сделать его динамичным.


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

create table temp12(id number, col1 number, col2 number, col3 number); 
create table temp12_audit(id number, col1 number, col2 number, col3 number, 
    action char(1), when timestamp); 

create or replace trigger temp12_trig 
before update or insert on temp12 
for each row 
declare 
    l_action char(1); 
begin 
    if inserting then 
     l_action := 'I'; 
    else 
     l_action := 'U'; 
    end if; 

    insert into temp12_audit(id, col1, col2, col3, action, when) 
    values (:new.id, :new.col1, :new.col2, :new.col3, l_action, systimestamp); 
end; 
/

insert into temp12(id, col1, col2, col3) values (123, 1, 2, 3); 
insert into temp12(id, col1, col2, col3) values (456, 4, 5, 6); 
update temp12 set col1 = 9, col2 = 8 where id = 123; 
update temp12 set col1 = 7, col3 = 9 where id = 456; 
update temp12 set col3 = 7 where id = 123; 

select * from temp12_audit order by when; 

     ID  COL1  COL2  COL3 A WHEN 
---------- ---------- ---------- ---------- - ------------------------- 
     123   1   2   3 I 29/06/2012 15:07:47.349 
     456   4   5   6 I 29/06/2012 15:07:47.357 
     123   9   8   3 U 29/06/2012 15:07:47.366 
     456   7   5   9 U 29/06/2012 15:07:47.369 
     123   9   8   7 U 29/06/2012 15:07:47.371 

Таким образом, у вас есть одна строка аудита для каждого предпринятого действия, две вставки и три обновления. Но вы хотите увидеть отдельные данные для каждого измененного столбца.

select distinct id, when, 
    case 
     when action = 'I' then 'Record inserted' 
     when prev_value is null and value is not null 
      then col || ' set to ' || value 
     when prev_value is not null and value is null 
      then col || ' set to null' 
     else col || ' changed from ' || prev_value || ' to ' || value 
    end as change 
from (
    select * 
    from (
     select id, 
      col1, lag(col1) over (partition by id order by when) as prev_col1, 
      col2, lag(col2) over (partition by id order by when) as prev_col2, 
      col3, lag(col3) over (partition by id order by when) as prev_col3, 
      action, when 
     from temp12_audit 
    ) 
    unpivot ((value, prev_value) for col in (
     (col1, prev_col1) as 'col1', 
     (col2, prev_col2) as 'col2', 
     (col3, prev_col3) as 'col3') 
    ) 
) 
where value != prev_value 
    or (value is null and prev_value is not null) 
    or (value is not null and prev_value is null) 
order by when, id; 

     ID WHEN      CHANGE 
---------- ------------------------- ------------------------- 
     123 29/06/2012 15:07:47.349 Record inserted 
     456 29/06/2012 15:07:47.357 Record inserted 
     123 29/06/2012 15:07:47.366 col1 changed from 1 to 9 
     123 29/06/2012 15:07:47.366 col2 changed from 2 to 8 
     456 29/06/2012 15:07:47.369 col1 changed from 4 to 7 
     456 29/06/2012 15:07:47.369 col3 changed from 6 to 9 
     123 29/06/2012 15:07:47.371 col3 changed from 3 to 7 

Пять записей аудита превратились в семь обновлений; три оператора обновления показывают пять измененных столбцов. Если вы будете использовать это много, вы можете подумать о том, чтобы сделать это в представлении.

Так что давайте немного разобьем это. Ядро это внутренний выбор, который использует lag(), чтобы получить предыдущее значение строки из предыдущей записи аудита для этого id:

 select id, 
      col1, lag(col1) over (partition by id order by when) as prev_col1, 
      col2, lag(col2) over (partition by id order by when) as prev_col2, 
      col3, lag(col3) over (partition by id order by when) as prev_col3, 
      action, when 
     from temp12_audit 

Это дает нам временный вид, который имеет все таблицы аудита столбцов плюс столбец лаг, который затем используется для unpivot() операции, которую вы можете использовать, как вы добавили вопрос, как 11g:

select * 
    from (
     ... 
    ) 
    unpivot ((value, prev_value) for col in (
     (col1, prev_col1) as 'col1', 
     (col2, prev_col2) as 'col2', 
     (col3, prev_col3) as 'col3') 
    ) 

Теперь у нас есть временный вид, который имеет id, action, when, col, value, prev_value колонны; в этом случае, поскольку у меня есть только три столбца, которые в три раза превышают количество строк в таблице аудита. Наконец, внешние фильтры выбора, которые позволяют включать только строки, в которых значение изменилось, то есть где value != prev_value (с учетом нулей).

select 
    ... 
from (
    ... 
) 
where value != prev_value 
    or (value is null and prev_value is not null) 
    or (value is not null and prev_value is null) 

Я использую case просто напечатать что-нибудь, но, конечно, вы можете делать все, что вы хотите с данными. Требуется distinct, потому что записи insert в таблице аудита также преобразуются в три строки в неизолированном виде, и я показываю один и тот же текст для всех трех из моего первого предложения case.

+0

спасибо за хорошее объяснение, я никогда не думаю о том, что это можно сделать с помощью univot, но вопрос в том, что это оптимизируется, потому что есть миллионы транзакций, совершаемых за один день, если я вызову этот запрос, тогда это будет оптимизировано? за исключением использования простого if-else в триггере –

1

так, как я хотел бы сделать это:

  1. создать таблицу аудита, которая параллельна к существующей исходной таблицы.
  2. добавить столбцы времени и пользователя в эту таблицу аудита.
  3. всякий раз, когда исходная таблица вставлена ​​или обновлена, просто вставьте в таблицу аудита.
  4. В таблице audi должен быть триггер, чтобы установить временную метку и значения пользователя - все остальные значения входят в качестве новых значений.

Тогда вы можете запросить в любое время, кто что сделал, и когда.

+0

: Мне нужно получить доступ к этой таблице аудита из графического интерфейса пользователя, и на основании этого мне нужно показать результат на основе изменения уровня столбца, поэтому этот проект будет не оптимизирован, когда я сравню старое значение и новое значение в случае его вызова от переднего конца –

+0

это всего лишь один запрос ... но до вас. – Randy

3

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

create or replace trigger my_tab_tr 
before update or delete 
on my_tab 
referencing new as new and old as old 
for each row 
declare 
    l_type varchar2(3); 
begin 
    if (updating) then 
    l_type = 'UPD'; 
    else 
    l_type = 'DEL'; 
    end if; 

insert into my_tab_audit(
col1, 
col2, 
audit_type, 
audit_date) 
values (
:old.col1, 
:old.col2, 
l_type, 
sysdate 
); 
end; 

Добавление дополнительных столбцов, как вам нравится в таблице аудита, это просто типичный пример

+0

: но как я могу отслеживать это на уровне столбца, в моем запросе обновления я мог бы иметь 50 столбцов или только один столбец, и мне это нужно от GUI. В вашем soltuion вы отслеживаете только старое и новое значение, а не имя столбца, которое Я упомянул атрибут в моем примере –

+0

: Это можно сделать легко с 50, если -else для каждого столбца, но я просто хочу сделать это только обновленные столбцы. –

+0

@ GauravSoni простота этого подхода заключается в том, что вы отслеживали любые изменения в своей основной таблице, независимо от того, изменились ли данные из одного столбца или 50 столбцов. Например, вы можете запросить таблицу аудита для типов UPD (обновлений) на основе определенного диапазона дат и посмотреть, что со временем изменилось. – tbone

2

Единственный способ, который я видел, заключается в проверке каждого из полей: OLD и: NEW значений друг против друга и записи соответствующих записей в таблицу аудита. Вы можете полуавтоматизировать это, имея подпрограмму в триггере, на которую вы передаете соответствующие значения, но так или иначе я считаю, что вам придется писать код для каждого отдельного поля. Если у кого-то еще есть блестящий способ сделать это с каким-то отражающим API, о котором я не знаю (и «то, что я не знаю», применимо к большему количеству вещей каждый день, или так кажется :-).

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

Независимо от уровня аудита (поле за полем или по строкам), логику сравнения нужно аккуратно писать для обработки NULL/NOT NULL случаев, чтобы вы не укусили сравнивая :OLD.FIELD1 = :NEW.FIELD1, где одно из значений (или обоих) равно NULL и в конечном итоге не принимает соответствующее действие, поскольку NULL не равно никому, даже самому. Не спрашивайте меня, как я знаю ... :-)

Просто из любопытства, что будет установлено для OLD_VALUE и NEW_VALUE в одной строке, которая будет создана при возникновении INSERT?

Делитесь и наслаждайтесь.

+0

+1 @Bob, я считаю, что вы правы, единственный способ проверить изменения, внесенные обновлением (в триггере «ОБНОВЛЕНИЕ ДЛЯ КАЖДОГО ROW»), - это сравнить значения NEW.col и: OLD.col. (Если, конечно, как вы внимательно заметили, Oracle внесла некоторые невероятные изменения или представила потрясающий новый API). Я также согласен с вашими комментариями о том, что история «строк» ​​является более подходящим подходом в некоторых случаях. Действительно хороший ответ. – spencer7593

+0

@ spencer7593 - спасибо за ваши добрые комментарии (и upvote :-). Это дало мне возможность перечитать этот ответ, и при этом я понял, что забыл указать на важность обработки NULL/NOT NULL случаев в тестовой логике, поэтому я отредактировал ответ, чтобы включить это. –

1

А неортодоксальные решений очень: (только если у вас есть доступ к системным таблицам, по крайней мере ВЫБЕРИТЕ привилегию)

  1. Вы знаете имя Вашей таблицы. Определите идентификатор владельца таблицы. Посмотрите его в SYS.USER $ по имени пользователя (= владельца).

  2. Просмотрите идентификатор объекта вашей таблицы (= OBJ #) в SYS.OBJ $ по OWNER # (= идентификатор владельца) и NAME (= имя таблицы).

  3. Посмотрите столбцы, которые составляют таблицу в SYS.COL $ от OBJ #. Вы найдете все столбцы, их идентификаторы (COL #) и имена (NAME).

  4. Напишите триггер UPDATE с курсором, который перемещается по множеству этих столбцов. Вам нужно будет написать ядро ​​цикла только один раз.

  5. и в конце: я не предоставляю код, потому что детали могут отличаться от версии Oracle до версии Oracle.

Это настоящее динамическое программирование на языке SQL. Я использовал его даже на довольно крупных корпоративных системах (руководители команд не знали об этом), и это сработало. Это быстро и надежно. Недостатки: {privileges; транспортабельность; плохое отношение к ответственным людям}.

+0

Почему вы смотрите на те основные таблицы SYS, а не на представления 'user_tab_colums' или' all_tab_columns'? Первые три шага могут быть простым запросом к одному из этих представлений и, конечно же, не будут иметь разрешения? –