Stored Procedures e Triggers no Oracle - artigo SQL Magazine 03

Nesta edição, veremos a construção de triggers, stored procedures e functions, objetos que encapsulam e permitem a execução de blocos PL/SQL através de aplicações front-end.

No artigo anterior conhecemos as estruturas da linguagem PL/SQL. Nesta edição, veremos a construção de triggers, stored procedures e functions, objetos que encapsulam e permitem a execução de blocos PL/SQL através de aplicações front-end.

Stored Procedure é um bloco de código PL/SQL armazenado no servidor, que não retorna valor. As Stored Procedures são passivas, ou seja, alguma aplicação precisa solicitar sua execução. Function é um bloco PL/SQL que retorna valor. Assim como a stored procedure, uma function precisa ser solicitada por alguma aplicação.

Triggers também são procedimentos PL/SQL armazenados. A diferença é que triggers são ativas, ou seja, são acionadas automaticamente a partir de um evento que representa uma ação sobre a tabela. Esses eventos estão relacionados a comandos insert, update ou delete. Por exemplo, podemos associar uma trigger ao evento insert de uma tabela. Sempre que um registro for inserido nesta tabela, o código da trigger será executado, automaticamente. Uma trigger pode chamar uma stored procedure, que por sua vez pode chamar outra stored procedure.

Utilizando Triggers

Em relação a forma como são acionadas, existem três tipos de triggers:

A trigger é acionada pelos comandos INSERT, DELETE ou UPDATE executados na tabela associada. Independente da trigger ser do tipo statement ou row-level, seu disparo pode acontecer em dois momentos distintos: antes ou depois da execução do comando de atualização. A trigger instead-of é sempre do tipo row-level e é disparada por ação, não existindo os eventos after e before.

Observe a sintaxe para criação de uma trigger:

CREATE [ OR REPLACE ] TRIGGER Nome (INSTEAD OF / (BEFORE/AFTER)) Comando ON Tabela [ FOR EACH ROW ] [ WHEN Condição ] [ DECLARE lista de variáveis/constantes/objetos ] Begin { Bloco de códigos PL/SQL } End;

Onde,

Como exemplo prático, vamos criar um log sobre as operações de alteração e exclusão em uma tabela do banco de dados. Uma tabela chamada LogOperacao armazenará o log, que será gerado por duas triggers, uma before update e outra after delete. O script de criação e preenchimento das tabelas de exemplo está descrito na Listagem 1.

CREATE TABLE CARGO (IdCargo INTEGER NOT NULL PRIMARY KEY, NomeCargo VARCHAR(50), PercAumento NUMBER(5,2)); CREATE TABLE FUNCIONARIO (IDFUNCIONARIO NUMBER PRIMARY KEY NOT NULL, NOMEFUNCIONARIO VARCHAR2(100), SALARIO NUMBER(17,2), FLGGERENTE CHAR(1), IDCARGO NUMBER, FOREIGN KEY (IDCARGO) REFERENCES CARGO (IDCARGO)); CREATE TABLE LOGOPERACAO (IDLOGOPERCAO NUMBER PRIMARY KEY NOT NULL, USUARIO VARCHAR2(30), DATA DATE, DESCRICAO VARCHAR2(1000)); CREATE SEQUENCE SEQLOGOPERACAO START WITH 1 NOCACHE; INSERT INTO CARGO VALUES (1, 'GERENTE DE PROJETO', 15.75); INSERT INTO CARGO VALUES (2, 'ANALISTA DE SISTEMAS', 10.00); INSERT INTO FUNCIONARIO VALUES (1,’JOSÉ DAS COUVES’,1500,’S’, 1); INSERT INTO FUNCIONARIO VALUES (2,’ANTONIO FLORENÇO’,3725.50,’N’, 2); INSERT INTO FUNCIONARIO VALUES (3,’JUSTINA FEIJÓ’,2725.50,’N’, 2); INSERT INTO FUNCIONARIO VALUES (4,’MARIA ORACLINA’,8500,’N’, 2);

Listagem 1 – Criação de tabela para teste de trigger

O código completo das triggers está disponível na Listagem 2.

