Journal de alterações de uma tabela
Autor: Jorge Jeferson Ferreira Montalvão | Publicado: 2012-03-30T12:20:00-03:00



Olá



Normalmente a informática é sempre questionada sobre "quem fez a alteração deste registro na tabela?". Cada uma implementa uma solução para isso. Como boa parte das novas aplicações têm o registro de quem, quando e onde modificou o registro, montei uma package que gera toda a estrutura que preciso (bem próxima a oferecida pelo Oracle Designer):

- Operação: INSERT, UPDATE, DELETE

- Usuário: nome do usuário do banco de dados que fez a operação

- Data e hora do evento

- Cópia do registro modificado

-- Create table
create table JN_DEFAULT
(
&nbsp; JN_OPERATION &nbsp; VARCHAR2(3),
&nbsp; JN_ORACLE_USER VARCHAR2(256) default user,
&nbsp; JN_DATETIME &nbsp; &nbsp;DATE default sysdate,
&nbsp; JN_NOTES &nbsp; &nbsp; &nbsp; VARCHAR2(240),
&nbsp; JN_APPLN &nbsp; &nbsp; &nbsp; VARCHAR2(35),
&nbsp; JN_SESSION &nbsp; &nbsp; NUMBER default userenv('sessionid'),
&nbsp; CLIENT_INFO &nbsp; &nbsp;VARCHAR2(256) default SYS_CONTEXT('USERENV', 'CLIENT_INFO'),
&nbsp; MODULE &nbsp; &nbsp; &nbsp; &nbsp; VARCHAR2(256) default SYS_CONTEXT('USERENV', 'MODULE'),
&nbsp; ACTION &nbsp; &nbsp; &nbsp; &nbsp; VARCHAR2(256) default SYS_CONTEXT('USERENV', 'ACTION'),
&nbsp; OS_USER &nbsp; &nbsp; &nbsp; &nbsp;VARCHAR2(256) default SYS_CONTEXT('USERENV', 'OS_USER'),
&nbsp; HOST &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; VARCHAR2(256) default SYS_CONTEXT('USERENV', 'HOST'),
&nbsp; IP_ADDRESS &nbsp; &nbsp; VARCHAR2(256) default SYS_CONTEXT ('USERENV', 'IP_ADDRESS')
);
/

CREATE OR REPLACE PACKAGE PCK_JOURNAL IS

&nbsp; -- Author &nbsp;: JORGE MONTALVÃO
&nbsp; -- Created : 02/02/2012 12:33:47
&nbsp; -- Purpose : ROTINAS PARA JOURNAL NO BD

&nbsp; FUNCTION JN_OPERATION RETURN CHAR;
&nbsp; PROCEDURE CREATE_JN(P_TABELA VARCHAR2, P_TABELA_JOUNAL VARCHAR2 DEFAULT NULL);

END PCK_JOURNAL;
/
CREATE OR REPLACE PACKAGE BODY PCK_JOURNAL IS

&nbsp; FUNCTION JN_OPERATION RETURN CHAR IS
&nbsp; BEGIN
&nbsp; &nbsp; IF INSERTING THEN
&nbsp; &nbsp; &nbsp; RETURN 'INS';
&nbsp; &nbsp; ELSIF UPDATING THEN
&nbsp; &nbsp; &nbsp; RETURN 'UPD';
&nbsp; &nbsp; ELSIF DELETING THEN
&nbsp; &nbsp; &nbsp; RETURN 'DEL';
&nbsp; &nbsp; END IF;
&nbsp; END;

&nbsp; PROCEDURE CREATE_JN(P_TABELA VARCHAR2, P_TABELA_JOUNAL VARCHAR2 DEFAULT NULL) IS
&nbsp; &nbsp; V_STMT VARCHAR2(4000 BYTE);
&nbsp; &nbsp; V_TABELA_JN VARCHAR2(4000 BYTE);

&nbsp; &nbsp; V_LONG LONG;

&nbsp; BEGIN
&nbsp; &nbsp; -- A VARIAVE P_TABELA_JOUNAL É OPCIONAL. SE INFORMADA USA O VALOR PASSADO POR PARAMETRO AO INVES
&nbsp; &nbsp; -- DO PADRÃO "NOME_DA_TABELA" || "_JN" PARA TABELA DA JOURNAL
&nbsp; &nbsp; SELECT NVL(P_TABELA_JOUNAL,T.TABLE_NAME || '_JN')
&nbsp; &nbsp; &nbsp; INTO V_TABELA_JN
&nbsp; &nbsp; &nbsp; FROM USER_TABLES T
&nbsp; &nbsp; &nbsp;WHERE T.TABLE_NAME = P_TABELA;

&nbsp; &nbsp; V_STMT := 'CREATE TABLE ' || V_TABELA_JN || '(';

&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(V_STMT);

