Tente um gatilho composto:
CREATE OR REPLACE TRIGGER compound_trigger_name
FOR INSERT OR UPDATE OF salary ON treballa
COMPOUND TRIGGER
TYPE Departments_t IS TABLE OF treballa.department%TYPE INDEX BY varchar2(100);
Departments Departments_t;
BEFORE EACH ROW IS
BEGIN
-- collect updated or inserted departments
Departments( :new.department ) := :new.department;
END BEFORE EACH ROW;
AFTER STATEMENT IS
sum_sal NUMBER;
BEGIN
-- for each updated department check the restriction
FOR dept IN Departments.FIRST .. Departments.LAST
LOOP
SELECT sum(salary) INTO sum_sal FROM treballa WHERE department = dept;
IF sum_sal > 1000 THEN
raise_application_error(-20123, 'The total salary for department '||dept||' cannot exceed 1000');
END IF;
END LOOP;
END AFTER STATEMENT;
END compound_trigger_name;
/
========EDIT - algumas perguntas e respostas ===========
P:Por que ocorre um erro de tabela mutante?
R:Isso está descrito na documentação:
http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#g1699708
P:como evitar um erro de tabela mutante?
R:A documentação recomenda o uso de um gatilho composto, veja isto:http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CHDFEBFJ
P:O que é um acionador composto e como ele funciona?
R:Este é um tópico enorme, consulte a documentação aqui:http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CIHEFGFD
Resumindo:este é um tipo especial de gatilho que torna o psiable combinar quatro tipos de gatilhos separados:
BEFORE statement
, BEFORE-for each row
, AFTER for each row
e AFTER statament
em uma declaração. Facilita a implementação de alguns cenários em que há a necessidade de passar alguns dados de uma trigger para outra. Por favor, estude o link acima para mais detalhes. P:Mas o que realmente faz
"Departments( :new.department ) := :new.department;
? R:Esta declaração armazena um número de departamento em um array associativo.
Este array é declarado em uma parte declarativa do gatilho composto:
TYPE Departments_t IS TABLE OF treballa.department%TYPE INDEX BY varchar2(100);
Departments Departments_t;
A documentação relacionada aos gatilhos compostos diz que:http ://docs.oracle.com/cd/B28359_01/appdev.111/b28370/triggers.htm#CIHJBEFE
O acima significa que
Departments
A variável é inicializada apenas uma vez no início de todo o processamento, logo após o disparo do gatilho. "Duração da instrução de disparo" significa que esta variável é destruída após o término do gatilho.Esta instrução:
Departments( :new.department ) := :new.department;
armazena um número de departamento na matriz associativa. Está em BEFORE EACH ROW
seção, então ele é executado para cada linha que é atualizada (ou inserida) pela instrução update/insert.:new
e :old
são pseudoregistros, mais sobre eles você pode encontrar aqui: http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#LNPLS99955
Resumindo:
:new.department
recupera um novo valor de department
column- para uma linha atualmente atualizada (valor atualizado - APÓS a atualização), enquanto :old.department
fornece um valor antigo desta coluna (ANTES da atualização).Esta coleção é usada posteriormente na
AFTER STATEMENT
, quando os gatilhos selecionam todos os departamentos atualizados (em um FOR-LOOP), para cada departamento dispara SELECT SUM(salary) ...
e então verifica se esta soma é menor que 1000 Considere uma simples atualização:
UPDATE treballa SET salary = salary + 10
. Esta é uma única instrução de atualização, mas altera muitas linhas de uma vez. A ordem de execução do nosso gatilho é a seguinte:- A atualização de status é acionada:
UPDATE treballa SET salary = salary + 10
- A seção declarativa do gatilho é executada, ou seja:
Departments
variável é inicializada BEFORE EACH ROW
é executada separadamente para cada linha atualizada - quantas vezes houver linhas a serem atualizadas. Neste local, coletamos todos os departamentos das linhas alteradas.AFTER STATEMENT
seção é executada. Neste ponto, a tabela já está atualizada - todas as linhas já possuem salários novos e atualizados. Percorremos os departamentos salvos emDepartments
e para cada um verificamos se a soma dos salários é menor ou igual a 1.000. Se essa soma for> 1.000 para qualquer um desses departamentos, um erro será lançado e toda a atualização será abortada e revertida. Caso contrário, o gatilho termina e a atualização é feita (mas você precisa confirmar essas alterações de qualquer maneira).
P:O que é um array associativo e por que apenas esse tipo de coleção é usado, em vez de outras coleções (um varray ou uma tabela aninhada)?
R:As coleções PL/SQL são um grande tópico. Siga este link para aprender:http:// docs.oracle.com/cd/E11882_01/appdev.112/e25519/composites.htm#LNPLS005
Resumindo - array associativo (ou index-by table) é como um mapa em java (hashmap, treemap etc) - é um conjunto de pares chave-valor, e cada chave é único . Você pode colocar a mesma chave várias vezes nesse array (com valores diferentes), mas essa chave será armazenada apenas uma vez - ela é única.
Eu a usei para obter um conjunto exclusivo de departamentos.
Considere nosso exemplo de atualização novamente:
UPDATE treballa SET salary = salary + 10
- este comando toca centenas de linhas que possuem o mesmo departamento. Não quero uma coleção com o mesmo departamento duplicado 100 vezes, preciso de um conjunto único de departamentos e quero executar nossa consulta SELECT sum()...
apenas uma vez para cada departamento, não 100 vezes. Com a ajuda do array associativo, isso é feito automaticamente - recebo um conjunto exclusivo de departamentos.