/* Trigger para log o Update */ CREATE OR REPLACE TRIGGER UPDATE_FUNCIONARIO BEFORE UPDATE ON FUNCIONARIO FOR EACH ROW DECLARE sDecricao VARCHAR2(1000); bAlterouRegistro Boolean := False; BEGIN sDecricao := ''; IF :NEW.IDFUNCIONARIO <> :OLD.IDFUNCIONARIO THEN sDecricao := sDecricao || ' Campo: IDFUNCIONARIO' || ' Valor Atual: ' || :NEW.IDFUNCIONARIO || ' Valor Anterior: ' || :OLD.IDFUNCIONARIO; bAlterouRegistro := True; END IF; IF :NEW.NOMEFUNCIONARIO <> :OLD.NOMEFUNCIONARIO THEN sDecricao := sDecricao || ' Campo: NOMEFUNCIONARIO' || ' Valor Atual: ' || :NEW.NOMEFUNCIONARIO || ' Valor Anterior: ' || :OLD.NOMEFUNCIONARIO; bAlterouRegistro := True; END IF; IF :NEW.SALARIO <> :OLD.SALARIO THEN sDecricao := sDecricao || ' Campo: SALARIO' || ' Valor Atual: ' || :NEW.SALARIO || ' Valor Anterior: ' || :OLD.SALARIO; bAlterouRegistro := True; END IF; IF :NEW.FLGGERENTE <> :OLD.FLGGERENTE THEN sDecricao := sDecricao || ' Campo: FLGGERENTE' || ' Valor Atual: ' || :NEW.FLGGERENTE || ' Valor Anterior: ' || :OLD.FLGGERENTE; bAlterouRegistro := True; END IF; IF bAlterouRegistro THEN sDecricao := 'Alteração da Tabela Funcionario' || sDecricao; INSERT INTO LOGOPERACAO (IDLOGOPERCAO, USUARIO, DATA, DESCRICAO) VALUES (SEQLOGOPERACAO.NEXTVAL, USER, SYSDATE, sDecricao); END IF; END; /* Trigger para log do Delete */ CREATE OR REPLACE TRIGGER DELETE_FUNCIONARIO AFTER DELETE ON FUNCIONARIO FOR EACH ROW DECLARE sDecricao LOGOPERACAO.DESCRICAO%type; BEGIN sDecricao := 'Exclusão da Tabela Funcionario' || ' Campo: IDFUNCIONARIO' || ' Valor: ' || :OLD.IDFUNCIONARIO || ' Campo: NOMEFUNCIONARIO' || ' Valor: ' || :OLD.NOMEFUNCIONARIO || ' Campo: SALARIO' || ' Valor: ' || :OLD.SALARIO || ' Campo: FLGGERENTE' || ' Valor: ' || :OLD.FLGGERENTE; INSERT INTO LOGOPERACAO (IDLOGOPERCAO, USUARIO, DATA, DESCRICAO) VALUES (SEQLOGOPERACAO.NEXTVAL, USER, SYSDATE, sDecricao); END;

Listagem 2 – Criação das triggers de Log de Operações para a tabela funcionario

Observe que elas são do tipo row-level, pois especificamos a cláusula FOR EACH ROW. A trigger de update testa e compara os valores atuais, representados pela variável interna :NEW, com os valores anteriores ao update, representados pela variável :OLD. O log só acontece se os valores de NEW e OLD forem diferentes. Para testar a trigger, execute os comandos a seguir:

DELETE FROM FUNCIONARIO WHERE IDFUNCIONARIO = 2; UPDATE FUNCIONARIO SET SALARIO = SALARIO * 1.25 WHERE IDFUNCIONARIO = 1; UPDATE FUNCIONARIO SET SALARIO = SALARIO;

Em seguida, execute um SELECT na tabela LogOperacao. O resultado é exibido na Listagem 3. A primeira operação de update faz o log apenas da coluna SALARIO, pois somente esse campo tem seu valor alterado. A segunda linha de update não gera log, pois não provoca nenhuma alteração real.

IDLOGOPERCAO USUARIO DATA DESCRICAO 1 SCOTT 14/02/03 Exclusão da Tabela Funcionario Campo: IDFUNCIONARIO Valor: 2 Campo: NOMEFUNCIONARIO Valor: ANTONIO FLORENÇO Campo: SALARIO Valor: 4507,86 2 SCOTT 14/02/03 Alteração da Tabela Funcionario Campo: SALARIO Valor Atual: 2835,94 Valor Anterior: 2268,75

Listagem 3 –Visualização dos registros da tabela de log

Variáveis NEW e OLD

Em uma trigger do tipo INSERT, podemos acessar os valores que serão gravados através da variável :NEW. No caso de uma trigger UPDATE, temos acesso aos valores alterados através do variável :OLD e dos valores a serem atribuídos através de :NEW. Para um comando DELETE estão disponíveis apenas os valores antigos, através de :OLD, uma vez que o registro não existirá após a execução do comando. O acesso a esses valores fornece flexibilidade para efetuação de testes e validações na trigger.

Triggers Instead-of

Vejamos outro tipo de trigger. Crie uma view chamada GERENTES, com o comando abaixo:

