O PL/pgSQL pode ser usado para definir
funções de gatilho em alterações de dados, ou eventos de banco de dados.
A função de gatilho é criada pelo comando
CREATE FUNCTION, declarando-a como uma função sem
argumentos, e retornando o tipo de dados trigger
(para gatilhos de alteração de dados) ou event_trigger
(para gatilhos de evento de banco de dados).
São definidas, automaticamente, variáveis locais especiais chamadas
TG_ para
descrever a condição que acionou a chamada.
alguma_coisa
Um gatilho de alteração de dados
é declarado como uma função sem argumentos, e retornando o tipo
trigger.
Note que a função deve ser declarada sem argumentos, mesmo que
espere receber alguns argumentos especificados em
CREATE TRIGGER — estes argumentos são
passados via TG_ARGV, conforme descrito abaixo.
Quando uma função PL/pgSQL é chamada como gatilho, são criadas automaticamente diversas variáveis especiais no bloco de nível superior. Elas são:
NEW record #
Nova linha de banco de dados para operações de
INSERT/UPDATE
em gatilhos no nível de linha.
Esta variável é nula em gatilhos no nível de instrução e para
operações de DELETE.
OLD record #
Linha antiga do banco de dados para operações de
UPDATE/DELETE
em gatilhos no nível de linha.
Esta variável é nula em gatilhos no nível de instrução e para
operações de INSERT.
TG_NAME name #Nome do gatilho disparado.
TG_WHEN text #
BEFORE, AFTER ou
INSTEAD OF, dependendo da definição do gatilho.
TG_LEVEL text #
ROW ou STATEMENT,
dependendo da definição do gatilho.
TG_OP text #
Operação que disparou o gatilho:
INSERT, UPDATE,
DELETE ou TRUNCATE.
TG_RELID oid (references pg_class.oid) #ID do objeto da tabela que causou o disparo do gatilho.
TG_RELNAME name #
Tabela que causou o disparo do gatilho.
Está obsoleta no memento podendo desaparecer em uma versão futura.
Deve ser usado TG_TABLE_NAME em seu lugar.
TG_TABLE_NAME name #Tabela que causou o disparo do gatilho.
TG_TABLE_SCHEMA name #Esquema da tabela que causou o disparo do gatilho.
TG_NARGS integer #
Número de argumentos fornecidos à função de gatilho na instrução
CREATE TRIGGER.
TG_ARGV text[] #
Argumentos da instrução CREATE TRIGGER.
A contagem do índice começa em 0.
Índices inválidos (menor que 0 ou maior ou igual a
tg_nargs) resultam em um valor nulo.
A função de gatilho deve retornar NULL, ou um valor
de registro/linha com a mesma estrutura da tabela para a
qual o gatilho foi acionado.
Os gatilhos no nível de linha disparados antes (BEFORE),
podem retornar nulo para informar ao gerenciador de gatilhos para
pular o restante da operação para esta linha
(ou seja, os gatilhos subsequentes não são acionados e
INSERT/UPDATE/DELETE
não ocorre para esta linha).
Se for retornado um valor não nulo, a operação continuará com este
valor de linha.
Retornar um valor de linha diferente do valor original de
NEW altera a linha que será inserida ou atualizada.
Portanto, se a função de gatilho desejar que a ação de gatilho
prossiga normalmente sem alterar o valor da linha, deverá ser
retornado NEW (ou um valor igual a ele).
Para alterar a linha a ser armazenada, é possível substituir valores
individuais diretamente em NEW, e retornar o
NEW modificado, ou construir um novo registro/linha
completo para retornar.
No caso do gatilho anterior ter sido para DELETE,
o valor retornado não tem efeito direto, mas deve ser não nulo para
permitir que a ação do gatilho continue.
Note que NEW é nulo nos gatilhos
DELETE, portanto, retornar a mesma coisa
geralmente não faz sentido.
O modo usual em gatilhos DELETE é retornar
OLD.
Os gatilhos INSTEAD OF (sendo sempre gatilhos
no nível de linha, só podendo ser usados em visões)
podem retornar nulo para indicar que não foi realizada nenhuma
atualização, e que o restante da operação para esta linha deve ser
ignorado (ou seja, os gatilhos subsequentes não serão acionados,
e a linha não será contada no status de linhas afetadas pelo
INSERT/UPDATE/DELETE
circundante).
Caso contrário, um valor não nulo deverá ser retornado para indicar
que o gatilho executou a operação solicitada.
Para as operações INSERT e UPDATE,
o valor retornado deverá ser NEW, que a função do
gatilho poderá modificar para dar suporte a
INSERT RETURNING e
UPDATE RETURNING
(isto também afetará o valor da linha passado para quaisquer
gatilhos subsequentes, ou passado para o alias especial
EXCLUDED em uma instrução INSERT
com a cláusula ON CONFLICT DO UPDATE).
Para operações DELETE, o valor retornado deverá
ser OLD.
O valor retornado por um gatilho no nível de linha disparado após
(AFTER), ou um gatilho no nível de instrução
disparado antes (BEFORE) ou após
(AFTER) é sempre ignorado; pode muito bem ser nulo.
Entretanto, qualquer um desses tipos de gatilho ainda pode
interromper toda a operação levantando um erro.
O Exemplo 41.5 mostra uma função de gatilho escrita em PL/pgSQL.
Exemplo 41.5. Função de gatilho escrita em PL/pgSQL
O gatilho desse exemplo garante que, sempre que uma linha for inserida ou atualizada na tabela, o nome do usuário e a hora corrente sejam estampados na linha. E verifica se é fornecido o nome do funcionário e se o salário é um valor positivo.
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Verificar se o nome do funcionário e o salário foram fornecidos.
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname não pode ser nulo';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% não pode ter um salário nulo', NEW.empname;
END IF;
-- Quem trabalha para nós quando precisa pagar por isto?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% não pode ter um salário negativo', NEW.empname;
END IF;
-- Lembre-se de quem mudou a folha de pagamento e quando
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE FUNCTION emp_stamp();
Outra maneira de registrar alterações em uma tabela envolve a criação de uma nova tabela contendo uma linha para cada inserção, atualização ou exclusão ocorrida. Esta abordagem pode ser vista como auditoria de alterações em uma tabela. O Exemplo 41.6 mostra uma função de gatilho para auditoria escrita em PL/pgSQL.
Exemplo 41.6. Função de gatilho para auditoria escrita em PL/pgSQL
O gatilho desse exemplo garante que qualquer inserção, atualização
ou exclusão de uma linha na tabela emp seja
registrada (ou seja, auditada) na tabela emp_audit.
O nome do usuário e a hora corrente são estampados na linha de
auditoria, mais o tipo de operação realizada na tabela
emp.
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
BEGIN
--
-- Criar uma linha em emp_audit para refletir a operação
-- realizada em emp, utilizando a variável especial TG_OP
-- para calcular a operação.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), current_user, OLD.*;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), current_user, NEW.*;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), current_user, NEW.*;
END IF;
RETURN NULL; -- O resultado é ignorado, porque se trata de um gatilho AFTER.
END;
$emp_audit$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE FUNCTION process_emp_audit();
Uma variação do exemplo anterior usa uma visão juntando a tabela principal à tabela de auditoria, para mostrar quando cada entrada foi modificada pela última vez. Esta abordagem ainda registra a trilha de auditoria completa das alterações na tabela, mas também apresenta uma visão simplificada da trilha de auditoria, mostrando apenas o registro de data e hora da última modificação derivada da trilha de auditoria para cada entrada. O Exemplo 41.7 mostra uma Função de gatilho de visão para auditoria escrita em PL/pgSQL.
Exemplo 41.7. Função de gatilho de visão para auditoria escrita em PL/pgSQL
Este exemplo usa um gatilho na visão para torná-la atualizável,
e garantir que qualquer inserção, atualização ou exclusão de uma
linha na visão seja registrada (ou seja, auditada) na tabela
emp_audit.
O nome do usuário e a hora corrente são registrados, mais
o tipo de operação realizada. A visão mostra a hora da última
modificação de cada linha.
CREATE TABLE emp (
empname text PRIMARY KEY,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer,
stamp timestamp NOT NULL
);
CREATE VIEW emp_view AS
SELECT e.empname,
e.salary,
max(ea.stamp) AS last_updated
FROM emp e
LEFT JOIN emp_audit ea ON ea.empname = e.empname
GROUP BY 1, 2;
CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
BEGIN
--
-- Executar a operação necessária em emp e criar uma linha em
-- emp_audit para refletir a alteração feita em emp.
--
IF (TG_OP = 'DELETE') THEN
DELETE FROM emp WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
OLD.last_updated = now();
INSERT INTO emp_audit VALUES('D', current_user, OLD.*);
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
IF NOT FOUND THEN RETURN NULL; END IF;
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('U', current_user, NEW.*);
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp VALUES(NEW.empname, NEW.salary);
NEW.last_updated = now();
INSERT INTO emp_audit VALUES('I', current_user, NEW.*);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
FOR EACH ROW EXECUTE FUNCTION update_emp_view();
Outro uso de gatilho é manter uma tabela de resumo de outra tabela. O resumo resultante pode ser usado no lugar da tabela original para determinadas consultas — muitas vezes com tempos de execução muito reduzidos. Esta técnica é comumente usada em Armazéns de Dados (Data Warehousing), onde as tabelas de dados medidos ou observados (chamadas de tabelas de fatos) podem ser extremamente grandes. O Exemplo 41.8 mostra uma função de gatilho escrita em PL/pgSQL que mantém uma tabela de resumo para uma tabela de fatos em um armazém de dados.
Exemplo 41.8. Função de gatilho para manter a tabela de resumo escrita em PL/pgSQL
O esquema detalhado aqui é parcialmente baseado no exemplo Grocery Store do livro The Data Warehouse Toolkit de .
--
-- Tabelas principais - dimensão temporal e fatos de vendas.
--
CREATE TABLE time_dimension (
time_key integer NOT NULL,
day_of_week integer NOT NULL,
day_of_month integer NOT NULL,
month integer NOT NULL,
quarter integer NOT NULL,
year integer NOT NULL
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);
CREATE TABLE sales_fact (
time_key integer NOT NULL,
product_key integer NOT NULL,
store_key integer NOT NULL,
amount_sold numeric(12,2) NOT NULL,
units_sold integer NOT NULL,
amount_cost numeric(12,2) NOT NULL
);
CREATE INDEX sales_fact_time ON sales_fact(time_key);
--
-- Tabela resumo - vendas por período.
--
CREATE TABLE sales_summary_bytime (
time_key integer NOT NULL,
amount_sold numeric(15,2) NOT NULL,
units_sold numeric(12) NOT NULL,
amount_cost numeric(15,2) NOT NULL
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);
--
-- Função e gatilho para alterar a(s) coluna(s) sumarizada(s)
-- em operações de UPDATE, INSERT e DELETE.
--
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER
AS $maint_sales_summary_bytime$
DECLARE
delta_time_key integer;
delta_amount_sold numeric(15,2);
delta_units_sold numeric(12);
delta_amount_cost numeric(15,2);
BEGIN
-- Cálculo do(s) valor(es) do incremento/decremento.
IF (TG_OP = 'DELETE') THEN
delta_time_key = OLD.time_key;
delta_amount_sold = -1 * OLD.amount_sold;
delta_units_sold = -1 * OLD.units_sold;
delta_amount_cost = -1 * OLD.amount_cost;
ELSIF (TG_OP = 'UPDATE') THEN
-- proibir atualizações que alterem time_key -
-- (provavelmente não muito trabalhoso, já que DELETE + INSERT
-- será como a maioria das alterações serão feitas).
IF ( OLD.time_key != NEW.time_key) THEN
RAISE EXCEPTION 'Update of time_key : % -> % not allowed',
OLD.time_key, NEW.time_key;
END IF;
delta_time_key = OLD.time_key;
delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
delta_units_sold = NEW.units_sold - OLD.units_sold;
delta_amount_cost = NEW.amount_cost - OLD.amount_cost;
ELSIF (TG_OP = 'INSERT') THEN
delta_time_key = NEW.time_key;
delta_amount_sold = NEW.amount_sold;
delta_units_sold = NEW.units_sold;
delta_amount_cost = NEW.amount_cost;
END IF;
-- Inserir ou atualizar a linha de sumário com os novos valores.
<<insert_update>>
LOOP
UPDATE sales_summary_bytime
SET amount_sold = amount_sold + delta_amount_sold,
units_sold = units_sold + delta_units_sold,
amount_cost = amount_cost + delta_amount_cost
WHERE time_key = delta_time_key;
EXIT insert_update WHEN found;
BEGIN
INSERT INTO sales_summary_bytime (
time_key,
amount_sold,
units_sold,
amount_cost)
VALUES (
delta_time_key,
delta_amount_sold,
delta_units_sold,
delta_amount_cost
);
EXIT insert_update;
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
-- não fazer nada
END;
END LOOP insert_update;
RETURN NULL;
END;
$maint_sales_summary_bytime$ LANGUAGE plpgsql;
CREATE TRIGGER maint_sales_summary_bytime
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
FOR EACH ROW EXECUTE FUNCTION maint_sales_summary_bytime();
INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
SELECT * FROM sales_summary_bytime;
DELETE FROM sales_fact WHERE product_key = 1;
SELECT * FROM sales_summary_bytime;
UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;
Os gatilhos AFTER também podem usar
tabelas de transição para inspecionar todo
o conjunto de linhas alteradas pela instrução de disparo.
O comando CREATE TRIGGER atribui nomes a uma,
ou as duas tabelas de transição e, em seguida, a função pode se
referir a estes nomes como se fossem tabelas temporárias de
leitura-apenas.
O Exemplo 41.9
mostra o uso de tabela de transição.
Exemplo 41.9. Auditoria com tabelas de transição
Este exemplo produz os mesmos resultados que o
Exemplo 41.6, mas em vez de
usar um gatilho que dispara para cada linha, usa um gatilho que
dispara uma vez por instrução, após coletar as informações
relevantes em uma tabela de transição.
Pode ser muito mais rápido do que a abordagem de
gatilho de linha quando a instrução de chamada modifica muitas linhas.
Note que deve ser feita uma declaração de gatilho separada para
cada tipo de evento, porque as cláusulas REFERENCING
devem ser diferentes para cada caso.
Mas isto não impede de usar uma única função de gatilho, se assim
se desejar.
(Na prática, pode ser melhor usar três funções separadas, evitando
os testes de tempo de execução em TG_OP.)
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
BEGIN
--
-- Criar linhas na tabela emp_audit para refletir as operações
-- realizadas em emp, utilizando a variável especial TG_OP
-- para determinar a operação.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit
SELECT 'D', now(), current_user, o.* FROM old_table o;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit
SELECT 'U', now(), current_user, n.* FROM new_table n;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit
SELECT 'I', now(), current_user, n.* FROM new_table n;
END IF;
RETURN NULL; -- o resultado é ignorado, porque se trata de um gatilho AFTER.
END;
$emp_audit$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit_ins
AFTER INSERT ON emp
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_upd
AFTER UPDATE ON emp
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_del
AFTER DELETE ON emp
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
O PL/pgSQL pode ser usado para definir
Gatilhos de evento.
O PostgreSQL requer que a função a ser
chamada como gatilho de evento seja declarada como uma função sem
argumentos, e retornando o tipo event_trigger.
Quando uma função PL/pgSQL é chamada como gatilho de evento, são criadas automaticamente diversas variáveis especiais no bloco de nível superior. Elas são:
Exemplo 41.10 mostra um exemplo de uma função de gatilho de evento no PL/pgSQL.
Exemplo 41.10. Função de gatilho de evento no PL/pgSQL
Este gatilho de exemplo simplesmente lança uma mensagem de
NOTICE sempre que um comando compatível é executado
[126].
CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE FUNCTION snitch();