&nbsp; &nbsp; FOR COLUNA IN (SELECT T.COLUMN_NAME
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,T.DATA_TYPE
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,DECODE(T.DATA_TYPE, 'VARCHAR2', '(4000 BYTE)') DATA_SIZE
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,T.DATA_DEFAULT
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,DECODE(LEAD(T.COLUMN_NAME) OVER (ORDER BY DECODE(T.TABLE_NAME,'JN_DEFAULT','A','B'), T.COLUMN_ID),NULL,1,0) ULTIMO
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FROM USER_TAB_COLUMNS T
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHERE T.TABLE_NAME IN ('JN_DEFAULT',P_TABELA)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ORDER BY DECODE(T.TABLE_NAME,'JN_DEFAULT','A','B'), T.COLUMN_ID)
&nbsp; &nbsp; LOOP
&nbsp; &nbsp; &nbsp; V_LONG := COLUNA.DATA_DEFAULT;

&nbsp; &nbsp; &nbsp; V_STMT := COLUNA.COLUMN_NAME || ' ' || COLUNA.DATA_TYPE || ' ' || COLUNA.DATA_SIZE;

&nbsp; &nbsp; &nbsp; IF SUBSTR(V_LONG,1,4000) IS NOT NULL THEN
&nbsp; &nbsp; &nbsp; &nbsp; V_STMT := V_STMT || ' DEFAULT ' || SUBSTR(V_LONG,1,4000) ;
&nbsp; &nbsp; &nbsp; END IF;

&nbsp; &nbsp; &nbsp; IF COLUNA.ULTIMO = 0 THEN
&nbsp; &nbsp; &nbsp; &nbsp; V_STMT := V_STMT || ',';
&nbsp; &nbsp; &nbsp; ELSIF COLUNA.ULTIMO = 1 THEN
&nbsp; &nbsp; &nbsp; &nbsp; V_STMT := V_STMT || ');';
&nbsp; &nbsp; &nbsp; END IF;

&nbsp; &nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(V_STMT);
&nbsp; &nbsp; END LOOP;

&nbsp; &nbsp; DBMS_OUTPUT.NEW_LINE;
&nbsp; &nbsp; V_STMT := 'comment on table ' || V_TABELA_JN || ' &nbsp;is ''TABELA CRIADA PELA PACKAGE PCK_JOURNAL'';';
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(V_STMT);


&nbsp; &nbsp; DBMS_OUTPUT.NEW_LINE;
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE('create or replace trigger TAIUP_'|| V_TABELA_JN );
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;after insert or update or delete on ' || P_TABELA);
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;for each row');
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE('DECLARE');
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;V_JN_OPERATION JN_DEFAULT.JN_OPERATION%TYPE;');
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE('BEGIN');
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;/*trigger CRIADA PELA PACKAGE PCK_JOURNAL*/');
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;V_JN_OPERATION := PCK_JOURNAL.JN_OPERATION;' );