CREATE VIEW GERENTES AS SELECT IDFUNCIONARIO, NOMEFUNCIONARIO, SALARIO FROM FUNCIONARIO WHERE FLGGERENTE = 'S'

Uma trigger do tipo instead of, no evento INSERT desta view, permitirá a inserção de dados como se estivéssemos trabalhando com uma tabela. Veja a seguir o código para criação da trigger:

CREATE OR REPLACE TRIGGER INSERT_GERENTES INSTEAD OF INSERT ON GERENTES FOR EACH ROW BEGIN INSERT INTO FUNCIONARIO (IDFUNCIONARIO, NOMEFUNCIONARIO, SALARIO, FLGGERENTE) VALUES (:NEW.IDFUNCIONARIO, :NEW.NOMEFUNCIONARIO, :NEW.SALARIO, 'S'); END;

A eficácia da trigger pode ser comprovada através do comando:

INSERT INTO GERENTES VALUES (6,’João da Silva’,2500);

Triggers Statement-Level

Para exemplificar esta trigger, vamos utilizar a mesma idéia de log. O comando para criar o exemplo é:

CREATE OR REPLACE TRIGGER LOG_UPDATE_FUNCIONARIO BEFORE UPDATE ON FUNCIONARIO BEGIN INSERT INTO LOGOPERACAO (IDLOGOPERCAO, USUARIO, DATA, DESCRICAO) VALUES (SEQLOGOPERACAO.NEXTVAL, USER, SYSDATE, 'O USUÁRIO ALTEROU REGISTROS NA TABELA'); END;

Note que a ausência da frase “FOR EACH ROW” caracteriza a trigger como statement-level. Para testar, execute o comando:

UPDATE FUNCIONARIO SET SALARIO = SALARIO * 1.1; SELECT * FROM LOGOPERACAO;

Observe que a mensagem 'O USUÁRIO ALTEROU...' será cadastrada na tabela de log apenas uma vez, independente da quantidade de registros alterados. Um fato interessante é que a trigger row-level UPDATE_FUNCIONARIO, criada anteriormente, continua ativa, inserindo ocorrências na tabela de log para registro alterado.

Algumas considerações sobre o uso de Triggers

Em alguns casos, como rotinas de importação ou manutenção de erros, é interessante suspender a ativação automática da trigger. Para isso, podemos utilizar o comando:

ALTER TRIGGER nome_da_trigger DISABLE

Para reativar a trigger utilize:

ALTER TRIGGER nome ENABLE

Utilizando Stored Procedures

A sintaxe para criação de uma stored procedure é:

CREATE [ OR REPLACE ] PROCEDURE nome [ (lista de parâmetros) ] AS [ DECLARE lista de variávies/constantes/obetos/procedures/funções ] Begin { Bloco de códigos PL/SQL } End;

Onde,

A execução de uma Stored Procedure pode ser feita diretamente pelo SQL*Plus ou por qualquer aplicativo front-end através do comando:

EXECUTE nome_da_procedure

A Listagem 4 exibe um exemplo de procedure que atualiza o salário de um funcionário de acordo com o percentual indicado no seu cargo. A procedure é executada através do comando EXECUTE PROCEDURE CorrigeSalario(1) , onde 1 é o código do empregado.

Utilizando Functions

Functions são blocos de código PL/SQL que retornam valor. O interessante é que podemos chamá-las a partir de qualquer outro código PL/SQL ou dentro de uma cláusula SQL. A sintaxe para criação de uma function é a seguinte:

CREATE [ OR REPLACE ] FUNCTION nome [ (lista de parâmetros) ] RETURN tipo de dado AS [ DECLARE lista de variávies/constantes/obetos ] BEGIN { Bloco de códigos PL/SQL } Return Valor End;

A Listagem 5 mostra uma função que retorna o percentual do salário de um gerente em relação ao somatório dos salários de todos os dirigentes.

CREATE OR REPLACE PROCEDURE CorrigeSalario ( iIdFuncionario FUNCIONARIO.IdFuncionario%TYPE) AS nPercAumento CARGO.PercAumento%TYPE; BEGIN SELECT C.PercAumento INTO nPercAumento FROM CARGO C, FUNCIONARIO F WHERE F.IdCargo = C.IdCargo AND F.IdFuncionario = iIdFuncionario; UPDATE FUNCIONARIO SET Salario = Salario * (1 + (nPercAumento/100 ) ) WHERE iIdFuncionario = iIdFuncionario; END;

Listagem 4– Criação da Stored Procedure

