Neste artigo vamos apresentar o conceito de Triggers no banco de dados Oracle 11g, a fim de vermos como e quando utilizá-las.
Uma trigger é basicamente uma unidade PL/SQL que é armazenada na base de dados e pode ser chamada várias vezes no decorrer de uma operação, de igual forma a uma stored procedure. A diferença entre as duas operações é que, diferentemente das stored procedures, as triggers podem ser habilitadas ou desabilitadas, e no momento em que está habilitada pode ser utilizada a qualquer momento.
Para criarmos uma trigger utilizamos a instrução CREATE TRIGGER, e esta, ao ser escrita, será executada em resposta a algum evento que tenha feito a solicitação com relação ao item que irá utilizá-la. Estes itens aos quais nos referimos podem ser Views, tables, schemas ou mesmo bases de dados as quais os eventos estejam associados. Além disso, podemos também definir o momento no qual as triggers serão disparadas, como por exemplo, se será antes ou depois da declaração ser executada. Por padrão, temos que a trigger ao ser criada, já encontra-se habilitada. Normalmente uma trigger é utilizada com base em três eventos, sendo estes, em instruções DDL, instruções DML e operações de banco de dados, vejamos então um pouco sobre cada uma delas.
- Instruções DML – estas como bem sabemos, são utilizadas para manipulação de dados através das instruções de Delete, Insert e Update.
- Instruções DDL – estas são mais utilizadas em momentos que são necessárias auditoria de alteração em objetos de um schema, como é o caso da utilização de operações como Create, Drop e Alter.
- Operações de banco de dados – neste caso, as triggers são utilizadas nas operações de startup, Logon, Logof, dentre outras operações.
No decorrer do nosso artigo, estaremos interessados em tratar sobre pontos referentes as triggers DML, pois estas são mais utilizadas nos projetos.
Por quê devemos usar triggers?
Veremos aqui algumas das razões pelas quais devemos usar as triggers, pois elas permitem que possamos personalizar o nosso sistema de gerenciamento de banco de dados. Alguns dos exemplos que podemos citar são geração automática de valores em colunas virtuais, log de eventos, mudança de dados da tabela quando DML’s que são disparados em views, imposição da integridade referencial, prevenção de transações inválidas, dentre outras. Para desenvolvermos nossas triggers, precisamos ter alguns cuidados e seguir algumas diretrizes, como podemos citar a seguir:
- Utilização de triggers para garantir que quando uma operação específica seja realizada, as ações relacionadas a ela também o sejam;
- Não definir triggers que dupliquem funções do banco de dados;
Mesmo que possamos utilizar triggers e restrições de integridade (integrity constraint) para definir e assegurar qualquer tipo de regra de integridade, a Oracle não recomenda que utilizemos triggers para restringir a entrada de dados em alguns casos, como descritos a seguir:
- Em casos onde são utilizados para impor a integridade referencial no momento em que as tabelas dependentes estão em diferentes nós de um banco de dados distribuído;
- E quando uma regra de integridade referencial necessária não pode ser executada utilizando restrições como o NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK, DELETE CASCADE ou DELETE SET NULL.
O que podemos fazer e não fazer com triggers?
Ao trabalharmos com triggers, precisamos tomar alguns cuidados, como é o caso do tamanho da lógica que será utilizada, pois esta não pode passar de 32k. Não devemos também criar triggers recursivas. Devemos, no entanto, utilizar as triggers de forma criteriosa na base de dados, pois estas são executadas para cada usuário para o qual a trigger foi criada. Em casos que utilizamos triggers de Login para fazer o monitoramento do logon dos usuários, estes incluem manipulações de exceção nas triggers, pois caso tenhamos alguma exceção que não tenha sido tratada, isso poderá bloquear todas as conexões do banco de dados. Já se utilizamos as triggers de logon apenas para a execução de pacotes, podemos colocar a manipulação de exceção diretamente no pacote, ao invés de ser na trigger. A utilização das triggers é de extrema importância para qualquer aplicação, pois com ela podemos garantir algumas funcionalidades, como por exemplo:
- A execução de validações quanto a alteração realizada em tables ou Views – isso acontece pelo fato de que triggers oferecem uma maior garantia quanto a validação de informações, já que as validações estão ligadas diretamente ao objeto do banco de dados.
- Automatiza a manutenção no banco de dados – neste ponto, é possível a associação de triggers a eventos do banco de dados.
Criando uma Trigger
Para criarmos uma trigger em estado desativado, devemos utilizar a cláusula DISABLE da instrução CREATE TRIGGER. Para obtermos mais informações sobre os estados das triggers, podemos ver na documentação oficial da Oracle através do seu site. Para a criação de uma trigger, precisamos ter noção quanto a sintaxe básica de sua estrutura, a qual apresentamos de acordo com a Listagem 1.
Listagem 1. Sintaxe básica de criação de uma trigger.
CREATE [OR REPLACE ] TRIGGER trigger_name
{BEFORE | AFTER | INSTEAD OF }
{INSERT [OR] | UPDATE [OR] | DELETE}
[OF col_name]
ON table_name
[REFERENCING OLD AS o NEW AS n]
[FOR EACH ROW]
WHEN (condition)
DECLARE
Declaration-statements
BEGIN
Executable-statements
EXCEPTION Exception-handling-statements
END;
Como podemos observar pela Listagem 1, definiremos a seguir o que significa cada um dos trechos presentes na sintaxe, onde:
- CREATE [OR REPLACE] TRIGGER trigger_name – é definido para a criação de uma trigger ou a sobrescrita de uma trigger já existente com o nome informado.
- {BEFORE | AFTER | INSTEAD OF } – neste ponto, definimos quando a trigger deverá ser executada. No caso da cláusula INSTEAD OF, esta é utilizada quando uma trigger é criada em uma View.
- {INSERT [OR] | UPDATE [OR] | DELETE} – neste ponto especificamos uma operação DML.
- [OF col_name] - especificamos aqui a coluna que será atualizada.
- ON table_name - aqui especificamos o nome da tabela que será associada a trigger.
- [REFERENCING OLD AS o NEW AS n] – aqui permitimos referências a valores novos e valores antigos para várias declarações do tipo DML.
- [FOR EACH ROW] - esta instrução especifica uma trigger no nível de linha.
- WHEN (condição) – neste momento, definimos a condição para que a trigger seja disparada nas linhas.
Dependendo da ferramenta que estejamos utilizando, como por exemplo o SQL*Plus, ao final da criação de nossa trigger, precisaremos adicionar uma “/” (barra) na última linha do código. Vejamos então um simples exemplo de criação de uma trigger, como a apresentada de acordo com a Listagem 2.
Listagem 2. Criando uma trigger simples.
CREATE OR REPLACE TRIGGER Atualiza_salario_func
BEFORE DELETE OR INSERT OR UPDATE ON funcionario
FOR EACH ROW
WHEN (NEW.CODFUNC > 0)
DECLARE
diferenca_salario number;
BEGIN
diferenca_salario := :NEW.SALARIOFUNC - :OLD.SALARIOFUNC;
dbms_output.put('Salário antigo:' || :OLD.salarioFunc);
dbms_output.put('Salário novo:' || :NEW.salarioFunc);
dbms_output.put_line(' A diferença de salário foi de: ' || diferenca_salario);
END;
/
Como podemos observar no código apresentado pela Listagem 2, temos uma trigger que é disparada quando as operações DML são executadas na tabela funcionario. Devido ao fato de estarmos declarando a palavra-chave “BEFORE” na nossa trigger, podemos acessar os novos valores antes que estes sejam enviados para a tabela, e dessa forma podemos alterar os valores. Caso ocorra alguma falha na execução, o erro poderá ser facilmente corrigido, atribuindo a cláusula NEW.column_name. Além disso, podemos utilizar a palavra-chave AFTER, caso tenhamos interesse em consultar ou mesmo alterar a mesma tabela. No momento em que utilizamos a cláusula FOR EACH ROW da trigger, podemos executar a instrução várias vezes, o que pode ocorrer em momentos onde precisamos realizar atualizações ou exclusões de várias linhas simultaneamente.
Após a criação da trigger, a instrução SQL dispara a trigger para cada linha de código que é atualizada, onde temos no fim, a apresentação dos valores dos salários antigo, novo e a diferença entre eles. Para a atualização dos valores, podemos definir então da seguinte forma:
UPDATE funcionario SET salarioFunc = salarioFunc + 750.00 WHERE codDep = 6;
Uma coisa que devemos ter em mente é que o nome das triggers deve ser exclusivo dentro de um mesmo schema, o que não ocorre com relação aos objetos de um schema, como é o caso de tabelas ou Views. Na Listagem 2, temos que a instrução ATUALIZA_SALARIO_FUNC é disparada após qualquer instrução de DELETE, INSERT ou UPDATE na tabela funcionários, onde podemos ver que qualquer uma das seguintes afirmações poderá acionar a trigger ATUALIZA_SALARIO_FUNC, que são:
DELETE FROM funcionario;
INSERT INTO funcionario VALUES ( ... );
INSERT INTO funcionario SELECT ... FROM ... ;
UPDATE funcionario SET ... ;
Outra maneira de criarmos nossas triggers é utilizando a cláusula INSTEAD_OF, a qual utilizaremos neste momento, onde primeiramente criaremos duas novas tabelas, as quais serão chamadas de cliente_juridico e cliente_fisico e em seguida, veremos como esta cláusula deve ser aplicada de acordo com os exemplos presentes na Listagem 3, 4, 5 e 6.
Listagem 3. Criando as tabelas cliente_físico e cliente_jurídico.
// Criando a tabela cliente_fisico
CREATE TABLE cliente_fisico
( codigo NUMBER(6),
nomeCli VARCHAR2(150),
creditoDisp NUMBER(10,2) );
// Criando a tabela cliente_juridico
CREATE TABLE cliente_juridico
( codigo NUMBER(6),
nomeCli VARCHAR2(150),
creditoDisp NUMBER(10,2) );
Ao criarmos nossas tabelas, preenchamos com alguns dados e em seguida, criaremos um novo tipo OBJECT, o qual, chamaremos de clienteObjeto. Nele teremos os mesmos dados presentes nas tabelas de clientes e um campo adicional que será o estado, como apresentado de acordo com a Listagem 4. Além disso, criaremos uma nova View que será responsável por apresentar todos os dados presentes nas tabelas de cliente_fisico e cliente_juridico, como podemos ver de acordo com a Listagem 5.
Listagem 4. Criação do tipo clienteObjeto e da View todosclientes.
CREATE TYPE clienteObjeto AS OBJECT
( codigo NUMBER(6),
nomeCli VARCHAR2(50),
creditoDisp NUMBER(9,2),
estado VARCHAR2(2) );
Listagem 5. Criando a View todosClientes.
CREATE VIEW todosClientes (codigo)
AS SELECT clienteObjeto (codigo, nomeCli, creditoDisp, 'PE')
FROM cliente_fisico
UNION ALL
SELECT clienteObjeto (codigo, nomeCli, creditoDisp, 'BH')
FROM cliente_juridico;
Agora que estamos com a nossa View e o nosso objeto clienteObjeto, iremos criar a nossa trigger, a qual chamaremos de triggerUtilizandoIsteadOf, como podemos ver de acordo com a Listagem 6.
Listagem 6. Criação da trigger triggerUtilizandoIsteadOf.
CREATE TRIGGER triggerUtilizandoIsteadOf INSTEAD OF INSERT ON todosClientes
FOR EACH ROW
BEGIN
IF (:new.codigo.estado = 'PE') THEN
INSERT INTO cliente_fisico
VALUES (:new.codigo.codigo, :new.codigo.nomeCli,:new.codigo.creditoDisp);
ELSE
INSERT INTO cliente_juridico
VALUES (:new.codigo.codigo, :new.codigo.nomeCli, :new.codigo.creditoDisp);
END IF;
END;
Como podemos ver de acordo com a Listagem 6, temos uma trigger que faz uma verificação com o Instead of de inserções e passa as informações por um FOR EACH ROW, por onde faz uma verificação com base no estado especificado para realizar as inserções nas tabelas de clientes especificadas.
Triggers compostas
Uma trigger composta permite que o código para um ou mais pontos de tempo para um objeto específico possam ser combinados em uma única trigger. Os pontos de sincronização individuais podem compartilhar uma única seção de declaração global, cujo estado é mantido por toda a vida útil da declaração. Nas versões anteriores do Oracle, este tipo de funcionalidade só foi possível através da definição de várias triggers cujo código e variáveis globais foram definidas em um pacote separado.
As ações da trigger são definidas da mesma forma que uma trigger DML, com a adição da cláusula COMPOUND TRIGGER. O corpo principal da trigger é formada por uma seção opcional de declaração global e com uma ou mais seções de ponto temporais, onde cada uma das quais podem conter uma seção de declarações locais cujo estado não é mantido. A sintaxe básica utilizada é de acordo com a Listagem 7.
Listagem 7. Sintaxe básica de uma trigger composta.
CREATE OR REPLACE TRIGGER <trigger-name>
FOR <trigger-action> ON <table-name>
COMPOUND TRIGGER
-- Declaração global.
g_global_variable VARCHAR2(10);
BEFORE STATEMENT IS
BEGIN
NULL; -- as instruções são postas aqui. --
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
NULL; -- as instruções são postas aqui. --
END BEFORE EACH ROW;
AFTER EACH ROW IS
BEGIN
NULL; -- as instruções são postas aqui. --
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
NULL; -- as instruções são postas aqui. --
END AFTER STATEMENT;
END <trigger-name>;
/
A partir de agora, criaremos um exemplo simples, onde criaremos uma tabela de testes e uma trigger composta que será disparada para cada ponto de tempo associado com instruções insert, update e delete. As ações que forem disparadas serão armazenadas em uma tabela PL/SQL definida na seção de declaração global. O ponto de sincronização final para cada declaração mostra o conteúdo da tabela para mostrar que o estado variável foi mantido durante todo o tempo de vida da declaração, como podemos ver de acordo com a Listagem 8.
Listagem 8. Utilizando trigger composta.
-- criação da tabela --
CREATE TABLE testeTriggerComposta (
id NUMBER,
descricao VARCHAR2(255)
);
-- Criação da trigger --
CREATE OR REPLACE TRIGGER testeTriggerComposta_trigger
FOR INSERT OR UPDATE OR DELETE ON testeTriggerComposta
COMPOUND TRIGGER
-- Global declaration.
TYPE tipo_tabela IS TABLE OF VARCHAR2(50);
tabelaTeste tipo_tabela := tipo_tabela();
BEFORE STATEMENT IS
BEGIN
tabelaTeste.extend;
CASE
WHEN INSERE THEN
tabelaTeste(tabelaTeste.last) := 'ANTES DO MÉTODO - INSERT';
WHEN ATUALIZA THEN
tabelaTeste(tabelaTeste.last) := 'ANTES DO MÉTODO - UPDATE';
WHEN EXCLUE THEN
tabelaTeste(tabelaTeste.last) := 'ANTES DO MÉTODO - DELETE';
END CASE;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
tabelaTeste.extend;
CASE
WHEN INSERE THEN
tabelaTeste(tabelaTeste.last) := 'antes da cláusula INSERT (new.id=' || :new.id || ')';
WHEN ATUALIZA THEN
tabelaTeste(tabelaTeste.last) := 'antes da cláusula UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
WHEN EXCLUE THEN
tabelaTeste(tabelaTeste.last) := ' antes da cláusula DELETE (old.id=' || :old.id || ')';
END CASE;
END BEFORE EACH ROW;
AFTER EACH ROW IS
BEGIN
tabelaTeste.extend;
CASE
WHEN INSERE THEN
tabelaTeste(tabelaTeste.last) := 'Após cada INSERT (new.id=' || :new.id || ')';
WHEN ATUALIZA THEN
tabelaTeste(tabelaTeste.last) := ' Após cada UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
WHEN EXCLUE THEN
tabelaTeste(tabelaTeste.last) := ' Após cada DELETE (old.id=' || :old.id || ')';
END CASE;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
tabelaTeste.extend;
CASE
WHEN INSERE THEN
tabelaTeste(tabelaTeste.last) := 'Após o método - INSERT';
WHEN ATUALIZA THEN
tabelaTeste(tabelaTeste.last) := 'Após o método - UPDATE';
WHEN EXCLUE THEN
tabelaTeste(tabelaTeste.last) := 'Após o método - DELETE';
END CASE;
FOR i IN tabelaTeste.first .. tabelaTeste.last LOOP
DBMS_OUTPUT.put_line(tabelaTeste(i));
END LOOP;
tabelaTeste.delete;
END AFTER STATEMENT;
END testeTriggerComposta_trigger;
/
Com base no código visto na Listagem 8, temos uma a criação de uma tabela de testes e uma trigger composta que dispara para cada ponto de sincronização associado com os métodos insert, update e delete. As ações que são disparadas passam a ser registradas numa tabela que foi definida pela seção de declaração global. Para cada ponto de sincronização final de cada declaração apresenta o conteúdo presente na tabela para mostrar que a variável de estado permanece ativo durante todo o ciclo de vida da declaração. Para comprovarmos isso, basta que criemos algumas declarações de insert, updates e deletes, para isso, basta que façamos essas declarações na tabela de testes, como podemos ver de acordo com a Listagem 9.
Listagem 9. Inserindo dados na tabela de testes.
INSERT INTO testeTriggerComposta VALUES (1, 'teste de inserção');
UPDATE testeTriggerComposta SET id = id;
DELETE FROM testeTriggerComposta;
Antes da versão 11g da Oracle, tínhamos a problemática com relação a ativação e desativação de uma trigger, o que foi solucionado a partir desta versão, onde de forma explicita podemos realizar as operações tanto de habilitar quanto de desabilitar uma trigger. Podemos ver isso através de um exemplo simples, onde criaremos primeiro uma nova tabela chamada de controleTrigger e em seguida, criaremos duas triggers, as quais contaram com as cláusulas de Enable e Disable, como apresentado pelas Listagens 10, 11 e 12.
Listagem 10. Criação da tabela controleTrigger.
CREATE TABLE controleTrigger (
id NUMBER,
descricaoOperacao VARCHAR2(150)
);
Listagem 11. Criação da trigger com a cláusula Enable (ativa).
CREATE OR REPLACE TRIGGER controleTrigger_trigger
BEFORE INSERT ON controleTrigger
FOR EACH ROW
ENABLE
BEGIN
DBMS_OUTPUT.put_line('A trigger que criamos foi utilizada!! =) ');
END;
/
Para que possamos testar a nossa trigger, basta que realizemos a inserção de um registro na tabela controleTrigger, como podemos ver a seguir:
INSERT INTO trigger_control_test VALUES (3, 'Nossa trigger');
Ao executarmos a instrução, veremos que o registro foi inserido e que a mensagem foi apresentada. Vejamos agora o que ocorre com relação a nossa Listagem 11.
Listagem 12. Criação da trigger com a cláusula Disabled (desativado).
CREATE OR REPLACE TRIGGER controleTrigger_trigger
BEFORE INSERT ON controleTrigger
FOR EACH ROW
DISABLE
BEGIN
DBMS_OUTPUT.put_line('A trigger que criamos não foi utilizada!! =( ');
END;
/
De igual forma, realizaremos a inserção de um novo registro na tabela, mas a diferença é que neste caso não tivemos a apresentação da mensagem que criamos anteriormente.
Com isso finalizamos o nosso artigo, onde vimos um pouco com relação ao mundo das triggers no Oracle 11g, pois como ainda é o mais usado no mercado, precisamos estar cientes de seu funcionamento. Vimos então como podemos criar as triggers, como habilitá-las e desabilitá-las, além de aprendermos de forma prática com relação a utilização de triggers compostas. Esperamos que tenham gostado! =)