&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp;INSERT INTO ' || V_TABELA_JN || '(');

&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp; &nbsp; JN_OPERATION,');

&nbsp; &nbsp; FOR COLUNA IN (SELECT T.COLUMN_NAME
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,DECODE(LEAD(T.COLUMN_NAME) OVER (ORDER BY T.COLUMN_ID),NULL,')',',') FIM_DA_LINHA
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FROM USER_TAB_COLUMNS T
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHERE T.TABLE_NAME IN (P_TABELA)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ORDER BY T.COLUMN_ID)
&nbsp; &nbsp; LOOP
&nbsp; &nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp; &nbsp; ' || COLUNA.COLUMN_NAME || COLUNA.FIM_DA_LINHA);
&nbsp; &nbsp; END LOOP;

&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp; &nbsp; VALUES(' );
&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp; &nbsp; V_JN_OPERATION,');

&nbsp; &nbsp; FOR COLUNA IN (SELECT T.COLUMN_NAME
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,DECODE(LEAD(T.COLUMN_NAME) OVER (ORDER BY T.COLUMN_ID),NULL,');',',') FIM_DA_LINHA
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FROM USER_TAB_COLUMNS T
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WHERE T.TABLE_NAME IN (P_TABELA)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ORDER BY T.COLUMN_ID)
&nbsp; &nbsp; LOOP
&nbsp; &nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE(' &nbsp; &nbsp;DECODE(V_JN_OPERATION,''DEL'',:OLD.' || COLUNA.COLUMN_NAME || ',:NEW.' ||COLUNA.COLUMN_NAME ||')' || COLUNA.FIM_DA_LINHA);
&nbsp; &nbsp; END LOOP;

&nbsp; &nbsp; DBMS_OUTPUT.PUT_LINE('END TAIUP_' || V_TABELA_JN || ';');

&nbsp; END;
END PCK_JOURNAL;
/


Explicações:

Table JN_DEFAULT: Tabela com colunas padrão para todas as tabelas de log criadas a partir desta package. É uma opção fácil de manipular se o DBA quiser acrescentar ou remover colunas sem ter que reescrever o código.

Observe que os objetos (package, package body, triggers e tabelas) são criadas no esquema do usuário do banco e a descrição da tabela são obtidas por dicionários do banco de dados USER_TAB_COLUMNS. Verifique as permissões.



Para exemplificar, veja o exemplo criando a partir da tabela&nbsp;EMPLOYEES do esquema de exemplo HR:




BEGIN

PCK_JOURNAL.CREATE_JN(P_TABELA =&gt; 'EMPLOYEES',P_TABELA_JOUNAL =&gt; 'EMPLOYEES_JN');

END;



O resultado é gerado na saída do terminal:




CREATE TABLE EMPLOYEES_JN(

JN_OPERATION VARCHAR2 (4000 BYTE),

JN_ORACLE_USER VARCHAR2 (4000 BYTE) DEFAULT user,

JN_DATETIME DATE &nbsp;DEFAULT sysdate,

JN_NOTES VARCHAR2 (4000 BYTE),

JN_APPLN VARCHAR2 (4000 BYTE),

JN_SESSION NUMBER &nbsp;DEFAULT userenv('sessionid'),

CLIENT_INFO VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT('USERENV', 'CLIENT_INFO'),

MODULE VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT('USERENV', 'MODULE'),

ACTION VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT('USERENV', 'ACTION'),

OS_USER VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT('USERENV', 'OS_USER'),

HOST VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT('USERENV', 'HOST'),

IP_ADDRESS VARCHAR2 (4000 BYTE) DEFAULT SYS_CONTEXT ('USERENV', 'IP_ADDRESS')

,

EMPLOYEE_ID NUMBER ,

FIRST_NAME VARCHAR2 (4000 BYTE),

LAST_NAME VARCHAR2 (4000 BYTE),

EMAIL VARCHAR2 (4000 BYTE),

PHONE_NUMBER VARCHAR2 (4000 BYTE),

HIRE_DATE DATE ,

JOB_ID VARCHAR2 (4000 BYTE),

SALARY NUMBER ,

COMMISSION_PCT NUMBER ,

MANAGER_ID NUMBER ,

DEPARTMENT_ID NUMBER );



comment on table EMPLOYEES_JN &nbsp;is 'TABELA CRIADA PELA PACKAGE PCK_JOURNAL';



create or replace trigger TAIUP_EMPLOYEES_JN

&nbsp; after insert or update or delete on EMPLOYEES

&nbsp; for each row

DECLARE

&nbsp; V_JN_OPERATION JN_DEFAULT.JN_OPERATION%TYPE;

BEGIN

&nbsp; /*trigger CRIADA PELA PACKAGE PCK_JOURNAL*/

&nbsp; V_JN_OPERATION := PCK_JOURNAL.JN_OPERATION;

&nbsp; INSERT INTO EMPLOYEES_JN(

&nbsp; &nbsp; &nbsp;JN_OPERATION,

&nbsp; &nbsp; &nbsp;EMPLOYEE_ID,

&nbsp; &nbsp; &nbsp;FIRST_NAME,

&nbsp; &nbsp; &nbsp;LAST_NAME,

&nbsp; &nbsp; &nbsp;EMAIL,

&nbsp; &nbsp; &nbsp;PHONE_NUMBER,

&nbsp; &nbsp; &nbsp;HIRE_DATE,

&nbsp; &nbsp; &nbsp;JOB_ID,

&nbsp; &nbsp; &nbsp;SALARY,

&nbsp; &nbsp; &nbsp;COMMISSION_PCT,

&nbsp; &nbsp; &nbsp;MANAGER_ID,

&nbsp; &nbsp; &nbsp;DEPARTMENT_ID)

&nbsp; &nbsp; &nbsp;VALUES(

&nbsp; &nbsp; &nbsp;V_JN_OPERATION,

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.EMPLOYEE_ID,:NEW.EMPLOYEE_ID),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.FIRST_NAME,:NEW.FIRST_NAME),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.LAST_NAME,:NEW.LAST_NAME),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.EMAIL,:NEW.EMAIL),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.PHONE_NUMBER,:NEW.PHONE_NUMBER),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.HIRE_DATE,:NEW.HIRE_DATE),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.JOB_ID,:NEW.JOB_ID),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.SALARY,:NEW.SALARY),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.COMMISSION_PCT,:NEW.COMMISSION_PCT),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.MANAGER_ID,:NEW.MANAGER_ID),

&nbsp; &nbsp; DECODE(V_JN_OPERATION,'DEL',:OLD.DEPARTMENT_ID,:NEW.DEPARTMENT_ID));

END TAIUP_EMPLOYEES_JN;




Aceito sugestões. Quem tiver sucesso ou alguma dificuldade com a utilização deste recurso, por favor, comente aqui no blog.

Boa sorte!