CREATE OR REPLACE FUNCTION PERCENT_SAL_GERENTES(nIdFuncionario Number) RETURN NUMBER AS iNumGerentes Integer; dSalarioGerentes Number; dPercentSal Number; BEGIN SELECT COUNT(*) INTO iNumGerentes FROM FUNCIONARIO WHERE FLGGERENTE = 'S'; SELECT SUM(SALARIO) INTO dSalarioGerentes FROM FUNCIONARIO WHERE FLGGERENTE = 'S'; IF dSalarioGerentes = 0 THEN RETURN dPercentSal; ELSE SELECT ( SALARIO * 100 / dSalarioGerentes ) INTO dPercentSal FROM FUNCIONARIO WHERE IDFUNCIONARIO = nIdFuncionario; RETURN ROUND(dPercentSal,2); END IF; END;

Listagem 5

Se o identificador passado como parâmetro não for de um gerente, o valor retornado será zero. Para testar a função, podemos executar o comando a seguir:

SELECT NOMEFUNCIONARIO AS GERENTE, PERCENT_SAL_GERENTES(IDFUNCIONARIO) FROM FUNCIONARIO WHERE FLGGERENTE = ‘S’;

O Oracle fornece uma tabela para execução e teste de funções, chamada dual. A figura 1 exibe um exemplo de uso desta tabela.

Figura 1

Utilizando Packages

Os packages funcionam como containers, agrupando vários objetos do banco de dados de uma vez. O package torna a distribuição das procedures e functions mais organizada, permitindo criar grupos de atividades em comum. Outra vantagem é que após a primeira leitura, os packages permanecem em memória, tornando a execução dos objetos mais veloz. A sintaxe para criação do package é vista a seguir:

CREATE PACKAGE nome AS [ seção de declaração ] END; CREATE [ OR REPLACE ] PACKAGE BODY nome AS [ seção de declaração ] [ implementação de procedures, functions, inicialização ] END; ALTER PACKAGE nome COMPILE;

A criação de um package é feita em duas etapas: primeiro definimos a especificação e em seguida o corpo do pacote.

A especificação contém as declarações dos objetos contidos no package. O corpo do package contém toda a implementação dos objetos declarados na especificação. Após a criação dessas seções o package deve ser montado e compilado para que possa ser acessado de forma otimizada.

Como exemplo, vamos utilizar alguns objetos criados neste artigo, encapsulando-os em um package, chamado MANUT_FUNCIONARIOS. O código completo para criação do package está disponível na Listagem 6.

CREATE PACKAGE MANUT_FUNCIONARIOS AS FUNCTION CONTA_GERENTES RETURN INTEGER; PROCEDURE CorrigeSalario ( iIdFuncionario FUNCIONARIO.IdFuncionario%TYPE); END; CREATE OR REPLACE PACKAGE BODY MANUT_FUNCIONARIOS AS -- FUNCTION CONTA_GERENTES RETURN INTEGER AS iNumGerentes Integer; BEGIN SELECT COUNT(*) INTO iNumGerentes FROM FUNCIONARIO WHERE FLGGERENTE = 'S'; RETURN iNumGerentes; END; -- PROCEDURE CorrigeSalario ( iIdFuncionario FUNCIONARIO.IdFuncionario%TYPE) AS nPercAumento CARGO.PercAumento%TYPE; BEGIN SELECT C.PercAumento INTO nPercAumento FROM CARGO C, FUNCIONARIO F WHERE F.IdCargo = C.IdCargo AND F.IdFuncionario = iIdFuncionario; UPDATE FUNCIONARIO SET Salario = Salario * (1 + (nPercAumento/100 ) ) WHERE iIdFuncionario = iIdFuncionario; END; -- END;

Listagem 6

Para compilar o package execute o comando:

ALTER PACKAGE manut_funcionarios COMPILE;

Para executar um objeto contido em um package, basta acrescentar o nome do pacote seguido por “ponto”:

SELECT MANUT_FUNCIONARIOS.CONTA_GERENTES FROM DUAL;

A desvantagem deste recurso é a dificuldade de manutenção, pois não é possível manipular os objetos do package individualmente. Se temos um pacote com 10 objetos e precisamos alterar um, o script deve ser igual ao de criação do package, com o objeto em questão alterado.

Verificando erros de criação

Quando um objeto do Oracle é criado com erros, a mensagem a seguir é exibida:

“Aviso: Procedimento criado com erros de compilação.”

O problema é que esta mensagem não dá maiores informações sobre o erro. Para ter acesso aos detalhes, devemos consultar a tabela de sistema ALL_ERRORS, através do comando:

SELECT * FROM ALL_ERRORS WHERE NAME = ‘nome_da_trigger_ou_procedure’.

Conclusão

Nesta matéria vimos conceitos, definições e exemplos referentes a criação de objetos armazenados. Ainda há muito a ser discutido, não deixe de acompanhar novos artigos sobre esse assunto em edições futuras. Até a próxima!

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados